diff --git a/Cargo.toml b/Cargo.toml index 6422e0503bf8..5871282ad94d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,25 +11,25 @@ members = [ "wezterm-gui", "wezterm-mux-server", "wezterm-open-url", - "wezterm-ssh" + "wezterm-ssh", ] resolver = "2" -exclude = [ - "termwiz/codegen" -] +exclude = ["termwiz/codegen"] [profile.release] opt-level = 3 +lto = "thin" +codegen-units = 1 # debug = 2 [profile.dev] # https://jakedeichert.com/blog/reducing-rust-incremental-compilation-times-on-macos-by-70-percent/ # Disabled because it breaks builds on Windows -#split-debuginfo = "unpacked" +split-debuginfo = "unpacked" [patch.crates-io] -xcb = {git="https://github.com/rust-x-bindings/rust-xcb", rev="dbdaa01c178c6fbe68bd51b7ad44c08172181083"} # waiting on a release with https://github.com/rust-x-bindings/rust-xcb/pull/230 +xcb = { git = "https://github.com/rust-x-bindings/rust-xcb", rev = "dbdaa01c178c6fbe68bd51b7ad44c08172181083" } # waiting on a release with https://github.com/rust-x-bindings/rust-xcb/pull/230 # 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" } diff --git a/config/src/config.rs b/config/src/config.rs index 19d5b9c81d90..8cc675faf42f 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -461,6 +461,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")] diff --git a/docs/config/appearance.md b/docs/config/appearance.md index 99cb144dce56..116d5a2141dd 100644 --- a/docs/config/appearance.md +++ b/docs/config/appearance.md @@ -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 diff --git a/docs/config/lua/config/tab_bar_title_fill.md b/docs/config/lua/config/tab_bar_title_fill.md new file mode 100644 index 000000000000..195e2593d6dd --- /dev/null +++ b/docs/config/lua/config/tab_bar_title_fill.md @@ -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 +``` diff --git a/wezterm-gui/src/tabbar.rs b/wezterm-gui/src/tabbar.rs index 06ac12c33200..363e75ed5fb9 100644 --- a/wezterm-gui/src/tabbar.rs +++ b/wezterm-gui/src/tabbar.rs @@ -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 consits of the tab titles, the new tab button, and some padding + title_width.saturating_sub(new_tab.len() + 3) / (tab_info.len()) + } else { + config.tab_max_width + }; + let tab_titles: Vec = if config.show_tabs_in_tab_bar { tab_info .iter() @@ -337,7 +345,7 @@ impl TabBarState { pane_info, config, false, - config.tab_max_width, + config_tab_max_width, ) }) .collect() @@ -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; @@ -399,9 +405,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. @@ -411,7 +418,7 @@ impl TabBarState { pane_info, config, hover, - tab_title_len, + tab_title_max_len, ); let cell_attrs = if active { diff --git a/wezterm-gui/src/termwindow/box_model.rs b/wezterm-gui/src/termwindow/box_model.rs index 5f0c046123ae..6993445a8cb3 100644 --- a/wezterm-gui/src/termwindow/box_model.rs +++ b/wezterm-gui/src/termwindow/box_model.rs @@ -239,6 +239,7 @@ pub struct Element { pub max_width: Option, pub min_width: Option, pub min_height: Option, + pub fill_width: bool, } impl Element { @@ -262,6 +263,7 @@ impl Element { max_width: None, min_width: None, min_height: None, + fill_width: false, } } @@ -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 { @@ -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()) { @@ -773,6 +774,67 @@ 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(); + } + } + } + } + println!( + "Static element size i {}, Available min right x is {}", + static_element_size, available_min_right_x + ); + 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); diff --git a/wezterm-gui/src/termwindow/render/fancy_tab_bar.rs b/wezterm-gui/src/termwindow/render/fancy_tab_bar.rs index 56a50705d9ed..c826027de730 100644 --- a/wezterm-gui/src/termwindow/render/fancy_tab_bar.rs +++ b/wezterm-gui/src/termwindow/render/fancy_tab_bar.rs @@ -124,7 +124,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 { @@ -299,9 +304,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 @@ -335,7 +342,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!(), @@ -439,8 +451,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 { @@ -451,11 +465,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);