diff --git a/Cargo.lock b/Cargo.lock index a4e7dfc..1cb8dc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -871,6 +871,7 @@ dependencies = [ "fork", "i18n-embed", "i18n-embed-fl", + "indexmap", "lazy_static", "libcosmic", "log", diff --git a/Cargo.toml b/Cargo.toml index 9c701fa..23e50fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" alacritty_terminal = "0.20" env_logger = "0.10" lazy_static = "1" +indexmap = "2" log = "0.4" serde = { version = "1", features = ["serde_derive"] } tokio = { version = "1", features = ["sync"] } diff --git a/src/terminal.rs b/src/terminal.rs index a512008..34bcd7f 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -19,6 +19,7 @@ use cosmic::{iced::advanced::graphics::text::font_system, widget::segmented_butt use cosmic_text::{ Attrs, AttrsList, Buffer, BufferLine, CacheKeyFlags, Family, Metrics, Shaping, Weight, Wrap, }; +use indexmap::IndexSet; use std::{ borrow::Cow, collections::HashMap, @@ -132,6 +133,28 @@ fn linear_color(color: cosmic_text::Color) -> cosmic_text::Color { ) } +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct Metadata { + pub bg: cosmic_text::Color, + pub underline_color: cosmic_text::Color, + pub flags: Flags, +} + +impl Metadata { + fn new(bg: cosmic_text::Color, underline_color: cosmic_text::Color) -> Self { + let flags = Flags::empty(); + Self { bg, underline_color, flags } + } + + fn with_underline_color(self, underline_color: cosmic_text::Color) -> Self { + Self { underline_color, ..self } + } + + fn with_flags(self, flags: Flags) -> Self { + Self { flags, ..self } + } +} + pub struct Terminal { default_attrs: Attrs<'static>, buffer: Arc, @@ -146,6 +169,7 @@ pub struct Terminal { pub needs_update: bool, search_regex_opt: Option, search_value: String, + pub metadata_set: IndexSet, } impl Terminal { @@ -165,13 +189,22 @@ impl Terminal { let use_bright_bold = app_config.use_bright_bold; let metrics = Metrics::new(14.0, 20.0); + + let default_bg = convert_color(&colors, Color::Named(NamedColor::Background)); + let default_fg = convert_color(&colors, Color::Named(NamedColor::Foreground)); + + let mut metadata_set = IndexSet::new(); + let default_metada = Metadata::new(default_bg, default_fg); + let (default_metada_idx, _) = metadata_set.insert_full(default_metada); + //TODO: set color to default fg let default_attrs = Attrs::new() .family(Family::Monospace) .weight(Weight(font_weight)) .stretch(font_stretch) - .color(convert_color(&colors, Color::Named(NamedColor::Foreground))) - .metadata(convert_color(&colors, Color::Named(NamedColor::Background)).0 as usize); + .color(default_fg) + .metadata(default_metada_idx); + let mut buffer = Buffer::new_empty(metrics); let (cell_width, cell_height) = { @@ -219,6 +252,7 @@ impl Terminal { needs_update: true, search_regex_opt: None, search_value: String::new(), + metadata_set, } } @@ -490,14 +524,19 @@ impl Terminal { } } if changed { + self.metadata_set.clear(); + let default_bg = convert_color(&colors, Color::Named(NamedColor::Background)); + let default_fg = convert_color(&colors, Color::Named(NamedColor::Foreground)); + + let default_metadata = Metadata::new(default_bg, default_fg); + let (default_metadata_idx, _) = self.metadata_set.insert_full(default_metadata); + self.default_attrs = Attrs::new() .family(Family::Monospace) .weight(Weight(config.font_weight)) .stretch(config.typed_font_stretch()) - .color(convert_color(&colors, Color::Named(NamedColor::Foreground))) - .metadata( - convert_color(&colors, Color::Named(NamedColor::Background)).0 as usize, - ); + .color(default_fg) + .metadata(default_metadata_idx); update = true; } } @@ -544,6 +583,9 @@ impl Terminal { let instant = Instant::now(); + // Only keep default + self.metadata_set.truncate(1); + //TODO: is redraw needed after all events? //TODO: use LineDamageBounds { @@ -641,8 +683,16 @@ impl Terminal { // Convert foreground to linear attrs = attrs.color(linear_color(fg)); - // Use metadata as background color - attrs = attrs.metadata(bg.0 as usize); + + let underline_color = indexed.cell.underline_color() + .map(|c| convert_color(&self.colors, c)) + .unwrap_or(fg); + let metadata = Metadata::new(bg, fg) + .with_flags(indexed.cell.flags) + .with_underline_color(underline_color); + let (meta_idx, _) = self.metadata_set.insert_full(metadata); + attrs = attrs.metadata(meta_idx); + //TODO: more flags if indexed.cell.flags.contains(Flags::BOLD) { attrs = attrs.weight(self.bold_font_weight); diff --git a/src/terminal_box.rs b/src/terminal_box.rs index 443ece8..f77dbf8 100644 --- a/src/terminal_box.rs +++ b/src/terminal_box.rs @@ -3,7 +3,7 @@ use alacritty_terminal::{ index::{Column as TermColumn, Point as TermPoint, Side as TermSide}, selection::{Selection, SelectionType}, - term::TermMode, + term::{cell::Flags, TermMode}, }; use cosmic::{ cosmic_theme::palette::{blend::Compose, WithAlpha}, @@ -30,6 +30,7 @@ use cosmic::{ Renderer, }; use cosmic_text::LayoutGlyph; +use indexmap::IndexSet; use std::{ cell::Cell, cmp, @@ -37,7 +38,7 @@ use std::{ time::{Duration, Instant}, }; -use crate::{Terminal, TerminalScroll}; +use crate::{Terminal, TerminalScroll, terminal::Metadata}; pub struct TerminalBox<'a, Message> { terminal: &'a Mutex, @@ -250,7 +251,9 @@ where // Render default background { - let background_color = cosmic_text::Color(terminal.default_attrs().metadata as u32); + let meta = &terminal.metadata_set[terminal.default_attrs().metadata]; + let background_color = meta.bg; + renderer.fill_quad( Quad { bounds: Rectangle::new( @@ -273,17 +276,19 @@ where // Render cell backgrounds that do not match default terminal.with_buffer(|buffer| { for run in buffer.layout_runs() { - struct BgRect { + struct BgRect<'a> { default_metadata: usize, metadata: usize, + glyph_font_size: f32, start_x: f32, end_x: f32, line_height: f32, line_top: f32, view_position: Point, + metadata_set: &'a IndexSet, } - impl BgRect { + impl<'a> BgRect<'a> { fn update( &mut self, glyph: &LayoutGlyph, @@ -294,6 +299,7 @@ where } else { self.fill(renderer); self.metadata = glyph.metadata; + self.glyph_font_size = glyph.font_size; self.start_x = glyph.x; self.end_x = glyph.x + glyph.w; } @@ -304,36 +310,159 @@ where return; } - let color = cosmic_text::Color(self.metadata as u32); - renderer.fill_quad( - Quad { - bounds: Rectangle::new( - self.view_position + Vector::new(self.start_x, self.line_top), - Size::new(self.end_x - self.start_x, self.line_height), - ), - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, + let cosmic_text_to_iced_color = |color: cosmic_text::Color| { Color::new( color.r() as f32 / 255.0, color.g() as f32 / 255.0, color.b() as f32 / 255.0, color.a() as f32 / 255.0, - ), + ) + }; + + macro_rules! mk_pos_offset { + ($x_offset:expr, $bottom_offset:expr) => { + Vector::new(self.start_x + $x_offset, self.line_top + self.line_height - $bottom_offset) + }; + } + + macro_rules! mk_quad { + ($pos_offset:expr, $style_line_height:expr, $width:expr) => { + Quad { + bounds: Rectangle::new( + self.view_position + $pos_offset, + Size::new($width, $style_line_height), + ), + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + } + + }; + ($pos_offset:expr, $style_line_height:expr) => { + mk_quad!($pos_offset, $style_line_height, self.end_x - self.start_x) + }; + } + + let metadata = &self.metadata_set[self.metadata]; + let color = metadata.bg; + renderer.fill_quad( + mk_quad!(mk_pos_offset!(0.0, self.line_height), self.line_height), + cosmic_text_to_iced_color(color), ); + + if !metadata.flags.is_empty() { + let style_line_height = (self.glyph_font_size / 10.0).max(2.0).min(16.0); + + let line_color = cosmic_text_to_iced_color(metadata.underline_color); + + if metadata.flags.contains(Flags::STRIKEOUT) { + let bottom_offset = (self.line_height - style_line_height) / 2.0; + let pos_offset = mk_pos_offset!(0.0, bottom_offset); + let underline_quad = mk_quad!(pos_offset, style_line_height); + renderer.fill_quad(underline_quad, line_color); + } + + if metadata.flags.contains(Flags::UNDERLINE) { + let bottom_offset = style_line_height * 2.0; + let pos_offset = mk_pos_offset!(0.0, bottom_offset); + let underline_quad = mk_quad!(pos_offset, style_line_height); + renderer.fill_quad(underline_quad, line_color); + } + + if metadata.flags.contains(Flags::DOUBLE_UNDERLINE) { + let style_line_height = style_line_height / 2.0; + let gap = style_line_height.max(1.5); + let bottom_offset = (style_line_height + gap) * 2.0; + + let pos_offset1 = mk_pos_offset!(0.0, bottom_offset); + let underline1_quad = mk_quad!(pos_offset1, style_line_height); + + let pos_offset2 = mk_pos_offset!(0.0, bottom_offset/2.0); + let underline2_quad = mk_quad!(pos_offset2, style_line_height); + + renderer.fill_quad(underline1_quad, line_color); + renderer.fill_quad(underline2_quad, line_color); + } + + if metadata.flags.contains(Flags::DOTTED_UNDERLINE) { + let bottom_offset = style_line_height * 2.0; + + let full_width = self.end_x - self.start_x; + let mut accu_width = 0.0; + let mut dot_width = 2.0f32.min(full_width - accu_width); + + while accu_width < full_width { + dot_width = dot_width.min(full_width - accu_width); + let pos_offset = mk_pos_offset!(accu_width, bottom_offset); + let underline_quad = mk_quad!(pos_offset, style_line_height, dot_width); + renderer.fill_quad(underline_quad, line_color); + accu_width += 2.0 * dot_width; + } + } + + if metadata.flags.contains(Flags::DASHED_UNDERLINE) { + let bottom_offset = style_line_height * 2.0; + + let full_width = self.end_x - self.start_x; + let mut accu_width = 0.0; + let mut dash_width = 6.0f32.min(full_width - accu_width); + let gap_width = dash_width / 2.0; + + // gap-width dash first + let pos_offset = mk_pos_offset!(accu_width, bottom_offset); + let underline_quad = mk_quad!(pos_offset, style_line_height, gap_width); + renderer.fill_quad(underline_quad, line_color); + accu_width += gap_width * 2.0; + + while accu_width < full_width { + dash_width = dash_width.min(full_width - accu_width); + let pos_offset = mk_pos_offset!(accu_width, bottom_offset); + let underline_quad = mk_quad!(pos_offset, style_line_height, dash_width); + renderer.fill_quad(underline_quad, line_color); + accu_width += dash_width + gap_width; + } + } + + if metadata.flags.contains(Flags::UNDERCURL) { + let style_line_height = style_line_height.floor(); + let bottom_offset = style_line_height * 1.5; + + let full_width = self.end_x - self.start_x; + let mut accu_width = 0.0; + let mut dot_width = 1.0f32.min(full_width - accu_width); + + while accu_width < full_width { + dot_width = dot_width.min(full_width - accu_width); + + let dot_bottom_offset = match accu_width as u32 % 8 { + 3 | 4 | 5 => bottom_offset + style_line_height, + 2 | 6 => bottom_offset + 2.0 * style_line_height / 3.0, + 1 | 7 => bottom_offset + 1.0 * style_line_height / 3.0, + _ => bottom_offset, + }; + + let pos_offset = mk_pos_offset!(accu_width, dot_bottom_offset); + let underline_quad = mk_quad!(pos_offset, style_line_height, dot_width); + renderer.fill_quad(underline_quad, line_color); + accu_width += dot_width; + } + } + } } } let default_metadata = terminal.default_attrs().metadata; + let metadata_set = &terminal.metadata_set; let mut bg_rect = BgRect { default_metadata, metadata: default_metadata, + glyph_font_size: 0.0, start_x: 0.0, end_x: 0.0, line_height: buffer.metrics().line_height, line_top: run.line_top, view_position, + metadata_set, }; for glyph in run.glyphs.iter() { bg_rect.update(glyph, renderer);