Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for stretching tab bar to fill width of tab bar #4403

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ ar = "x86_64-w64-mingw32-gcc-ar"
# enabled for the target, so let's turn that on here.
[target.x86_64-pc-windows-msvc]
rustflags = "-C target-feature=+crt-static"

[aarch64-unknown-linux-gnu]
rustflags = ["-C", "linker-plugin-lto", "-C", "link-arg=-fuse-ld=lld"]

5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ members = [
"wezterm-uds",
]
resolver = "2"
exclude = [
"termwiz/codegen"
]
exclude = ["termwiz/codegen"]

[profile.release]
opt-level = 3
# debug = 2
lto = "fat"
codegen-units = 1

[profile.dev]
# https://jakedeichert.com/blog/reducing-rust-incremental-compilation-times-on-macos-by-70-percent/
Expand All @@ -31,4 +31,4 @@ opt-level = 3
[patch.crates-io]
# We use our own vendored cairo, which has minimal deps and should just
# build via cargo.
cairo-sys-rs = {path="deps/cairo", version="0.18.0"}
cairo-sys-rs = { path = "deps/cairo", version = "0.18.0" }
2 changes: 2 additions & 0 deletions assets/macos/WezTerm.app/Contents/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
<string>An application launched via WezTerm would like to access your Downloads folder.</string>
<key>NSSystemAdministrationUsageDescription</key>
<string>An application launched via WezTerm requires elevated permission.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>An application launched via WeZterm would like to access Bluetooth adapter.</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
Expand Down
4 changes: 4 additions & 0 deletions config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,10 @@ pub struct Config {
#[dynamic(default)]
pub tab_and_split_indices_are_zero_based: bool,

/// Specifies to fill the width of the window with the tab bar
#[dynamic(default)]
pub tab_bar_fill: bool,

/// Specifies the maximum width that a tab can have in the
/// tab bar. Defaults to 16 glyphs in width.
#[dynamic(default = "default_tab_max_width")]
Expand Down
2 changes: 2 additions & 0 deletions docs/config/appearance.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ details.
bar at the bottom of the window instead of the top
* [tab_max_width](lua/config/tab_max_width.md) sets the maximum width, measured in cells,
of a given tab when using retro tab mode.
* [tab_bar_fill](lua/config/tab_bar_fill.md) sets the fancy tab bar to fill
the width of the title bar.

#### Native (Fancy) Tab Bar appearance

Expand Down
15 changes: 15 additions & 0 deletions docs/config/lua/config/tab_bar_title_fill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
tags:
- tab_bar
---
# `tab_bar_fill`

Specifies that the fancy tab bar should allow tab titles
to take up the entire width of the tab bar.
In this mode, maximum tab width is ignored.

Defaults to false.

```lua
config.tab_bar_fill = true
```
23 changes: 15 additions & 8 deletions wezterm-gui/src/tabbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,14 @@ impl TabBarState {
// are symbols representing minimize, maximize and close.

let mut active_tab_no = 0;
let tab_info_len = tab_info.len().max(1);
let config_tab_max_width = if config.tab_bar_fill {
// We have no layout, so this is a rough estimate
// The tab bar consists of the tab titles, the new tab button, and some padding
title_width.saturating_sub(new_tab.len() + 2 + tab_info_len) / (tab_info_len)
} else {
config.tab_max_width
};

let tab_titles: Vec<TitleText> = if config.show_tabs_in_tab_bar {
tab_info
Expand All @@ -337,7 +345,7 @@ impl TabBarState {
pane_info,
config,
false,
config.tab_max_width,
config_tab_max_width,
)
})
.collect()
Expand All @@ -346,18 +354,16 @@ impl TabBarState {
};
let titles_len: usize = tab_titles.iter().map(|s| s.len).sum();
let number_of_tabs = tab_titles.len();

let available_cells =
title_width.saturating_sub(number_of_tabs.saturating_sub(1) + new_tab.len());
let tab_width_max = if config.use_fancy_tab_bar || available_cells >= titles_len {
// We can render each title with its full width
usize::max_value()
usize::MAX
} else {
// We need to clamp the length to balance them out
available_cells / number_of_tabs
}
.min(config.tab_max_width);

.min(config_tab_max_width);
let mut line = Line::with_width(0, SEQ_ZERO);

let mut x = 0;
Expand Down Expand Up @@ -400,9 +406,10 @@ impl TabBarState {
}

for (tab_idx, tab_title) in tab_titles.iter().enumerate() {
let tab_title_len = tab_title.len.min(tab_width_max);
// The title is allowed to grow to the max size of the computed tab width
let tab_title_max_len = tab_title.len.max(tab_width_max).min(tab_width_max);
let active = tab_idx == active_tab_no;
let hover = !active && is_tab_hover(mouse_x, x, tab_title_len);
let hover = !active && is_tab_hover(mouse_x, x, tab_title_max_len);

// Recompute the title so that it factors in both the hover state
// and the adjusted maximum tab width based on available space.
Expand All @@ -412,7 +419,7 @@ impl TabBarState {
pane_info,
config,
hover,
tab_title_len,
tab_title_max_len,
);

let cell_attrs = if active {
Expand Down
100 changes: 79 additions & 21 deletions wezterm-gui/src/termwindow/box_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ pub struct Element {
pub max_width: Option<Dimension>,
pub min_width: Option<Dimension>,
pub min_height: Option<Dimension>,
pub fill_width: bool,
}

impl Element {
Expand All @@ -262,6 +263,7 @@ impl Element {
max_width: None,
min_width: None,
min_height: None,
fill_width: false,
}
}

Expand Down Expand Up @@ -689,8 +691,8 @@ impl super::TermWindow {
let mut block_pixel_height: f32 = 0.;
let mut computed_kids = vec![];
let mut max_x: f32 = 0.;
let mut float_width: f32 = 0.;
let mut y_coord: f32 = 0.;
let mut filled_layout_contexts = Vec::new();

for child in kids {
if child.display == DisplayType::Block {
Expand All @@ -713,40 +715,39 @@ impl super::TermWindow {
context.bounds.max_y() - (context.bounds.min_y() + y_coord),
),
};
let kid = self.compute_element(
&LayoutContext {
bounds,
gl_state: context.gl_state,
height: context.height,
metrics: context.metrics,
width: DimensionContext {
dpi: context.width.dpi,
pixel_cell: context.width.pixel_cell,
pixel_max: max_width,
},
zindex: context.zindex + element.zindex,
let layout_context = LayoutContext {
bounds,
gl_state: context.gl_state,
height: context.height,
metrics: context.metrics,
width: DimensionContext {
dpi: context.width.dpi,
pixel_cell: context.width.pixel_cell,
pixel_max: max_width,
},
child,
)?;
zindex: context.zindex + element.zindex,
};
let kid = self.compute_element(&layout_context, child)?;
match child.float {
Float::Right => {
float_width += float_width.max(kid.bounds.width());
}
Float::None => {
block_pixel_width += kid.bounds.width();
max_x = max_x.max(block_pixel_width);
}
// Float right is taken care of below
_ => {}
}
if child.fill_width {
filled_layout_contexts.push(layout_context);
}
block_pixel_height = block_pixel_height.max(kid.bounds.height());

computed_kids.push(kid);
}

// Respect min-width
max_x = max_x.max(min_width);

let mut float_max_x = (max_x + float_width).min(max_width);

// Right floated things start at the right edge, and move left as needed
let mut float_max_x = max_width;
let pixel_height = (y_coord + block_pixel_height).max(min_height);

for (kid, child) in computed_kids.iter_mut().zip(kids.iter()) {
Expand All @@ -773,6 +774,63 @@ impl super::TermWindow {
}
}

// We have to compute fill after all elements have been floated
if filled_layout_contexts.len() > 0 {
// This mechanism assumes that we don't mix float right and
// fill width elements and try to have it work That is, we
// assume all float right elements are all the way to the
// right, in a row, and are not marked to be filled
let mut available_min_right_x = context.bounds.max_x();
let mut static_element_size = 0.;
for (kid, child) in computed_kids.iter_mut().zip(kids.iter()) {
// Subtract out the space from non-filling, non right-floating elements
if !child.fill_width {
match child.float {
Float::Right => {
available_min_right_x =
available_min_right_x.min(kid.bounds.min_x());
}
Float::None => {
static_element_size += kid.bounds.width();
}
}
}
}
let mut current_kid = 0;

// We only fill from the leftmost point to the leftmost point of the first float right element.
// We assume we have all the space not taken up by non-filled elements.
let available_space = available_min_right_x - static_element_size;
// Evenly distribute remaining space
let new_width = available_space / filled_layout_contexts.len() as f32;
// We know there is at least one kid or we would not be in this loop
let mut new_origin = computed_kids.first().unwrap().bounds.origin.x;
for (kid, child) in computed_kids.iter_mut().zip(kids.iter()) {
let old_bounds = kid.bounds;
if child.fill_width {
// Recompute
let layout_context = &mut filled_layout_contexts[current_kid];
current_kid = current_kid + 1;
layout_context.width = DimensionContext {
dpi: context.width.dpi,
pixel_cell: context.width.pixel_cell,
pixel_max: max_width,
};
layout_context.bounds = euclid::rect(
new_origin,
old_bounds.origin.y,
new_width,
old_bounds.height(),
);
let new_kid = self.compute_element(layout_context, child)?;
*kid = new_kid;
} else {
kid.translate(euclid::vec2(new_origin - old_bounds.origin.x, 0.));
}
new_origin = kid.bounds.max_x();
}
}

computed_kids.sort_by(|a, b| a.zindex.cmp(&b.zindex));

let content_rect = euclid::rect(0., 0., max_x.min(max_width), pixel_height);
Expand Down
25 changes: 17 additions & 8 deletions wezterm-gui/src/termwindow/render/fancy_tab_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,12 @@ impl crate::TermWindow {
bottom: Dimension::Cells(0.),
})
.border(BoxDimension::new(Dimension::Pixels(0.)))
.colors(bar_colors.clone()),
.colors(bar_colors.clone())
.float(if item.item == TabBarItem::LeftStatus {
Float::None
} else {
Float::Right
}),
TabBarItem::NewTabButton => Element::new(
&font,
ElementContent::Poly {
Expand Down Expand Up @@ -301,9 +306,11 @@ impl crate::TermWindow {
_ => 0.,
})
.sum();

let max_tab_width = ((self.dimensions.pixel_width as f32 / num_tabs)
- (1.5 * metrics.cell_size.width as f32))
.max(0.);
let min_tab_width = 0.;

// Reserve space for the native titlebar buttons
if self
Expand Down Expand Up @@ -338,7 +345,12 @@ impl crate::TermWindow {
}
TabBarItem::Tab { tab_idx, active } => {
let mut elem = item_to_elem(item);
elem.max_width = Some(Dimension::Pixels(max_tab_width));
elem.min_width = Some(Dimension::Pixels(min_tab_width));
if self.config.tab_bar_fill {
elem.fill_width = true;
} else {
elem.max_width = Some(Dimension::Pixels(max_tab_width));
}
elem.content = match elem.content {
ElementContent::Text(_) => unreachable!(),
ElementContent::Poly { .. } => unreachable!(),
Expand Down Expand Up @@ -442,8 +454,10 @@ impl crate::TermWindow {
Dimension::Cells(0.5)
};

let mut new_children = left_eles;
new_children.append(&mut right_eles);
children.push(
Element::new(&font, ElementContent::Children(left_eles))
Element::new(&font, ElementContent::Children(new_children))
.vertical_align(VerticalAlign::Bottom)
.colors(bar_colors.clone())
.padding(BoxDimension {
Expand All @@ -454,11 +468,6 @@ impl crate::TermWindow {
})
.zindex(1),
);
children.push(
Element::new(&font, ElementContent::Children(right_eles))
.colors(bar_colors.clone())
.float(Float::Right),
);

let content = ElementContent::Children(children);

Expand Down
9 changes: 6 additions & 3 deletions window/src/os/wayland/copy_and_paste.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,12 @@ impl CopyAndPaste {
impl WaylandState {
pub(super) fn resolve_copy_and_paste(&mut self) -> Option<Arc<Mutex<CopyAndPaste>>> {
let active_surface_id = self.active_surface_id.borrow();
let active_surface_id = active_surface_id.as_ref().unwrap();
if let Some(pending) = self.surface_to_pending.get(&active_surface_id) {
Some(Arc::clone(&pending.lock().unwrap().copy_and_paste))
if let Some(active_surface_id) = active_surface_id.as_ref() {
if let Some(pending) = self.surface_to_pending.get(&active_surface_id) {
Some(Arc::clone(&pending.lock().unwrap().copy_and_paste))
} else {
None
}
} else {
None
}
Expand Down
2 changes: 1 addition & 1 deletion window/src/os/wayland/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl Dispatch<WlKeyboard, KeyboardData> for WaylandState {
}

Err(err) => {
log::error!("Error processing keymap change: {:#}", err);
log::error!("{}", err);
}
}
}
Expand Down
Loading