From 3f09e17156eeb405314302c4e19620a1acaa98a9 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 11 Jan 2024 11:33:39 -0700 Subject: [PATCH] Normalize scrollbar style with figma designs --- src/config.rs | 6 +-- src/main.rs | 102 +++++++++++++++++++++++++++++--------------- src/terminal.rs | 20 ++++----- src/terminal_box.rs | 97 ++++++++++++++++++++++++++++++----------- 4 files changed, 153 insertions(+), 72 deletions(-) diff --git a/src/config.rs b/src/config.rs index 77b17ce..9033f19 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,11 +4,11 @@ use cosmic::{ cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry}, theme, }; -use cosmic_text::{Metrics, Weight, Stretch}; +use cosmic_text::{Metrics, Stretch, Weight}; use serde::{Deserialize, Serialize}; -use std::sync::OnceLock; use std::collections::BTreeMap; +use std::sync::OnceLock; pub const CONFIG_VERSION: u64 = 1; @@ -99,7 +99,7 @@ impl Config { static NUM_TO_TYPED_MAP: OnceLock> = OnceLock::new(); NUM_TO_TYPED_MAP.get_or_init(|| { - populate_num_typed_map!{ + populate_num_typed_map! { UltraCondensed, ExtraCondensed, Condensed, SemiCondensed, Normal, SemiExpanded, Expanded, ExtraExpanded, UltraExpanded, } diff --git a/src/main.rs b/src/main.rs index 14d4b11..72686ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,14 +14,20 @@ use cosmic::{ futures::SinkExt, keyboard::{Event as KeyEvent, KeyCode, Modifiers}, subscription::{self, Subscription}, - window, Event, Length, Point, Padding, + window, Event, Length, Padding, Point, }, style, widget::{self, segmented_button}, Application, ApplicationExt, Element, }; -use cosmic_text::{Family, Weight, Stretch, fontdb::FaceInfo}; -use std::{any::TypeId, collections::{HashMap, BTreeMap, BTreeSet}, env, process, sync::Mutex, time::Duration}; +use cosmic_text::{fontdb::FaceInfo, Family, Stretch, Weight}; +use std::{ + any::TypeId, + collections::{BTreeMap, BTreeSet, HashMap}, + env, process, + sync::Mutex, + time::Duration, +}; use tokio::sync::mpsc; use config::{AppTheme, Config, CONFIG_VERSION}; @@ -267,34 +273,43 @@ impl App { .into_iter() .collect(); - self.curr_font_stretch_names = self.curr_font_stretches.iter() + self.curr_font_stretch_names = self + .curr_font_stretches + .iter() .map(|stretch| &self.all_font_stretches_vals_names_map[stretch]) .cloned() .collect::>(); - if !self.curr_font_stretches.contains(&self.config.typed_font_stretch()) { + if !self + .curr_font_stretches + .contains(&self.config.typed_font_stretch()) + { self.config.font_stretch = Stretch::Normal.to_number(); } - let curr_weights = |conf_stretch| curr_font_faces - .iter() - .filter(|face| face.stretch == conf_stretch) - .map(|face| face.weight.0) - .collect::>() // remove duplicates and sort - .into_iter() - .collect(); + let curr_weights = |conf_stretch| { + curr_font_faces + .iter() + .filter(|face| face.stretch == conf_stretch) + .map(|face| face.weight.0) + .collect::>() // remove duplicates and sort + .into_iter() + .collect() + }; self.curr_font_weights = curr_weights(self.config.typed_font_stretch()); if self.curr_font_weights.is_empty() { // stretch fallback - self.config.font_stretch = Stretch::Normal.to_number(); + self.config.font_stretch = Stretch::Normal.to_number(); } self.curr_font_weights = curr_weights(self.config.typed_font_stretch()); assert!(!self.curr_font_weights.is_empty()); - self.curr_font_weight_names = self.curr_font_weights.iter() + self.curr_font_weight_names = self + .curr_font_weights + .iter() .map(|weight| &self.all_font_weights_vals_names_map[weight]) .cloned() .collect::>(); @@ -303,7 +318,10 @@ impl App { self.config.font_weight = Weight::NORMAL.0; } - if !self.curr_font_weights.contains(&self.config.bold_font_weight) { + if !self + .curr_font_weights + .contains(&self.config.bold_font_weight) + { self.config.bold_font_weight = Weight::BOLD.0; } } @@ -354,23 +372,29 @@ impl App { let section = widget::settings::view_section("") .add( widget::settings::item::builder(fl!("default-font-stretch")).control( - widget::dropdown(&self.curr_font_stretch_names, font_stretch_selected, |index| { - Message::DefaultFontStretch(index) - }), + widget::dropdown( + &self.curr_font_stretch_names, + font_stretch_selected, + |index| Message::DefaultFontStretch(index), + ), ), ) .add( widget::settings::item::builder(fl!("default-font-weight")).control( - widget::dropdown(&self.curr_font_weight_names, font_weight_selected, |index| { - Message::DefaultFontWeight(index) - }), + widget::dropdown( + &self.curr_font_weight_names, + font_weight_selected, + |index| Message::DefaultFontWeight(index), + ), ), ) .add( widget::settings::item::builder(fl!("default-bold-font-weight")).control( - widget::dropdown(&self.curr_font_weight_names, bold_font_weight_selected, |index| { - Message::DefaultBoldFontWeight(index) - }), + widget::dropdown( + &self.curr_font_weight_names, + bold_font_weight_selected, + |index| Message::DefaultBoldFontWeight(index), + ), ), ); let padding = Padding { @@ -418,8 +442,10 @@ impl App { )), ) .add( - widget::settings::item::builder(fl!("advanced-font-settings")) - .toggler(self.show_advanced_font_settings, Message::ShowAdvancedFontSettings), + widget::settings::item::builder(fl!("advanced-font-settings")).toggler( + self.show_advanced_font_settings, + Message::ShowAdvancedFontSettings, + ), ); if self.show_advanced_font_settings { @@ -500,13 +526,16 @@ impl Application for App { for face in font_system.raw().db().faces() { // only monospace fonts and weights that match named constants. let weight = face.weight.0; - if face.monospaced && {1..9}.contains(&{weight / 100}) && weight % 100 == 0 { + if face.monospaced && { 1..9 }.contains(&{ weight / 100 }) && weight % 100 == 0 { //TODO: get localized name if possible let font_name = face .families .get(0) .map_or_else(|| face.post_script_name.to_string(), |x| x.0.to_string()); - font_name_faces_map.entry(font_name).or_default().push(face.clone()); + font_name_faces_map + .entry(font_name) + .or_default() + .push(face.clone()); } } @@ -514,8 +543,12 @@ impl Application for App { // a `Stretch::Normal` face. // This is important for fallbacks. font_name_faces_map.retain(|_, v| { - let has_normal = v.iter().any(|face| face.weight == Weight::NORMAL && face.stretch == Stretch::Normal); - let has_bold = v.iter().any(|face| face.weight == Weight::BOLD && face.stretch == Stretch::Normal); + let has_normal = v + .iter() + .any(|face| face.weight == Weight::NORMAL && face.stretch == Stretch::Normal); + let has_bold = v + .iter() + .any(|face| face.weight == Weight::BOLD && face.stretch == Stretch::Normal); has_normal && has_bold }); font_name_faces_map @@ -543,12 +576,12 @@ impl Application for App { }; } - populate_font_weights!{ + populate_font_weights! { THIN, EXTRA_LIGHT, LIGHT, NORMAL, MEDIUM, SEMIBOLD, BOLD, EXTRA_BOLD, BLACK, }; - let mut all_font_stretches_vals_names_map= BTreeMap::new(); + let mut all_font_stretches_vals_names_map = BTreeMap::new(); macro_rules! populate_font_stretches { ($($stretch:ident,)+) => { @@ -560,7 +593,7 @@ impl Application for App { }; } - populate_font_stretches!{ + populate_font_stretches! { UltraCondensed, ExtraCondensed, Condensed, SemiCondensed, Normal, SemiExpanded, Expanded, ExtraExpanded, UltraExpanded, }; @@ -907,7 +940,7 @@ impl Application for App { } Message::ShowAdvancedFontSettings(show) => { self.show_advanced_font_settings = show; - }, + } Message::WindowClose => { return window::close(window::Id::MAIN); } @@ -1094,7 +1127,6 @@ impl Application for App { let (event_tx, mut event_rx) = mpsc::channel(100); output.send(Message::TermEventTx(event_tx)).await.unwrap(); - // Avoid creating two tabs at startup tokio::time::sleep(Duration::from_millis(50)).await; diff --git a/src/terminal.rs b/src/terminal.rs index d700af2..87404c5 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -16,7 +16,7 @@ use alacritty_terminal::{ }; use cosmic::{iced::advanced::graphics::text::font_system, widget::segmented_button}; use cosmic_text::{ - Attrs, AttrsList, Buffer, BufferLine, CacheKeyFlags, Family, Metrics, Shaping, Weight, Stretch, Wrap, + Attrs, AttrsList, Buffer, BufferLine, CacheKeyFlags, Family, Metrics, Shaping, Weight, Wrap, }; use std::{ borrow::Cow, @@ -349,16 +349,15 @@ impl Terminal { let mut update = false; if self.default_attrs.stretch != config.typed_font_stretch() { - self.default_attrs = self.default_attrs.stretch(config.typed_font_stretch()); + self.default_attrs = self.default_attrs.stretch(config.typed_font_stretch()); update_cell_size = true; } if self.default_attrs.weight.0 != config.font_weight { - self.default_attrs = self.default_attrs.weight(Weight(config.font_weight)); + self.default_attrs = self.default_attrs.weight(Weight(config.font_weight)); update_cell_size = true; } - if self.bold_font_weight.0 != config.font_weight { self.bold_font_weight = Weight(config.bold_font_weight); update_cell_size = true; @@ -437,7 +436,7 @@ impl Terminal { // This will be added to the beginning of lines to force the shaper to treat detected RTL // lines as LTR. RTL text would still be rendered correctly. But this fixes the wrong // behavior of it being aligned to the right. - const LRI:char = '\u{2066}'; + const LRI: char = '\u{2066}'; let instant = Instant::now(); @@ -492,11 +491,12 @@ impl Terminal { let mut attrs = self.default_attrs; - let cell_fg = if self.use_bright_bold && indexed.cell.flags.contains(Flags::BOLD) { - as_bright(indexed.cell.fg) - } else { - indexed.cell.fg - }; + let cell_fg = + if self.use_bright_bold && indexed.cell.flags.contains(Flags::BOLD) { + as_bright(indexed.cell.fg) + } else { + indexed.cell.fg + }; let (mut fg, mut bg) = if indexed.cell.flags.contains(Flags::INVERSE) { ( diff --git a/src/terminal_box.rs b/src/terminal_box.rs index be4bfe7..58a2e82 100644 --- a/src/terminal_box.rs +++ b/src/terminal_box.rs @@ -6,6 +6,7 @@ use alacritty_terminal::{ term::TermMode, }; use cosmic::{ + cosmic_theme::palette::{blend::Compose, WithAlpha}, iced::{ advanced::graphics::text::{font_system, Raw}, event::{Event, Status}, @@ -15,13 +16,14 @@ use cosmic::{ }, iced_core::{ clipboard::Clipboard, - image, layout::{self, Layout}, - renderer::{self, Quad}, - text, + renderer::{self, Quad, Renderer as _}, + text::Renderer as _, widget::{self, tree, Widget}, Shell, }, + theme::Theme, + Renderer, }; use cosmic_text::LayoutGlyph; use std::{ @@ -86,11 +88,9 @@ where TerminalBox::new(terminal) } -impl<'a, Message, Renderer> Widget for TerminalBox<'a, Message> +impl<'a, Message> Widget for TerminalBox<'a, Message> where Message: Clone, - Renderer: - renderer::Renderer + image::Renderer + text::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -182,18 +182,18 @@ where &self, tree: &widget::Tree, renderer: &mut Renderer, - _theme: &Renderer::Theme, + theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, - _cursor_position: mouse::Cursor, + cursor_position: mouse::Cursor, viewport: &Rectangle, ) { let instant = Instant::now(); let state = tree.state.downcast_ref::(); - //TODO: make this configurable - let scrollbar_w = 8.0; + let cosmic_theme = theme.cosmic(); + let scrollbar_w = cosmic_theme.spacing.space_xxs as f32; let view_position = layout.position() + [self.padding.left as f32, self.padding.top as f32].into(); @@ -334,19 +334,68 @@ where [view_w as f32, scrollbar_y].into(), Size::new(scrollbar_w, scrollbar_h), ); - let scrollbar_alpha = match &state.dragging { - Some(Dragging::Scrollbar { .. }) => 0.5, - _ => 0.25, + + let pressed = match &state.dragging { + Some(Dragging::Scrollbar { .. }) => true, + _ => false, }; + + let mut hover = false; + if let Some(p) = cursor_position.position_in(layout.bounds()) { + let x = p.x - self.padding.left; + let y = p.y - self.padding.top; + if x >= scrollbar_rect.x && x < (scrollbar_rect.x + scrollbar_rect.width) { + hover = true; + } + } + + let mut scrollbar_draw = scrollbar_rect + Vector::new(view_position.x, view_position.y); + if !hover && !pressed { + // Decrease draw width and keep centered when not hovered or pressed + scrollbar_draw.width /= 2.0; + scrollbar_draw.x += scrollbar_draw.width / 2.0; + } + + // neutral_6, 0.7 + let base_color = cosmic_theme + .palette + .neutral_6 + .without_alpha() + .with_alpha(0.7); + let scrollbar_color: Color = if pressed { + // pressed_state_color, 0.5 + cosmic_theme + .background + .component + .pressed + .without_alpha() + .with_alpha(0.5) + .over(base_color) + .into() + } else if hover { + // hover_state_color, 0.2 + cosmic_theme + .background + .component + .hover + .without_alpha() + .with_alpha(0.2) + .over(base_color) + .into() + } else { + base_color.into() + }; + renderer.fill_quad( Quad { - bounds: scrollbar_rect + Vector::new(view_position.x, view_position.y), - border_radius: (scrollbar_w / 2.0).into(), + bounds: scrollbar_draw, + border_radius: (scrollbar_draw.width / 2.0).into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, - Color::new(1.0, 1.0, 1.0, scrollbar_alpha), + scrollbar_color, ); + state.scrollbar_rect.set(scrollbar_rect); } else { state.scrollbar_rect.set(Rectangle::default()) @@ -728,14 +777,16 @@ where } else if x >= scrollbar_rect.x && x < (scrollbar_rect.x + scrollbar_rect.width) { - if let Some(start_scroll) = terminal.scrollbar() { + if terminal.scrollbar().is_some() { let scroll_ratio = terminal.with_buffer(|buffer| y / buffer.size().1); terminal.scroll_to(scroll_ratio); - state.dragging = Some(Dragging::Scrollbar { - start_y: y, - start_scroll, - }); + if let Some(start_scroll) = terminal.scrollbar() { + state.dragging = Some(Dragging::Scrollbar { + start_y: y, + start_scroll, + }); + } } } } @@ -838,11 +889,9 @@ where } } -impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> +impl<'a, Message> From> for Element<'a, Message, Renderer> where Message: Clone + 'a, - Renderer: - renderer::Renderer + image::Renderer + text::Renderer, { fn from(terminal_box: TerminalBox<'a, Message>) -> Self { Self::new(terminal_box)