Skip to content

Commit

Permalink
Add support for stretching tab bar to fill width of tab bar
Browse files Browse the repository at this point in the history
Resolves #1914
  • Loading branch information
dberlin committed May 5, 2024
1 parent ee61e61 commit cd4c097
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 37 deletions.
4 changes: 4 additions & 0 deletions config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,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 @@ -324,6 +324,14 @@ impl TabBarState {

let mut active_tab_no = 0;

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
.iter()
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

0 comments on commit cd4c097

Please sign in to comment.