From 5b71f5f850c809e4a3f9591a7cdb63128583fab6 Mon Sep 17 00:00:00 2001 From: l-const Date: Mon, 17 Feb 2025 05:17:21 +0200 Subject: [PATCH] (feat): Text zoom for cosmic-edit. --- i18n/en/cosmic_edit.ftl | 5 +++ src/config.rs | 13 ++++++- src/key_bind.rs | 6 +++ src/main.rs | 86 ++++++++++++++++++++++++++++++++++++++++- src/menu.rs | 4 ++ src/tab.rs | 14 ++++++- 6 files changed, 123 insertions(+), 5 deletions(-) diff --git a/i18n/en/cosmic_edit.ftl b/i18n/en/cosmic_edit.ftl index 57e80db..c04a49f 100644 --- a/i18n/en/cosmic_edit.ftl +++ b/i18n/en/cosmic_edit.ftl @@ -47,6 +47,8 @@ syntax-dark = Syntax dark syntax-light = Syntax light default-font = Default font default-font-size = Default font size +default-zoom-step = Zoom steps + ### Keyboard shortcuts keyboard-shortcuts = Keyboard shortcuts @@ -99,6 +101,9 @@ spell-check = Spell check... ## View view = View +zoom-in = Zoom in +default-size = Default size +zoom-out = Zoom out indentation = Indentation ### Indentation diff --git a/src/config.rs b/src/config.rs index 2766b41..7a1cdb8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -44,6 +44,7 @@ pub struct Config { pub find_wrap_around: bool, pub font_name: String, pub font_size: u16, + pub font_size_zoom_step_mul_100: u16, pub highlight_current_line: bool, pub line_numbers: bool, pub syntax_theme_dark: String, @@ -63,6 +64,7 @@ impl Default for Config { find_wrap_around: true, font_name: "Fira Mono".to_string(), font_size: 14, + font_size_zoom_step_mul_100: 100, highlight_current_line: true, line_numbers: true, syntax_theme_dark: "COSMIC Dark".to_string(), @@ -75,6 +77,13 @@ impl Default for Config { } impl Config { + pub fn font_size_adjusted(&self, zoom_adj: i8) -> f32 { + let font_size = f32::from(self.font_size).max(1.0); + let adj = f32::from(zoom_adj); + let adj_step = f32::from(self.font_size_zoom_step_mul_100) / 100.0; + (font_size + adj * adj_step).max(1.0) + } + pub fn find_regex(&self, pattern: &str) -> Result { let mut builder = if self.find_use_regex { regex::RegexBuilder::new(pattern) @@ -86,8 +95,8 @@ impl Config { } // Calculate metrics from font size - pub fn metrics(&self) -> Metrics { - let font_size = self.font_size.max(1) as f32; + pub fn metrics(&self, zoom_adj: i8) -> Metrics { + let font_size = self.font_size_adjusted(zoom_adj); let line_height = (font_size * 1.4).ceil(); Metrics::new(font_size, line_height) } diff --git a/src/key_bind.rs b/src/key_bind.rs index e641196..6536775 100644 --- a/src/key_bind.rs +++ b/src/key_bind.rs @@ -35,6 +35,12 @@ pub fn key_binds() -> HashMap { bind!([Ctrl], Key::Character("s".into()), Save); bind!([Ctrl, Shift], Key::Character("S".into()), SaveAsDialog); bind!([Ctrl], Key::Character("a".into()), SelectAll); + // Ctrl+0, Ctrl+-, and Ctrl+= are not special keys for terminals and are free to use + bind!([Ctrl], Key::Character("0".into()), ZoomReset); + bind!([Ctrl], Key::Character("-".into()), ZoomOut); + bind!([Ctrl], Key::Character("=".into()), ZoomIn); + bind!([Ctrl], Key::Character("+".into()), ZoomIn); + bind!([Ctrl], Key::Character("1".into()), TabActivate0); bind!([Ctrl], Key::Character("2".into()), TabActivate1); bind!([Ctrl], Key::Character("3".into()), TabActivate2); diff --git a/src/main.rs b/src/main.rs index 672e1e4..a9de417 100644 --- a/src/main.rs +++ b/src/main.rs @@ -225,6 +225,9 @@ pub enum Action { ToggleSettingsPage, ToggleWordWrap, Undo, + ZoomIn, + ZoomOut, + ZoomReset, } impl Action { @@ -274,6 +277,9 @@ impl Action { Self::ToggleSettingsPage => Message::ToggleContextPage(ContextPage::Settings), Self::ToggleWordWrap => Message::ToggleWordWrap, Self::Undo => Message::Undo, + Self::ZoomIn => Message::ZoomIn, + Self::ZoomOut => Message::ZoomOut, + Self::ZoomReset => Message::ZoomReset, } } } @@ -329,6 +335,10 @@ pub enum Message { Cut, DefaultFont(usize), DefaultFontSize(usize), + ZoomIn, + ZoomOut, + ZoomReset, + DefaultZoomStep(usize), DialogCancel, DialogMessage(DialogMessage), Find(Option), @@ -433,6 +443,8 @@ pub struct App { config: Config, config_state_handler: Option, config_state: ConfigState, + zoom_step_names: Vec, + zoom_steps: Vec, key_binds: HashMap, app_themes: Vec, font_names: Vec, @@ -684,6 +696,36 @@ impl App { self.update_config() } + fn update_render_active_tab_zoom(&mut self, zoom_message: Message) -> Task { + if let Some(Tab::Editor(tab)) = self.active_tab_mut() { + let current_zoom_adj = tab.zoom_adj(); + match zoom_message { + Message::ZoomIn => tab.set_zoom_adj(current_zoom_adj.saturating_add(1)), + Message::ZoomOut => tab.set_zoom_adj(current_zoom_adj.saturating_sub(1)), + _ => {} + } + let entities: Vec<_> = self.tab_model.iter().collect(); + for entity in entities { + if self.tab_model.is_active(entity) { + if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::(entity) { + eprintln!("setting stuff"); + tab.set_config(&self.config); + } + } + } + } + Task::none() + } + + fn reset_tabs_zoom(&mut self) { + let entities: Vec<_> = self.tab_model.iter().collect(); + for entity in entities { + if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::(entity) { + tab.set_zoom_adj(0); + } + } + } + fn save_config_state(&mut self) { if let Some(ref config_state_handler) = self.config_state_handler { if let Err(err) = self.config_state.write_entry(config_state_handler) { @@ -1185,6 +1227,10 @@ impl App { .font_sizes .iter() .position(|font_size| font_size == &self.config.font_size); + let zoom_step_selected = self + .zoom_steps + .iter() + .position(|zoom_step| zoom_step == &self.config.font_size_zoom_step_mul_100); widget::settings::view_column(vec![ widget::settings::section() .title(fl!("appearance")) @@ -1229,6 +1275,13 @@ impl App { }), ), ) + .add( + widget::settings::item::builder(fl!("default-zoom-step")).control( + widget::dropdown(&self.zoom_step_names, zoom_step_selected, |index| { + Message::DefaultZoomStep(index) + }), + ), + ) .into(), widget::settings::section() .title(fl!("keyboard-shortcuts")) @@ -1311,6 +1364,13 @@ impl Application for App { theme_names.push(theme_name.to_string()); } + let mut zoom_step_names = Vec::new(); + let mut zoom_steps = Vec::new(); + for zoom_step in [25, 50, 75, 100, 150, 200] { + zoom_step_names.push(format!("{}px", f32::from(zoom_step) / 100.0)); + zoom_steps.push(zoom_step); + } + let mut app = App { core, nav_model: nav_bar::Model::builder().build(), @@ -1320,6 +1380,8 @@ impl Application for App { config_state_handler: flags.config_state_handler, config_state: flags.config_state, key_binds: key_binds(), + zoom_step_names, + zoom_steps, app_themes, font_names, font_size_names, @@ -1699,12 +1761,34 @@ impl Application for App { Message::DefaultFontSize(index) => match self.font_sizes.get(index) { Some(font_size) => { self.config.font_size = *font_size; + self.reset_tabs_zoom(); return self.save_config(); } None => { log::warn!("failed to find font with index {}", index); } }, + Message::ZoomIn => { + return self.update_render_active_tab_zoom(message); + } + Message::ZoomOut => { + return self.update_render_active_tab_zoom(message); + } + Message::ZoomReset => { + self.reset_tabs_zoom(); + return self.save_config(); + } + Message::DefaultZoomStep(index) => match self.zoom_steps.get(index) { + Some(zoom_step) => { + self.config.font_size_zoom_step_mul_100 = *zoom_step; + self.reset_tabs_zoom(); // reset zoom + return self.save_config(); + } + None => { + log::warn!("failed to find zoom step with index {}", index); + } + }, + Message::DialogCancel => { self.dialog_page_opt = None; } @@ -2703,7 +2787,7 @@ impl Application for App { let tab_id = self.tab_model.active(); match self.tab_model.data::(tab_id) { Some(Tab::Editor(tab)) => { - let mut text_box = text_box(&tab.editor, self.config.metrics()) + let mut text_box = text_box(&tab.editor, self.config.metrics(tab.zoom_adj())) .id(self.text_box_id.clone()) .on_auto_scroll(Message::AutoScroll) .on_changed(Message::TabChanged(tab_id)) diff --git a/src/menu.rs b/src/menu.rs index e5cba94..a8c3eb3 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -211,6 +211,10 @@ pub fn menu_bar<'a>( ], ), MenuItem::Divider, + MenuItem::Button(fl!("zoom-in"), None, Action::ZoomIn), + MenuItem::Button(fl!("default-size"), None, Action::ZoomReset), + MenuItem::Button(fl!("zoom-out"), None, Action::ZoomOut), + MenuItem::Divider, MenuItem::CheckBox( fl!("word-wrap"), None, diff --git a/src/tab.rs b/src/tab.rs index 132b8db..bd051f1 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -42,14 +42,15 @@ pub struct EditorTab { attrs: Attrs<'static>, pub editor: Mutex>, pub context_menu: Option, + pub zoom_adj: i8, } impl EditorTab { pub fn new(config: &Config) -> Self { //TODO: do not repeat, used in App::init let attrs = Attrs::new().family(cosmic_text::Family::Monospace); - - let mut buffer = Buffer::new_empty(config.metrics()); + let zoom_adj = Default::default(); + let mut buffer = Buffer::new_empty(config.metrics(zoom_adj)); buffer.set_text( font_system().write().unwrap().raw(), "", @@ -69,6 +70,7 @@ impl EditorTab { attrs, editor: Mutex::new(ViEditor::new(editor)), context_menu: None, + zoom_adj, }; // Update any other config settings @@ -309,6 +311,14 @@ impl EditorTab { false } + pub fn zoom_adj(&self) -> i8 { + self.zoom_adj + } + + pub fn set_zoom_adj(&mut self, value: i8) { + self.zoom_adj = value; + } + // Code adapted from cosmic-text ViEditor search pub fn search(&self, regex: &Regex, forwards: bool, wrap_around: bool) -> bool { let mut editor = self.editor.lock().unwrap();