From 6c0e1043146b589297ff9a58ae3f8d8e826a571a Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 3 Nov 2023 11:02:25 -0600 Subject: [PATCH] Add support for setting theme and default font --- Cargo.lock | 10 +-- Cargo.toml | 2 +- i18n/en/cosmic_edit.ftl | 6 ++ src/config.rs | 24 +++++++ src/main.rs | 141 +++++++++++++++++++++++++++++++++------- src/tab.rs | 41 ++++++------ src/text_box.rs | 8 --- 7 files changed, 177 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a0af40..d07ff4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -851,7 +851,7 @@ dependencies = [ [[package]] name = "cosmic-text" version = "0.10.0" -source = "git+https://github.com/pop-os/cosmic-text?branch=vi-editor#e62fea5efddb20fd1bc518e0d733a86f6858fa73" +source = "git+https://github.com/pop-os/cosmic-text?branch=vi-editor#f8da72f7a3cbbb4d0807eb089ff10c602b38baed" dependencies = [ "fontdb 0.15.0", "libm", @@ -5544,18 +5544,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" [[package]] name = "zerocopy" -version = "0.7.23" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e50cbb27c30666a6108abd6bc7577556265b44f243e2be89a8bc4e07a528c107" +checksum = "092cd76b01a033a9965b9097da258689d9e17c69ded5dcf41bca001dd20ebc6d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.23" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25f293fe55f0a48e7010d65552bb63704f6ceb55a1a385da10d41d8f78e4a3d" +checksum = "a13a20a7c6a90e2034bcc65495799da92efcec6a8dd4f3fcb6f7a48988637ead" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 868181c..6d5961e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ rust-embed = "6.3.0" [dependencies.cosmic-text] git = "https://github.com/pop-os/cosmic-text" branch = "vi-editor" -features = ["syntect", "two-face", "vi"] +features = ["two-face", "vi"] #path = "../cosmic-text" [dependencies.libcosmic] diff --git a/i18n/en/cosmic_edit.ftl b/i18n/en/cosmic_edit.ftl index 305c13d..8d58168 100644 --- a/i18n/en/cosmic_edit.ftl +++ b/i18n/en/cosmic_edit.ftl @@ -13,6 +13,12 @@ line-count = Lines ## Settings settings = Settings +## Appearance +appearance = Appearance +theme = Theme +default-font = Default font +default-font-size = Default font size + ### Keyboard shortcuts keyboard-shortcuts = Keyboard shortcuts enable-vim-bindings = Enable Vim bindings diff --git a/src/config.rs b/src/config.rs index f23caf5..c97c075 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,6 +3,10 @@ use std::{collections::HashMap, fmt}; use crate::{ContextPage, Message}; +const DEFAULT_FONT_SIZE: f32 = 14.0; +const DEFAULT_SYNTAX_THEME_DARK: &'static str = "base16-eighties.dark"; +const DEFAULT_SYNTAX_THEME_LIGHT: &'static str = "base16-ocean.light"; + // Makes key binding definitions simpler const CTRL: Modifiers = Modifiers::CTRL; const ALT: Modifiers = Modifiers::ALT; @@ -70,6 +74,9 @@ impl fmt::Display for KeyBind { #[derive(Clone, Debug)] pub struct Config { + pub font_size: f32, + pub syntax_theme_dark: String, + pub syntax_theme_light: String, pub vim_bindings: bool, pub word_wrap: bool, pub keybinds: HashMap, @@ -79,9 +86,26 @@ impl Config { //TODO: load from cosmic-config pub fn load() -> Self { Self { + font_size: DEFAULT_FONT_SIZE, + syntax_theme_dark: DEFAULT_SYNTAX_THEME_DARK.to_string(), + syntax_theme_light: DEFAULT_SYNTAX_THEME_LIGHT.to_string(), vim_bindings: false, word_wrap: false, keybinds: KeyBind::load(), } } + + // Calculate line height from font size + pub fn line_height(&self) -> f32 { + (self.font_size * 1.4).ceil() + } + + // Get current syntax theme based on dark mode + pub fn syntax_theme(&self, dark: bool) -> &str { + if dark { + &self.syntax_theme_dark + } else { + &self.syntax_theme_light + } + } } diff --git a/src/main.rs b/src/main.rs index 4d8e743..77c55c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,13 +6,13 @@ use cosmic::{ iced::{ clipboard, event, keyboard, subscription, widget::{row, text}, - window, Alignment, Length, Limits, + window, Alignment, Length, }, style, widget::{self, button, icon, nav_bar, segmented_button, view_switcher}, ApplicationExt, Element, }; -use cosmic_text::{Edit, FontSystem, SwashCache, SyntaxSystem, ViMode}; +use cosmic_text::{Edit, Family, FontSystem, SwashCache, SyntaxSystem, ViMode}; use std::{ env, fs, path::{Path, PathBuf}, @@ -65,6 +65,7 @@ fn main() -> Result<(), Box> { pub enum Message { Cut, Copy, + DefaultFont(usize), KeyBind(KeyBind), NewFile, NewWindow, @@ -76,6 +77,7 @@ pub enum Message { PasteValue(String), Quit, Save, + SyntaxTheme(usize, bool), TabActivate(segmented_button::Entity), TabClose(segmented_button::Entity), Todo, @@ -104,6 +106,8 @@ pub struct App { nav_model: segmented_button::SingleSelectModel, tab_model: segmented_button::SingleSelectModel, config: Config, + font_names: Vec, + theme_names: Vec, context_page: ContextPage, } @@ -208,8 +212,7 @@ impl App { } pub fn open_tab(&mut self, path_opt: Option) { - let mut tab = Tab::new(); - tab.set_config(&self.config); + let mut tab = Tab::new(&self.config); if let Some(path) = path_opt { tab.open(path); } @@ -324,11 +327,37 @@ impl cosmic::Application for App { /// Creates the application, and optionally emits command on initialize. fn init(core: Core, _flags: Self::Flags) -> (Self, Command) { + let font_names = { + let mut font_names = Vec::new(); + let font_system = FONT_SYSTEM.lock().unwrap(); + //TODO: do not repeat, used in Tab::new + let attrs = cosmic_text::Attrs::new().family(Family::Monospace); + for face in font_system.db().faces() { + if attrs.matches(face) && face.monospaced { + //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_names.push(font_name); + } + } + font_names.sort(); + font_names + }; + + let mut theme_names = Vec::with_capacity(SYNTAX_SYSTEM.theme_set.themes.len()); + for (theme_name, _theme) in SYNTAX_SYSTEM.theme_set.themes.iter() { + theme_names.push(theme_name.to_string()); + } + let mut app = App { core, nav_model: nav_bar::Model::builder().build(), tab_model: segmented_button::Model::builder().build(), config: Config::load(), + font_names, + theme_names, context_page: ContextPage::Settings, }; @@ -444,6 +473,27 @@ impl cosmic::Application for App { } None => {} }, + Message::DefaultFont(index) => { + match self.font_names.get(index) { + Some(font_name) => { + let mut font_system = FONT_SYSTEM.lock().unwrap(); + font_system.db_mut().set_monospace_family(font_name); + // This does a complete reset of shaping data! + let entities: Vec<_> = self.tab_model.iter().collect(); + for entity in entities { + if let Some(tab) = self.tab_model.data_mut::(entity) { + let mut editor = tab.editor.lock().unwrap(); + for line in editor.buffer_mut().lines.iter_mut() { + line.reset(); + } + } + } + } + None => { + log::warn!("failed to find font with index {}", index); + } + } + } Message::KeyBind(key_bind) => { for (config_key_bind, config_message) in self.config.keybinds.iter() { if config_key_bind == &key_bind { @@ -459,7 +509,7 @@ impl cosmic::Application for App { //TODO: support multi-window in winit match env::current_exe() { Ok(exe) => match process::Command::new(&exe).spawn() { - Ok(child) => {} + Ok(_child) => {} Err(err) => { log::error!("failed to execute {:?}: {}", exe, err); } @@ -506,15 +556,13 @@ impl cosmic::Application for App { None => message::none(), }); } - Message::PasteValue(value) => { - match self.active_tab() { - Some(tab) => { - let mut editor = tab.editor.lock().unwrap(); - editor.insert_string(&value, None); - } - None => {} + Message::PasteValue(value) => match self.active_tab() { + Some(tab) => { + let mut editor = tab.editor.lock().unwrap(); + editor.insert_string(&value, None); } - } + None => {} + }, Message::Quit => { //TODO: prompt for save? return window::close(); @@ -540,6 +588,19 @@ impl cosmic::Application for App { self.tab_model.text_set(self.tab_model.active(), title); } } + Message::SyntaxTheme(index, dark) => match self.theme_names.get(index) { + Some(theme_name) => { + if dark { + self.config.syntax_theme_dark = theme_name.to_string(); + } else { + self.config.syntax_theme_light = theme_name.to_string(); + } + self.update_config(); + } + None => { + log::warn!("failed to find syntax theme with index {}", index); + } + }, Message::TabActivate(entity) => { self.tab_model.activate(entity); return self.update_tab(); @@ -643,14 +704,50 @@ impl cosmic::Application for App { .into() } ContextPage::Settings => { - widget::settings::view_column(vec![widget::settings::view_section(fl!( - "keyboard-shortcuts" - )) - .add( - widget::settings::item::builder(fl!("enable-vim-bindings")) - .toggler(self.config.vim_bindings, Message::VimBindings), - ) - .into()]) + let dark = cosmic::theme::is_dark(); + let current_theme_name = if dark { + &self.config.syntax_theme_dark + } else { + &self.config.syntax_theme_light + }; + let theme_selected = self + .theme_names + .iter() + .position(|theme_name| theme_name == current_theme_name); + let font_selected = { + let font_system = FONT_SYSTEM.lock().unwrap(); + let current_font_name = font_system.db().family_name(&Family::Monospace); + self.font_names + .iter() + .position(|font_name| font_name == current_font_name) + }; + widget::settings::view_column(vec![ + widget::settings::view_section(fl!("appearance")) + .add(widget::settings::item::builder(fl!("theme")).control( + widget::dropdown(&self.theme_names, theme_selected, move |index| { + Message::SyntaxTheme(index, dark) + }), + )) + .add( + widget::settings::item::builder(fl!("default-font")).control( + widget::dropdown(&self.font_names, font_selected, |index| { + Message::DefaultFont(index) + }), + ), + ) + .add( + widget::settings::item::builder(fl!("default-font-size")).control( + widget::dropdown(&["TODO"], Some(0), |_index| Message::Todo), + ), + ) + .into(), + widget::settings::view_section(fl!("keyboard-shortcuts")) + .add( + widget::settings::item::builder(fl!("enable-vim-bindings")) + .toggler(self.config.vim_bindings, Message::VimBindings), + ) + .into(), + ]) .into() } }) @@ -718,7 +815,7 @@ impl cosmic::Application for App { } fn subscription(&self) -> subscription::Subscription { - subscription::events_with(|event, status| match event { + subscription::events_with(|event, _status| match event { event::Event::Keyboard(keyboard::Event::KeyPressed { modifiers, key_code, diff --git a/src/tab.rs b/src/tab.rs index 4e98f65..5a1efaa 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -3,16 +3,7 @@ use cosmic_text::{Attrs, Buffer, Edit, Metrics, SyntaxEditor, ViEditor, Wrap}; use std::{fs, path::PathBuf, sync::Mutex}; -use crate::{fl, text_box, Config, FONT_SYSTEM, SYNTAX_SYSTEM}; - -static FONT_SIZES: &'static [Metrics] = &[ - Metrics::new(10.0, 14.0), // Caption - Metrics::new(14.0, 20.0), // Body - Metrics::new(20.0, 28.0), // Title 4 - Metrics::new(24.0, 32.0), // Title 3 - Metrics::new(28.0, 36.0), // Title 2 - Metrics::new(32.0, 44.0), // Title 1 -]; +use crate::{fl, Config, FONT_SYSTEM, SYNTAX_SYSTEM}; pub struct Tab { pub path_opt: Option, @@ -21,24 +12,30 @@ pub struct Tab { } impl Tab { - pub fn new() -> Self { + pub fn new(config: &Config) -> Self { + //TODO: do not repeat, used in App::init let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Monospace); let editor = SyntaxEditor::new( - Buffer::new(&mut FONT_SYSTEM.lock().unwrap(), FONT_SIZES[1 /* Body */]), + Buffer::new( + &mut FONT_SYSTEM.lock().unwrap(), + Metrics::new(config.font_size, config.line_height()), + ), &SYNTAX_SYSTEM, - text_box::Appearance::dark().syntax_theme, + config.syntax_theme(cosmic::theme::is_dark()), ) .unwrap(); - let mut editor = ViEditor::new(editor); - editor.set_passthrough(true); - - Self { + let mut tab = Self { path_opt: None, attrs, - editor: Mutex::new(editor), - } + editor: Mutex::new(ViEditor::new(editor)), + }; + + // Update any other config settings + tab.set_config(config); + + tab } pub fn set_config(&mut self, config: &Config) { @@ -51,6 +48,12 @@ impl Tab { } else { Wrap::None }); + //TODO: dynamically discover light/dark changes + editor.update_theme(if cosmic::theme::is_dark() { + &config.syntax_theme_dark + } else { + &config.syntax_theme_light + }); } pub fn open(&mut self, path: PathBuf) { diff --git a/src/text_box.rs b/src/text_box.rs index 0a44453..edfb90a 100644 --- a/src/text_box.rs +++ b/src/text_box.rs @@ -25,7 +25,6 @@ use crate::{FONT_SYSTEM, SWASH_CACHE}; pub struct Appearance { pub background_color: Option, pub text_color: Color, - pub syntax_theme: &'static str, } impl Appearance { @@ -33,7 +32,6 @@ impl Appearance { Self { background_color: Some(Color::from_rgb8(0x34, 0x34, 0x34)), text_color: Color::from_rgb8(0xFF, 0xFF, 0xFF), - syntax_theme: "base16-eighties.dark", } } @@ -41,7 +39,6 @@ impl Appearance { Self { background_color: Some(Color::from_rgb8(0xFC, 0xFC, 0xFC)), text_color: Color::from_rgb8(0x00, 0x00, 0x00), - syntax_theme: "base16-ocean.light", } } } @@ -63,7 +60,6 @@ impl StyleSheet for Theme { pub struct TextBox<'a> { editor: &'a Mutex>, padding: Padding, - line_numbers: bool, } impl<'a> TextBox<'a> { @@ -71,7 +67,6 @@ impl<'a> TextBox<'a> { Self { editor, padding: Padding::new(0.0), - line_numbers: true, } } @@ -267,9 +262,6 @@ where let mut font_system = FONT_SYSTEM.lock().unwrap(); let mut editor = editor.borrow_with(&mut font_system); - // Set theme - editor.update_theme(appearance.syntax_theme); - // Set metrics and size editor.buffer_mut().set_metrics_and_size( //TODO: get from config