diff --git a/i18n/en/cosmic_term.ftl b/i18n/en/cosmic_term.ftl index d4a8c90..bdc68c5 100644 --- a/i18n/en/cosmic_term.ftl +++ b/i18n/en/cosmic_term.ftl @@ -59,6 +59,7 @@ use-bright-bold = Make bold text brighter splits = Splits focus-follow-mouse = Typing focus follows mouse show-pane-borders = Show pane borders +unfocused-pane-dim = Dim unfocused panes ### Advanced advanced = Advanced diff --git a/i18n/fr/cosmic_term.ftl b/i18n/fr/cosmic_term.ftl index 350158b..f699685 100644 --- a/i18n/fr/cosmic_term.ftl +++ b/i18n/fr/cosmic_term.ftl @@ -172,3 +172,4 @@ keywords = Commande;Shell;Terminal;CLI; tab-new-inherit-working-directory = Les nouveaux onglets et fenêtres utilisent le répertoire actuel tab-new-inherit-working-directory-description = Ouvrir les nouveaux onglets et fenêtres dans le répertoire de travail de l'onglet actif show-pane-borders = Afficher les bordures des panneaux +unfocused-pane-dim = Atténuation des panneaux inactifs diff --git a/src/config.rs b/src/config.rs index caedf4d..5b434bc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -232,6 +232,10 @@ pub struct Config { pub profiles: BTreeMap, pub show_headerbar: bool, pub show_pane_borders: bool, + /// [yoda] Voile posé sur les panneaux non focalisés, en pourcentage + /// (`unfocused-split-opacity` de Ghostty : 0,7 d'opacité = 30 % de voile). + #[serde(default = "default_unfocused_pane_dim")] + pub unfocused_pane_dim: u8, pub use_bright_bold: bool, pub syntax_theme_dark: String, pub syntax_theme_light: String, @@ -243,6 +247,13 @@ pub struct Config { pub shortcuts_custom: Shortcuts, } +/// Voile maximal des panneaux non focalisés : Ghostty borne leur opacité à 0,15. +pub const UNFOCUSED_PANE_DIM_MAX: u8 = 85; + +fn default_unfocused_pane_dim() -> u8 { + 30 +} + impl Default for Config { fn default() -> Self { Self { @@ -262,6 +273,7 @@ impl Default for Config { profiles: BTreeMap::new(), show_headerbar: true, show_pane_borders: false, + unfocused_pane_dim: default_unfocused_pane_dim(), syntax_theme_dark: COSMIC_THEME_DARK.to_string(), syntax_theme_light: COSMIC_THEME_LIGHT.to_string(), use_bright_bold: false, @@ -343,6 +355,12 @@ impl Config { Metrics::new(font_size, line_height) } + /// Opacité du voile des panneaux non focalisés ; `None` si désactivé. + pub fn unfocused_pane_dim_ratio(&self) -> Option { + let dim = self.unfocused_pane_dim.min(UNFOCUSED_PANE_DIM_MAX); + (dim > 0).then(|| f32::from(dim) / 100.0) + } + pub fn opacity_ratio(&self) -> f32 { f32::from(self.opacity) / 100.0 } @@ -404,3 +422,22 @@ impl Config { })[&self.font_stretch] } } + +#[cfg(test)] +mod tests { + use super::{Config, UNFOCUSED_PANE_DIM_MAX}; + + #[test] + fn unfocused_pane_dim_ratio() { + let mut config = Config::default(); + // Défaut de Ghostty : opacité 0,7, soit 30 % de voile + assert_eq!(config.unfocused_pane_dim_ratio(), Some(0.3)); + config.unfocused_pane_dim = 0; + assert_eq!(config.unfocused_pane_dim_ratio(), None); + config.unfocused_pane_dim = 100; + assert_eq!( + config.unfocused_pane_dim_ratio(), + Some(f32::from(UNFOCUSED_PANE_DIM_MAX) / 100.0) + ); + } +} diff --git a/src/main.rs b/src/main.rs index be8fce8..e749142 100644 --- a/src/main.rs +++ b/src/main.rs @@ -591,6 +591,7 @@ pub enum Message { ShowHeaderBar(bool), SshConnect(usize), ShowPaneBorders(bool), + UnfocusedPaneDim(u8), SyntaxTheme(ColorSchemeKind, usize), SystemThemeChange, @@ -1729,6 +1730,15 @@ impl App { .add( widget::settings::item::builder(fl!("show-pane-borders")) .toggler(self.config.show_pane_borders, Message::ShowPaneBorders), + ) + .add( + widget::settings::item::builder(fl!("unfocused-pane-dim")) + .description(format!("{}%", self.config.unfocused_pane_dim)) + .control(widget::slider( + 0..=config::UNFOCUSED_PANE_DIM_MAX, + self.config.unfocused_pane_dim, + Message::UnfocusedPaneDim, + )), ); let advanced_section = widget::settings::section() @@ -3371,6 +3381,12 @@ impl Application for App { config_set!(show_pane_borders, show_pane_borders); } } + Message::UnfocusedPaneDim(dim) => { + config_set!( + unfocused_pane_dim, + cmp::min(config::UNFOCUSED_PANE_DIM_MAX, dim) + ); + } Message::UseBrightBold(use_bright_bold) => { if use_bright_bold != self.config.use_bright_bold { config_set!(use_bright_bold, use_bright_bold); @@ -4247,6 +4263,15 @@ impl Application for App { terminal_box = terminal_box.on_mouse_enter(move || Message::MouseEnter(pane)); } + // [yoda] Voile sur les panneaux non focalisés (à la Ghostty), seulement + // quand il y en a plusieurs : on voit d'un coup d'œil où l'on tape. + if let Some(dim) = self.config.unfocused_pane_dim_ratio() + && self.pane_model.panes.panes.len() > 1 + && pane != self.pane_model.focused() + { + terminal_box = terminal_box.dim(dim); + } + // The terminal records what was under the right press; the widget opens the // menu on release, so build it from that state let context_link = terminal diff --git a/src/terminal_box.rs b/src/terminal_box.rs index be73890..9b41a22 100644 --- a/src/terminal_box.rs +++ b/src/terminal_box.rs @@ -120,6 +120,7 @@ pub struct TerminalBox<'a, Message> { on_context_menu: Option) -> Message + 'a>>, on_mouse_enter: Option Message + 'a>>, opacity: Option, + dim: Option, mouse_inside_boundary: Option, on_middle_click: Option Message + 'a>>, on_open_hyperlink: Option Message + 'a>>, @@ -147,6 +148,7 @@ where on_context_menu: None, on_mouse_enter: None, opacity: None, + dim: None, mouse_inside_boundary: None, on_middle_click: None, key_binds, @@ -216,6 +218,13 @@ where self } + /// [yoda] Voile de la couleur du fond posé sur tout le panneau (texte + /// compris), d'opacité `amount` : panneau non focalisé. + pub fn dim(mut self, amount: f32) -> Self { + self.dim = Some(amount); + self + } + pub fn sharp_corners(mut self, sharp_corners: bool) -> Self { self.sharp_corners = sharp_corners; self @@ -915,6 +924,32 @@ where } } + // [yoda] Panneau non focalisé : voile de la couleur du fond, comme + // `unfocused-split-opacity` de Ghostty. Calque à part, sinon le texte + // (primitive du même calque) serait tracé par-dessus. + if let Some(dim) = self.dim { + let bg = terminal.metadata_set[terminal.default_attrs().metadata].bg; + renderer.with_layer(layout.bounds(), |renderer| { + renderer.fill_quad( + Quad { + bounds: layout.bounds(), + border: Border { + radius: border_radius, + ..Default::default() + }, + snap: true, + ..Default::default() + }, + Color::from_rgba( + f32::from(bg.r()) / 255.0, + f32::from(bg.g()) / 255.0, + f32::from(bg.b()) / 255.0, + dim, + ), + ); + }); + } + let duration = instant.elapsed(); log::trace!("redraw {}, {}: {:?}", view_w, view_h, duration); }