From 962e44bbfbb0720e9d8a49c5e34876bfef27b0a8 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sun, 3 Oct 2021 22:00:28 -0700 Subject: [PATCH] WIP: fancier tab bar `use_fancy_tab_bar` switches to an alternate rendering of the tab bar that uses the window_frame config to get a proportional title font to use to render tabs, as well as rendering a few additional elements to space out and make the tabs feel more like tabs. Computing the number of tabs doesn't respect the alternate font at this time. Formatted tab item foreground and background colors are also not respected at this time. refs: #1180 --- config/src/color.rs | 2 +- config/src/lib.rs | 2 + termwiz/src/surface/line.rs | 20 ++ wezterm-gui/src/glyphcache.rs | 16 +- wezterm-gui/src/tabbar.rs | 102 ++++---- wezterm-gui/src/termwindow/mod.rs | 29 ++- wezterm-gui/src/termwindow/mouseevent.rs | 56 ++--- wezterm-gui/src/termwindow/render.rs | 297 +++++++++++++++++++++-- wezterm-gui/src/termwindow/resize.rs | 32 ++- 9 files changed, 425 insertions(+), 131 deletions(-) diff --git a/config/src/color.rs b/config/src/color.rs index 690ff7e3b..5cfbc0a92 100644 --- a/config/src/color.rs +++ b/config/src/color.rs @@ -307,7 +307,7 @@ fn default_inactive_titlebar_bg() -> RgbColor { } fn default_active_titlebar_bg() -> RgbColor { - RgbColor::new_8bpc(0x2b, 0x20, 0x42) + RgbColor::new_8bpc(0x29, 0x29, 0x29) } fn default_inactive_titlebar_fg() -> RgbColor { diff --git a/config/src/lib.rs b/config/src/lib.rs index e49f06c91..cfd1c1798 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -978,6 +978,8 @@ pub struct Config { /// active tab. Clicking on a tab activates it. #[serde(default = "default_true")] pub enable_tab_bar: bool, + #[serde(default)] + pub use_fancy_tab_bar: bool, #[serde(default)] pub tab_bar_at_bottom: bool, diff --git a/termwiz/src/surface/line.rs b/termwiz/src/surface/line.rs index 635597bd2..766fc9254 100644 --- a/termwiz/src/surface/line.rs +++ b/termwiz/src/surface/line.rs @@ -71,6 +71,15 @@ impl Line { } } + pub fn from_cells(cells: Vec) -> Self { + let bits = LineBits::NONE; + Self { + bits, + cells, + seqno: SEQ_ZERO, + } + } + pub fn with_width(width: usize) -> Self { let mut cells = Vec::with_capacity(width); cells.resize_with(width, Cell::blank); @@ -556,6 +565,17 @@ impl Line { self.update_last_change_seqno(seqno); } + pub fn remove_cell(&mut self, x: usize, seqno: SequenceNo) { + if x >= self.cells.len() { + // Already implicitly removed + return; + } + self.invalidate_implicit_hyperlinks(seqno); + self.invalidate_grapheme_at_or_before(x); + self.cells.remove(x); + self.update_last_change_seqno(seqno); + } + pub fn erase_cell_with_margin( &mut self, x: usize, diff --git a/wezterm-gui/src/glyphcache.rs b/wezterm-gui/src/glyphcache.rs index 262669966..58f749a0c 100644 --- a/wezterm-gui/src/glyphcache.rs +++ b/wezterm-gui/src/glyphcache.rs @@ -24,7 +24,7 @@ use termwiz::color::RgbColor; use termwiz::image::{ImageData, ImageDataType}; use termwiz::surface::CursorShape; use wezterm_font::units::*; -use wezterm_font::{FontConfiguration, GlyphInfo}; +use wezterm_font::{FontConfiguration, GlyphInfo, LoadedFont}; use wezterm_term::Underline; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -107,6 +107,7 @@ pub struct CachedGlyph { pub brightness_adjust: f32, pub x_offset: PixelLength, pub y_offset: PixelLength, + pub x_advance: PixelLength, pub bearing_x: PixelLength, pub bearing_y: PixelLength, pub texture: Option>, @@ -313,6 +314,7 @@ impl GlyphCache { info: &GlyphInfo, style: &TextStyle, followed_by_space: bool, + font: Option<&Rc>, ) -> anyhow::Result>> { let key = BorrowedGlyphKey { font_idx: info.font_idx, @@ -327,7 +329,7 @@ impl GlyphCache { } metrics::histogram!("glyph_cache.glyph_cache.miss.rate", 1.); - let glyph = match self.load_glyph(info, style, followed_by_space) { + let glyph = match self.load_glyph(info, style, font, followed_by_space) { Ok(g) => g, Err(err) => { if err @@ -353,6 +355,7 @@ impl GlyphCache { brightness_adjust: 1.0, has_color: false, texture: None, + x_advance: PixelLength::zero(), x_offset: PixelLength::zero(), y_offset: PixelLength::zero(), bearing_x: PixelLength::zero(), @@ -371,6 +374,7 @@ impl GlyphCache { &mut self, info: &GlyphInfo, style: &TextStyle, + font: Option<&Rc>, followed_by_space: bool, ) -> anyhow::Result>> { let base_metrics; @@ -379,7 +383,10 @@ impl GlyphCache { let glyph; { - let font = self.fonts.resolve_font(style)?; + let font = match font { + Some(f) => Rc::clone(f), + None => self.fonts.resolve_font(style)?, + }; base_metrics = font.metrics(); glyph = font.rasterize_glyph(info.glyph_pos, info.font_idx)?; @@ -472,6 +479,7 @@ impl GlyphCache { texture: None, x_offset: info.x_offset * scale, y_offset: info.y_offset * scale, + x_advance: info.x_advance * scale, bearing_x: PixelLength::zero(), bearing_y: PixelLength::zero(), scale, @@ -488,6 +496,7 @@ impl GlyphCache { let bearing_y = glyph.bearing_y * scale; let x_offset = info.x_offset * scale; let y_offset = info.y_offset * scale; + let x_advance = info.x_advance * scale; let (scale, raw_im) = if scale != 1.0 { log::trace!( @@ -513,6 +522,7 @@ impl GlyphCache { texture: Some(tex), x_offset, y_offset, + x_advance, bearing_x, bearing_y, scale, diff --git a/wezterm-gui/src/tabbar.rs b/wezterm-gui/src/tabbar.rs index 369e3394d..a0824b632 100644 --- a/wezterm-gui/src/tabbar.rs +++ b/wezterm-gui/src/tabbar.rs @@ -20,13 +20,14 @@ pub struct TabBarState { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TabBarItem { None, - Tab(usize), + Tab { tab_idx: usize, active: bool }, NewTabButton, } #[derive(Clone, Debug, PartialEq)] -struct TabEntry { - item: TabBarItem, +pub struct TabEntry { + pub item: TabBarItem, + pub title: Line, x: usize, width: usize, } @@ -70,11 +71,11 @@ fn call_format_tab_title( let items = >::from_lua(v, &*lua)?; let esc = format_as_escapes(items.clone())?; - let cells = parse_status_text(&esc, CellAttributes::default()); + let line = parse_status_text(&esc, CellAttributes::default()); Ok(Some(TitleText { items, - len: cells.len(), + len: line.cells().len(), })) } _ => { @@ -162,6 +163,10 @@ impl TabBarState { &self.line } + pub fn items(&self) -> &[TabEntry] { + &self.items + } + /// Build a new tab bar from the current state /// mouse_x is some if the mouse is on the same row as the tab bar. /// title_width is the total number of cell columns in the window. @@ -217,7 +222,7 @@ impl TabBarState { let number_of_tabs = tab_titles.len(); let available_cells = - title_width.saturating_sub(number_of_tabs.saturating_sub(1) + new_tab.len()); + title_width.saturating_sub(number_of_tabs.saturating_sub(1) + new_tab.cells().len()); let tab_width_max = if available_cells >= titles_len { // We can render each title with its full width usize::max_value() @@ -227,7 +232,7 @@ impl TabBarState { } .min(config.tab_max_width); - let mut line = Line::with_width(title_width); + let mut line = Line::with_width(0); let mut x = 0; let mut items = vec![]; @@ -259,44 +264,45 @@ impl TabBarState { let tab_start_idx = x; let esc = format_as_escapes(tab_title.items.clone()).expect("already parsed ok above"); - let cells = parse_status_text(&esc, cell_attrs.clone()); - let mut n = 0; - for cell in cells { - let len = cell.width(); - if n + len > tab_width_max { - break; - } - line.set_cell(x, cell, SEQ_ZERO); - x += len; - n += len; + let mut tab_line = parse_status_text(&esc, cell_attrs.clone()); + + let title = tab_line.clone(); + if tab_line.cells().len() > tab_width_max { + tab_line.resize(tab_width_max, SEQ_ZERO); } + let width = tab_line.cells().len(); + items.push(TabEntry { - item: TabBarItem::Tab(tab_idx), + item: TabBarItem::Tab { tab_idx, active }, + title, x: tab_start_idx, - width: x - tab_start_idx, + width, }); + + line.append_line(tab_line, SEQ_ZERO); + x += width; } // New tab button { - let hover = is_tab_hover(mouse_x, x, new_tab_hover.len()); + let hover = is_tab_hover(mouse_x, x, new_tab_hover.cells().len()); - let cells = if hover { &new_tab_hover } else { &new_tab }; + let new_tab_button = if hover { &new_tab_hover } else { &new_tab }; let button_start = x; + let width = new_tab_button.cells().len(); - for c in cells { - let len = c.width(); - line.set_cell(x, c.clone(), SEQ_ZERO); - x += len; - } + line.append_line(new_tab_button.clone(), SEQ_ZERO); items.push(TabEntry { item: TabBarItem::NewTabButton, + title: new_tab_button.clone(), x: button_start, - width: x - button_start, + width, }); + + x += width; } let black_cell = Cell::blank_with_attrs( @@ -305,28 +311,28 @@ impl TabBarState { .clone(), ); - for idx in x..title_width { - line.set_cell(idx, black_cell.clone(), SEQ_ZERO); + let status_space_available = title_width.saturating_sub(x); + let mut status_line = parse_status_text(right_status, black_cell.attrs().clone()); + items.push(TabEntry { + item: TabBarItem::None, + title: status_line.clone(), + x, + width: status_space_available, + }); + + while status_line.cells().len() > status_space_available { + status_line.remove_cell(0, SEQ_ZERO); } - let rhs_cells = parse_status_text(right_status, black_cell.attrs().clone()); - let rhs_len = rhs_cells.len().min(title_width.saturating_sub(x)); - let skip = rhs_cells.len() - rhs_len; - - for (idx, cell) in rhs_cells.into_iter().skip(skip).rev().enumerate() { - line.set_cell(title_width - (1 + idx), cell, SEQ_ZERO); + line.append_line(status_line, SEQ_ZERO); + while line.cells().len() < title_width { + line.insert_cell(x, black_cell.clone(), title_width, SEQ_ZERO); } Self { line, items } } - pub fn compute_ui_items( - &self, - y: usize, - cell_height: usize, - cell_width: usize, - width: usize, - ) -> Vec { + pub fn compute_ui_items(&self, y: usize, cell_height: usize, cell_width: usize) -> Vec { let mut items = vec![]; let mut last_x = 0; @@ -341,19 +347,11 @@ impl TabBarState { last_x += entry.width; } - items.push(UIItem { - x: last_x * cell_width, - width: width - (last_x * cell_width), - y, - height: cell_height, - item_type: UIItemType::TabBar(TabBarItem::None), - }); - items } } -fn parse_status_text(text: &str, default_cell: CellAttributes) -> Vec { +fn parse_status_text(text: &str, default_cell: CellAttributes) -> Line { let mut pen = default_cell.clone(); let mut cells = vec![]; let mut ignoring = false; @@ -444,5 +442,5 @@ fn parse_status_text(text: &str, default_cell: CellAttributes) -> Vec { } }); flush_print(&mut print_buffer, &mut cells, &pen); - cells + Line::from_cells(cells) } diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index 3afef3948..e3c676d06 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -125,6 +125,15 @@ pub struct UIItem { pub item_type: UIItemType, } +impl UIItem { + pub fn hit_test(&self, x: isize, y: isize) -> bool { + x >= self.x as isize + && x <= (self.x + self.width) as isize + && y >= self.y as isize + && y <= (self.y + self.height) as isize + } +} + #[derive(Clone, Default)] pub struct SemanticZoneCache { seqno: SequenceNo, @@ -525,6 +534,15 @@ impl TermWindow { let render_metrics = RenderMetrics::new(&fontconfig)?; log::trace!("using render_metrics {:#?}", render_metrics); + // Initially we have only a single tab, so take that into account + // for the tab bar state. + let show_tab_bar = config.enable_tab_bar && !config.hide_tab_bar_if_only_one_tab; + let tab_bar_height = if show_tab_bar { + fontconfig.title_font()?.metrics().cell_height.get() as usize + } else { + 0 + }; + let terminal_size = PtySize { rows: physical_rows as u16, cols: physical_cols as u16, @@ -532,20 +550,15 @@ impl TermWindow { pixel_height: (render_metrics.cell_size.height as usize * physical_rows) as u16, }; - // Initially we have only a single tab, so take that into account - // for the tab bar state. - let show_tab_bar = config.enable_tab_bar && !config.hide_tab_bar_if_only_one_tab; - - let rows_with_tab_bar = if show_tab_bar { 1 } else { 0 } + terminal_size.rows; - let dimensions = Dimensions { pixel_width: (terminal_size.pixel_width + config.window_padding.left + resize::effective_right_padding(&config, &render_metrics)) as usize, - pixel_height: ((rows_with_tab_bar * render_metrics.cell_size.height as u16) + pixel_height: ((terminal_size.rows * render_metrics.cell_size.height as u16) + config.window_padding.top - + config.window_padding.bottom) as usize, + + config.window_padding.bottom) as usize + + tab_bar_height, dpi, }; diff --git a/wezterm-gui/src/termwindow/mouseevent.rs b/wezterm-gui/src/termwindow/mouseevent.rs index 803401ec5..e846cf443 100644 --- a/wezterm-gui/src/termwindow/mouseevent.rs +++ b/wezterm-gui/src/termwindow/mouseevent.rs @@ -23,12 +23,7 @@ impl super::TermWindow { self.ui_items .iter() .rev() - .find(|item| { - x >= item.x as isize - && x <= (item.x + item.width) as isize - && y >= item.y as isize - && y <= (item.y + item.height) as isize - }) + .find(|item| item.hit_test(x, y)) .cloned() } @@ -64,37 +59,26 @@ impl super::TermWindow { self.current_mouse_event.replace(event.clone()); let config = &self.config; + let first_line_offset = if self.show_tab_bar && !self.config.tab_bar_at_bottom { + self.tab_bar_pixel_height().unwrap_or(0.) as isize + } else { + 0 + }; let y = (event .coords .y .sub(config.window_padding.top as isize) + .sub(first_line_offset) .max(0) / self.render_metrics.cell_size.height) as i64; - let first_line_offset = if self.show_tab_bar && !self.config.tab_bar_at_bottom { - 1 - } else { - 0 - }; - let tab_bar_y = if self.config.tab_bar_at_bottom { - let num_rows = self - .dimensions - .pixel_height - .sub((config.window_padding.top + config.window_padding.bottom) as usize) - / self.render_metrics.cell_size.height as usize; - num_rows - 1 - } else { - 0 - } as i64; - let in_tab_bar = self.show_tab_bar && y == tab_bar_y && event.coords.y >= 0; - let x = (event .coords .x .sub(config.window_padding.left as isize) .max(0) as f32) / self.render_metrics.cell_size.width as f32; - let x = if !in_tab_bar && !pane.is_mouse_grabbed() { + let x = if !pane.is_mouse_grabbed() { // Round the x coordinate so that we're a bit more forgiving of // the horizontal position when selecting cells x.round() @@ -105,9 +89,6 @@ impl super::TermWindow { self.last_mouse_coords = (x, y); - // y position relative to top of viewport (not including tab bar) - let term_y = y.saturating_sub(first_line_offset); - match event.kind { WMEK::Release(ref press) => { self.current_mouse_buttons.retain(|p| p != press); @@ -169,7 +150,7 @@ impl super::TermWindow { } if let Some((item, start_event)) = self.dragging.take() { - self.drag_ui_item(item, start_event, x, term_y, event, context); + self.drag_ui_item(item, start_event, x, y, event, context); return; } } @@ -182,20 +163,23 @@ impl super::TermWindow { (Some(prior), Some(item)) => { self.leave_ui_item(&prior); self.enter_ui_item(item); + context.invalidate(); } (Some(prior), None) => { self.leave_ui_item(&prior); + context.invalidate(); } (None, Some(item)) => { self.enter_ui_item(item); + context.invalidate(); } (None, None) => {} } if let Some(item) = ui_item { - self.mouse_event_ui_item(item, pane, term_y, event, context); + self.mouse_event_ui_item(item, pane, y, event, context); } else { - self.mouse_event_terminal(pane, x, term_y, event, context); + self.mouse_event_terminal(pane, x, y, event, context); } } @@ -317,10 +301,10 @@ impl super::TermWindow { ) { match event.kind { WMEK::Press(MousePress::Left) => match item { - TabBarItem::Tab(tab_idx) => { + TabBarItem::Tab { tab_idx, .. } => { self.activate_tab(tab_idx as isize).ok(); } - TabBarItem::NewTabButton => { + TabBarItem::NewTabButton { .. } => { self.spawn_tab(&SpawnTabDomain::CurrentPaneDomain); } TabBarItem::None => { @@ -330,16 +314,16 @@ impl super::TermWindow { } }, WMEK::Press(MousePress::Middle) => match item { - TabBarItem::Tab(tab_idx) => { + TabBarItem::Tab { tab_idx, .. } => { self.close_tab_idx(tab_idx).ok(); } - TabBarItem::NewTabButton | TabBarItem::None => {} + TabBarItem::NewTabButton { .. } | TabBarItem::None => {} }, WMEK::Press(MousePress::Right) => match item { - TabBarItem::Tab(_) => { + TabBarItem::Tab { .. } => { self.show_tab_navigator(); } - TabBarItem::NewTabButton => { + TabBarItem::NewTabButton { .. } => { self.show_launcher(); } TabBarItem::None => {} diff --git a/wezterm-gui/src/termwindow/render.rs b/wezterm-gui/src/termwindow/render.rs index ff6f6c58d..02c420c07 100644 --- a/wezterm-gui/src/termwindow/render.rs +++ b/wezterm-gui/src/termwindow/render.rs @@ -2,6 +2,7 @@ use crate::customglyph::BlockKey; use crate::glium::texture::SrgbTexture2d; use crate::glyphcache::{CachedGlyph, GlyphCache}; use crate::shapecache::*; +use crate::tabbar::{TabBarItem, TabEntry}; use crate::termwindow::{ BorrowedShapeCacheKey, MappedQuads, RenderState, ScrollHit, ShapedInfo, TermWindowNotif, UIItem, UIItemType, @@ -15,7 +16,7 @@ use ::window::glium::uniforms::{ use ::window::glium::{uniform, BlendingFunction, LinearBlendingFactor, Surface}; use ::window::WindowOps; use anyhow::anyhow; -use config::{ConfigHandle, HsbTransform, TextStyle, VisualBellTarget}; +use config::{ConfigHandle, HsbTransform, TabBarColors, TextStyle, VisualBellTarget}; use mux::pane::Pane; use mux::renderable::{RenderableDimensions, StableCursorPosition}; use mux::tab::{PositionedPane, PositionedSplit, SplitDirection}; @@ -27,7 +28,7 @@ use termwiz::cell::{unicode_column_width, Blink}; use termwiz::cellcluster::CellCluster; use termwiz::surface::{CursorShape, CursorVisibility}; use wezterm_font::units::PixelLength; -use wezterm_font::{ClearShapeCache, GlyphInfo}; +use wezterm_font::{ClearShapeCache, FontMetrics, GlyphInfo, LoadedFont}; use wezterm_term::color::{ColorAttribute, ColorPalette, RgbColor}; use wezterm_term::{CellAttributes, Line, StableRowIndex}; use window::bitmaps::atlas::SpriteSlice; @@ -35,6 +36,7 @@ use window::bitmaps::Texture2d; use window::color::LinearRgba; pub struct RenderScreenLineOpenGLParams<'a> { + pub first_line_offset: f32, pub line_idx: usize, pub stable_line_idx: Option, pub line: &'a Line, @@ -265,7 +267,242 @@ impl super::TermWindow { None } + fn paint_one_tab( + &self, + mut pos_x: f32, + item: &TabEntry, + colors: &TabBarColors, + style: &TextStyle, + font: &Rc, + metrics: &FontMetrics, + layers: &mut [MappedQuads; 3], + ) -> anyhow::Result<(f32, UIItem)> { + let left_offset = self.dimensions.pixel_width as f32 / 2.; + let top_offset = self.dimensions.pixel_height as f32 / 2.; + + let top_y = metrics.cell_height.get() as f32 / 4.; + + let cell_clusters = item.title.cluster(); + + let pos_y = top_y + metrics.cell_height.get() as f32 + metrics.descender.get() as f32; + + let gl_state = self.render_state.as_ref().unwrap(); + let mut shaped = vec![]; + let mut width = 0.; + for cluster in &cell_clusters { + let glyph_info = + self.cached_cluster_shape(style, &cluster, &gl_state, &item.title, Some(font))?; + for info in glyph_info.iter() { + width += info.glyph.x_advance.get() as f32; + } + shaped.push(glyph_info); + } + + let hover_x_start = pos_x + metrics.cell_width.get() as f32 / 4.; + let hover_x_end = hover_x_start + width; //+ metrics.cell_width.get() as f32 /2.; + + let hover = match &self.current_mouse_event { + Some(event) => { + let mouse_x = event.coords.x as f32; + let mouse_y = event.coords.y as f32; + mouse_x as f32 >= hover_x_start + && mouse_x as f32 <= hover_x_end + && mouse_y as f32 >= top_y + && mouse_y as f32 <= metrics.cell_height.get() as f32 * 1.75 + } + None => false, + }; + + let (bg_color, fg_color, is_status) = match item.item { + TabBarItem::Tab { active, .. } => { + let c = if active { + &colors.active_tab + } else if hover { + &colors.inactive_tab_hover + } else { + &colors.inactive_tab + }; + (c.bg_color, c.fg_color, false) + } + TabBarItem::NewTabButton => { + let c = if hover { + &colors.new_tab_hover + } else { + &colors.new_tab + }; + (c.bg_color, c.fg_color, false) + } + TabBarItem::None => (colors.background, colors.inactive_tab.fg_color, true), + }; + let glyph_color = rgbcolor_to_window_color(fg_color); + + let bg_start; + if is_status { + bg_start = pos_x; + // Right align status glyphs + pos_x = 2. * left_offset - width; + } else { + pos_x += metrics.cell_width.get() as f32 / 4.0; + bg_start = pos_x; + // width += metrics.cell_width.get() as f32 / 2.0; + } + + { + let mut quad = layers[0].allocate()?; + quad.set_position( + bg_start - left_offset, + top_y - top_offset, + bg_start + width - left_offset, + top_y + (metrics.cell_height.get() as f32 * 1.5) - top_offset, + ); + quad.set_texture_adjust(0., 0., 0., 0.); + quad.set_texture(gl_state.util_sprites.filled_box.texture_coords()); + quad.set_is_background(); + quad.set_fg_color(rgbcolor_to_window_color(bg_color)); + quad.set_hsv(None); + } + + for glyph_info in shaped { + for info in glyph_info.iter() { + let glyph = &info.glyph; + if let Some(texture) = glyph.texture.as_ref() { + let x = pos_x + (glyph.x_offset.get() + glyph.bearing_x.get()) as f32; + let y = top_y + pos_y - (glyph.y_offset.get() + glyph.bearing_y.get()) as f32; + + let mut quad = layers[1].allocate()?; + quad.set_position( + x - left_offset, + y - top_offset, + (x - left_offset) + texture.coords.size.width as f32, + (y - top_offset) + texture.coords.size.height as f32, + ); + quad.set_fg_color(glyph_color); + quad.set_texture(texture.texture_coords()); + quad.set_texture_adjust(0., 0., 0., 0.); + quad.set_hsv(if glyph.brightness_adjust != 1.0 { + let hsv = HsbTransform::default(); + Some(HsbTransform { + brightness: hsv.brightness * glyph.brightness_adjust, + ..hsv + }) + } else { + None + }); + quad.set_has_color(glyph.has_color); + } + pos_x += glyph.x_advance.get() as f32; + } + } + + Ok(( + bg_start + width, + UIItem { + x: if is_status { 0 } else { bg_start as usize }, + width: if is_status { + self.dimensions.pixel_width + } else { + width as usize + }, + y: if is_status { 0 } else { top_y as usize }, + height: (metrics.cell_height.get() as f32 * if is_status { 2. } else { 1.5 }) + as usize, + item_type: UIItemType::TabBar(item.item.clone()), + }, + )) + } + + pub fn tab_bar_pixel_height(&self) -> anyhow::Result { + if self.config.use_fancy_tab_bar { + let font = self.fonts.title_font()?; + Ok(font.metrics().cell_height.get() as f32 * 2.) + } else { + Ok(self.render_metrics.cell_size.height as f32) + } + } + + fn paint_fancy_tab_bar(&self) -> anyhow::Result> { + let colors = self + .config + .colors + .as_ref() + .and_then(|c| c.tab_bar.as_ref()) + .cloned() + .unwrap_or_else(TabBarColors::default); + let style = &self.config.window_frame.font; + let font = self.fonts.title_font()?; + let metrics = font.metrics(); + + let mut ui_items = vec![]; + + let items = self.tab_bar.items(); + + let gl_state = self.render_state.as_ref().unwrap(); + let vb = [&gl_state.vb[0], &gl_state.vb[1], &gl_state.vb[2]]; + let mut vb_mut0 = vb[0].current_vb_mut(); + let mut vb_mut1 = vb[1].current_vb_mut(); + let mut vb_mut2 = vb[2].current_vb_mut(); + let mut layers = [ + vb[0].map(&mut vb_mut0), + vb[1].map(&mut vb_mut1), + vb[2].map(&mut vb_mut2), + ]; + + // Overall window titlebar background + { + let mut quad = layers[0].allocate()?; + quad.set_position( + self.dimensions.pixel_width as f32 / -2., + self.dimensions.pixel_height as f32 / -2., + self.dimensions.pixel_width as f32 / 2., + (metrics.cell_height.get() as f32 * 2.) + self.dimensions.pixel_height as f32 / -2., + ); + quad.set_texture_adjust(0., 0., 0., 0.); + quad.set_texture(gl_state.util_sprites.filled_box.texture_coords()); + quad.set_is_background(); + quad.set_fg_color(rgbcolor_to_window_color(if self.focused.is_some() { + self.config.window_frame.active_titlebar_bg + } else { + self.config.window_frame.inactive_titlebar_bg + })); + quad.set_hsv(None); + } + // Dividing line that is logically part of the active tab + { + let mut quad = layers[0].allocate()?; + quad.set_position( + self.dimensions.pixel_width as f32 / -2., + (metrics.cell_height.get() as f32 * 1.75) + + self.dimensions.pixel_height as f32 / -2., + self.dimensions.pixel_width as f32 / 2., + (metrics.cell_height.get() as f32 * 2.) + self.dimensions.pixel_height as f32 / -2., + ); + quad.set_texture_adjust(0., 0., 0., 0.); + quad.set_texture(gl_state.util_sprites.filled_box.texture_coords()); + quad.set_is_background(); + quad.set_fg_color(rgbcolor_to_window_color(colors.active_tab.bg_color)); + quad.set_hsv(None); + } + + let mut x = 0.; + for item in items.iter() { + let (new_x, item) = + self.paint_one_tab(x, item, &colors, style, &font, &metrics, &mut layers)?; + x = new_x; + match item.item_type { + UIItemType::TabBar(TabBarItem::None) => ui_items.insert(0, item), + _ => ui_items.push(item), + } + } + + Ok(ui_items) + } + fn paint_tab_bar(&mut self) -> anyhow::Result<()> { + if self.config.use_fancy_tab_bar { + self.ui_items.append(&mut self.paint_fancy_tab_bar()?); + return Ok(()); + } + let avail_height = self.dimensions.pixel_height.saturating_sub( (self.config.window_padding.top + self.config.window_padding.bottom) as usize, ); @@ -286,7 +523,6 @@ impl super::TermWindow { }, self.render_metrics.cell_size.height as usize, self.render_metrics.cell_size.width as usize, - self.dimensions.pixel_width, )); let window_is_transparent = @@ -315,6 +551,7 @@ impl super::TermWindow { ]; self.render_screen_line_opengl( RenderScreenLineOpenGLParams { + first_line_offset: 0., line_idx: tab_bar_y, stable_line_idx: None, line: self.tab_bar.line(), @@ -375,9 +612,9 @@ impl super::TermWindow { let palette = pos.pane.palette(); let first_line_offset = if self.show_tab_bar && !self.config.tab_bar_at_bottom { - 1 + self.tab_bar_pixel_height()? } else { - 0 + 0. }; let cursor = pos.pane.get_cursor_position(); @@ -497,7 +734,8 @@ impl super::TermWindow { + (pos.left as f32 * cell_width) + self.config.window_padding.left as f32; let pos_y = (self.dimensions.pixel_height as f32 / -2.) - + ((first_line_offset + pos.top) as f32 * cell_height) + + first_line_offset + + (pos.top as f32 * cell_height) + self.config.window_padding.top as f32; quad.set_position( @@ -565,7 +803,8 @@ impl super::TermWindow { + (pos.left as f32 * cell_width) + self.config.window_padding.left as f32; let pos_y = (self.dimensions.pixel_height as f32 / -2.) - + ((first_line_offset + pos.top) as f32 * cell_height) + + first_line_offset + + (pos.top as f32 * cell_height) + self.config.window_padding.top as f32; quad.set_position( @@ -658,7 +897,8 @@ impl super::TermWindow { self.render_screen_line_opengl( RenderScreenLineOpenGLParams { - line_idx: line_idx + first_line_offset, + first_line_offset, + line_idx: line_idx, stable_line_idx: Some(stable_row), line: &line, selection: selrange, @@ -809,9 +1049,9 @@ impl super::TermWindow { let cell_height = self.render_metrics.cell_size.height as f32; let first_row_offset = if self.show_tab_bar && !self.config.tab_bar_at_bottom { - 1 + self.tab_bar_pixel_height()? } else { - 0 + 0. }; let block = BlockKey::from_char(if split.direction == SplitDirection::Horizontal { @@ -835,7 +1075,8 @@ impl super::TermWindow { quad.set_has_color(false); let pos_y = (self.dimensions.pixel_height as f32 / -2.) - + (split.top + first_row_offset) as f32 * cell_height + + split.top as f32 * cell_height + + first_row_offset + self.config.window_padding.top as f32; let pos_x = (self.dimensions.pixel_width as f32 / -2.) + split.left as f32 * cell_width @@ -852,7 +1093,8 @@ impl super::TermWindow { x: self.config.window_padding.left as usize + (split.left * cell_width as usize), width: cell_width as usize, y: self.config.window_padding.top as usize - + (split.top + first_row_offset) * cell_height as usize, + + first_row_offset as usize + + split.top * cell_height as usize, height: split.size * cell_height as usize, item_type: UIItemType::Split(split.clone()), }); @@ -867,7 +1109,8 @@ impl super::TermWindow { x: self.config.window_padding.left as usize + (split.left * cell_width as usize), width: split.size * cell_width as usize, y: self.config.window_padding.top as usize - + (split.top + first_row_offset) * cell_height as usize, + + first_row_offset as usize + + split.top * cell_height as usize, height: cell_height as usize, item_type: UIItemType::Split(split.clone()), }); @@ -897,10 +1140,6 @@ impl super::TermWindow { self.paint_pane_opengl(&pos, num_panes)?; } - if self.show_tab_bar { - self.paint_tab_bar()?; - } - if let Some(pane) = self.get_active_pane_or_overlay() { let splits = self.get_splits(); for split in &splits { @@ -908,6 +1147,10 @@ impl super::TermWindow { } } + if self.show_tab_bar { + self.paint_tab_bar()?; + } + Ok(()) } @@ -937,6 +1180,7 @@ impl super::TermWindow { let cell_width = self.render_metrics.cell_size.width as f32; let cell_height = self.render_metrics.cell_size.height as f32; let pos_y = (self.dimensions.pixel_height as f32 / -2.) + + params.first_line_offset + (params.line_idx + params.pos.map(|p| p.top).unwrap_or(0)) as f32 * cell_height + self.config.window_padding.top as f32; @@ -1140,8 +1384,13 @@ impl super::TermWindow { let style_params = last_style.as_ref().expect("we literally just assigned it"); // Shape the printable text from this cluster - let glyph_info = - self.cached_cluster_shape(style_params.style, &cluster, &gl_state, params.line)?; + let glyph_info = self.cached_cluster_shape( + style_params.style, + &cluster, + &gl_state, + params.line, + None, + )?; let mut current_idx = cluster.first_cell_idx; @@ -1748,6 +1997,7 @@ impl super::TermWindow { style: &TextStyle, glyph_cache: &mut GlyphCache, infos: &[GlyphInfo], + font: Option<&Rc>, ) -> anyhow::Result>>> { let mut glyphs = Vec::with_capacity(infos.len()); for info in infos { @@ -1757,7 +2007,7 @@ impl super::TermWindow { None => false, }; - glyphs.push(glyph_cache.cached_glyph(info, &style, followed_by_space)?); + glyphs.push(glyph_cache.cached_glyph(info, &style, followed_by_space, font)?); } Ok(glyphs) } @@ -1769,6 +2019,7 @@ impl super::TermWindow { cluster: &CellCluster, gl_state: &RenderState, line: &Line, + font: Option<&Rc>, ) -> anyhow::Result>>> { let shape_resolve_start = Instant::now(); let key = BorrowedShapeCacheKey { @@ -1779,7 +2030,10 @@ impl super::TermWindow { Some(Ok(info)) => info, Some(Err(err)) => return Err(err), None => { - let font = self.fonts.resolve_font(style)?; + let font = match font { + Some(f) => Rc::clone(f), + None => self.fonts.resolve_font(style)?, + }; let window = self.window.as_ref().unwrap().clone(); match font.shape( &cluster.text, @@ -1794,6 +2048,7 @@ impl super::TermWindow { &style, &mut gl_state.glyph_cache.borrow_mut(), &info, + Some(&font), )?; let shaped = Rc::new(ShapedInfo::process( &self.render_metrics, diff --git a/wezterm-gui/src/termwindow/resize.rs b/wezterm-gui/src/termwindow/resize.rs index 70f53b65c..20afcec82 100644 --- a/wezterm-gui/src/termwindow/resize.rs +++ b/wezterm-gui/src/termwindow/resize.rs @@ -119,6 +119,12 @@ impl super::TermWindow { let config = &self.config; + let tab_bar_height = if self.show_tab_bar { + self.tab_bar_pixel_height().unwrap_or(0.) + } else { + 0. + }; + let (size, dims) = if let Some(cell_dims) = scale_changed_cells { // Scaling preserves existing terminal dimensions, yielding a new // overall set of window dimensions @@ -129,11 +135,12 @@ impl super::TermWindow { pixel_width: cell_dims.cols as u16 * self.render_metrics.cell_size.width as u16, }; - let rows = size.rows + if self.show_tab_bar { 1 } else { 0 }; + let rows = size.rows; let cols = size.cols; let pixel_height = (rows * self.render_metrics.cell_size.height as u16) - + (config.window_padding.top + config.window_padding.bottom); + + (config.window_padding.top + config.window_padding.bottom) + + tab_bar_height as u16; let pixel_width = (cols * self.render_metrics.cell_size.width as u16) + (config.window_padding.left + self.effective_right_padding(&config)); @@ -150,12 +157,12 @@ impl super::TermWindow { let avail_width = dimensions.pixel_width.saturating_sub( (config.window_padding.left + self.effective_right_padding(&config)) as usize, ); - let avail_height = dimensions.pixel_height.saturating_sub( - (config.window_padding.top + config.window_padding.bottom) as usize, - ); + let avail_height = dimensions + .pixel_height + .saturating_sub((config.window_padding.top + config.window_padding.bottom) as usize) + .saturating_sub(tab_bar_height as usize); - let rows = (avail_height / self.render_metrics.cell_size.height as usize) - .saturating_sub(if self.show_tab_bar { 1 } else { 0 }); + let rows = avail_height / self.render_metrics.cell_size.height as usize; let cols = avail_width / self.render_metrics.cell_size.width as usize; let size = PtySize { @@ -293,16 +300,21 @@ impl super::TermWindow { }; let show_tab_bar = config.enable_tab_bar && !config.hide_tab_bar_if_only_one_tab; + let tab_bar_height = if show_tab_bar { + self.tab_bar_pixel_height()? as usize + } else { + 0 + }; - let rows_with_tab_bar = if show_tab_bar { 1 } else { 0 } + terminal_size.rows; let dimensions = Dimensions { pixel_width: ((terminal_size.cols * render_metrics.cell_size.width as u16) + config.window_padding.left + effective_right_padding(&config, &render_metrics)) as usize, - pixel_height: ((rows_with_tab_bar * render_metrics.cell_size.height as u16) + pixel_height: ((terminal_size.rows * render_metrics.cell_size.height as u16) + config.window_padding.top - + config.window_padding.bottom) as usize, + + config.window_padding.bottom) as usize + + tab_bar_height, dpi: config.dpi.unwrap_or_else(|| ::window::default_dpi()) as usize, };