From 0c7ae2d1b1b6551a3e1a96507643d1ef8ab9b282 Mon Sep 17 00:00:00 2001 From: candle Date: Sat, 26 Jul 2025 11:50:48 -0400 Subject: [PATCH] better state caching, started building custom syntax theme --- Cargo.toml | 2 +- src/app/state/config.rs | 35 ++++- src/app/state/state_cache.rs | 2 +- src/app/state/ui.rs | 12 -- src/io.rs | 154 +++++++++++++++++++ src/main.rs | 15 +- src/ui.rs | 1 + src/ui/about_window.rs | 9 +- src/ui/central_panel.rs | 5 +- src/ui/central_panel/editor.rs | 2 +- src/ui/constants.rs | 26 ++++ src/ui/find_window.rs | 15 +- src/ui/preferences_window.rs | 272 +++++++++++++++++---------------- src/ui/shortcuts_window.rs | 69 +++++---- 14 files changed, 419 insertions(+), 200 deletions(-) create mode 100644 src/ui/constants.rs diff --git a/Cargo.toml b/Cargo.toml index 7ef5dff..63ab481 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ eframe = "0.32" egui = "0.32" egui_extras = { version = "0.32", features = ["syntect"] } serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0" +serde_json = "1.0.141" rfd = "0.15.4" toml = "0.9.2" dirs = "6.0" diff --git a/src/app/state/config.rs b/src/app/state/config.rs index 1613936..fad9e93 100644 --- a/src/app/state/config.rs +++ b/src/app/state/config.rs @@ -1,6 +1,8 @@ use super::editor::TextEditor; use crate::app::config::Config; use crate::app::theme; +use crate::io; +use std::path::PathBuf; impl TextEditor { pub fn from_config(config: Config) -> Self { @@ -19,13 +21,44 @@ impl TextEditor { } } - pub fn from_config_with_context(config: Config, cc: &eframe::CreationContext<'_>) -> Self { + pub fn from_config_with_context( + config: Config, + cc: &eframe::CreationContext<'_>, + initial_paths: Vec, + ) -> Self { let mut editor = Self::from_config(config); if let Err(e) = editor.load_state_cache() { eprintln!("Failed to load state cache: {e}"); } + if !initial_paths.is_empty() { + let mut opened_any = false; + + for path in initial_paths { + if path.is_file() { + match io::open_file_from_path(&mut editor, path.clone()) { + Ok(()) => opened_any = true, + Err(e) => eprintln!("Error opening file {}: {}", path.display(), e), + } + } else if path.is_dir() { + match io::open_files_from_directory(&mut editor, path.clone()) { + Ok(count) => { + opened_any = true; + println!("Opened {} files from directory {}", count, path.display()); + } + Err(e) => eprintln!("Error opening directory {}: {}", path.display(), e), + } + } else { + eprintln!("Warning: Path does not exist: {}", path.display()); + } + } + + if opened_any { + editor.active_tab_index = editor.tabs.len().saturating_sub(1); + } + } + theme::apply(editor.theme, &cc.egui_ctx); cc.egui_ctx.options_mut(|o| o.zoom_with_keyboard = false); diff --git a/src/app/state/state_cache.rs b/src/app/state/state_cache.rs index 5912fa1..4cda33d 100644 --- a/src/app/state/state_cache.rs +++ b/src/app/state/state_cache.rs @@ -6,7 +6,7 @@ use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CachedTab { - pub diff_file: Option, // Path to diff file for modified tabs + pub diff_file: Option, pub full_content: Option, // This is used for 'new files' that don't have a path pub file_path: Option, pub is_modified: bool, diff --git a/src/app/state/ui.rs b/src/app/state/ui.rs index 0571615..c4f9a6b 100644 --- a/src/app/state/ui.rs +++ b/src/app/state/ui.rs @@ -53,18 +53,6 @@ impl TextEditor { self.save_config(); } - pub fn apply_font_settings_with_ui(&mut self, ctx: &egui::Context, ui: &egui::Ui) { - self.apply_font_settings(ctx); - self.reprocess_text_for_font_change(ui); - self.font_settings_changed = false; - } - - pub fn reprocess_text_for_font_change(&mut self, ui: &egui::Ui) { - if let Some(active_tab) = self.get_active_tab() { - self.process_text_for_rendering(&active_tab.content.to_string(), ui); - } - } - pub fn calculate_editor_dimensions(&self, ui: &egui::Ui) -> EditorDimensions { let total_available_width = ui.available_width(); diff --git a/src/io.rs b/src/io.rs index 7ee8e1c..ac0dd74 100644 --- a/src/io.rs +++ b/src/io.rs @@ -7,6 +7,116 @@ pub(crate) fn new_file(app: &mut TextEditor) { app.add_new_tab(); } +fn is_text_file(path: &PathBuf) -> bool { + if let Some(extension) = path.extension().and_then(|s| s.to_str()) { + matches!( + extension.to_lowercase().as_str(), + "txt" + | "md" + | "markdown" + | "rs" + | "py" + | "js" + | "ts" + | "tsx" + | "jsx" + | "c" + | "cpp" + | "cc" + | "cxx" + | "h" + | "hpp" + | "java" + | "go" + | "php" + | "rb" + | "cs" + | "swift" + | "kt" + | "scala" + | "sh" + | "bash" + | "zsh" + | "fish" + | "html" + | "htm" + | "xml" + | "css" + | "scss" + | "sass" + | "json" + | "yaml" + | "yml" + | "toml" + | "sql" + | "lua" + | "vim" + | "dockerfile" + | "makefile" + | "gitignore" + | "conf" + | "cfg" + | "ini" + | "log" + | "csv" + | "tsv" + ) + } else { + // Files without extensions might be text files, but let's be conservative + // and only include them if they're small and readable + if let Ok(metadata) = fs::metadata(path) { + metadata.len() < 1024 * 1024 // Only consider files smaller than 1MB + } else { + false + } + } +} + +pub(crate) fn open_files_from_directory( + app: &mut TextEditor, + dir_path: PathBuf, +) -> Result { + if !dir_path.is_dir() { + return Err(format!("{} is not a directory", dir_path.display())); + } + + let entries = fs::read_dir(&dir_path) + .map_err(|e| format!("Failed to read directory {}: {}", dir_path.display(), e))?; + + let mut opened_count = 0; + let mut text_files: Vec = Vec::new(); + + // Collect all text files in the directory + for entry in entries { + let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?; + let path = entry.path(); + + if path.is_file() && is_text_file(&path) { + text_files.push(path); + } + } + + // Sort files by name for consistent ordering + text_files.sort(); + + // Open each text file + for file_path in text_files { + match open_file_from_path(app, file_path.clone()) { + Ok(()) => opened_count += 1, + Err(e) => eprintln!("Warning: {}", e), + } + } + + if opened_count == 0 { + Err(format!( + "No text files found in directory {}", + dir_path.display() + )) + } else { + Ok(opened_count) + } +} + pub(crate) fn open_file(app: &mut TextEditor) { if let Some(path) = rfd::FileDialog::new() .add_filter("Text files", &["*"]) @@ -55,6 +165,50 @@ pub(crate) fn open_file(app: &mut TextEditor) { } } +pub(crate) fn open_file_from_path(app: &mut TextEditor, path: PathBuf) -> Result<(), String> { + match fs::read_to_string(&path) { + Ok(content) => { + let should_replace_current_tab = if let Some(active_tab) = app.get_active_tab() { + active_tab.file_path.is_none() + && active_tab.content.is_empty() + && !active_tab.is_modified + } else { + false + }; + + if should_replace_current_tab { + if let Some(active_tab) = app.get_active_tab_mut() { + let title = path + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("Untitled"); + active_tab.content = content; + active_tab.file_path = Some(path.to_path_buf()); + active_tab.title = title.to_string(); + active_tab.mark_as_saved(); + } + app.text_needs_processing = true; + } else { + let new_tab = Tab::new_with_file(content, path); + app.tabs.push(new_tab); + app.active_tab_index = app.tabs.len() - 1; + app.text_needs_processing = true; + } + + if app.show_find && !app.find_query.is_empty() { + app.update_find_matches(); + } + + if let Err(e) = app.save_state_cache() { + eprintln!("Failed to save state cache: {e}"); + } + + Ok(()) + } + Err(err) => Err(format!("Failed to open file {}: {}", path.display(), err)), + } +} + pub(crate) fn save_file(app: &mut TextEditor) { if let Some(active_tab) = app.get_active_tab() { if let Some(path) = &active_tab.file_path { diff --git a/src/main.rs b/src/main.rs index 46402b3..de06732 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use eframe::egui; use std::env; use std::io::IsTerminal; +use std::path::PathBuf; mod app; mod io; @@ -10,10 +11,12 @@ mod ui; use app::{config::Config, TextEditor}; fn main() -> eframe::Result { - let _args: Vec = env::args().collect(); + let args: Vec = env::args().collect(); + + let initial_paths: Vec = args.iter().skip(1).map(|arg| PathBuf::from(arg)).collect(); + if std::io::stdin().is_terminal() { println!("This is a GUI application, are you sure you want to launch from terminal?"); - // return Ok(()); } let options = eframe::NativeOptions { @@ -29,6 +32,12 @@ fn main() -> eframe::Result { eframe::run_native( "ced", options, - Box::new(move |cc| Ok(Box::new(TextEditor::from_config_with_context(config, cc)))), + Box::new(move |cc| { + Ok(Box::new(TextEditor::from_config_with_context( + config, + cc, + initial_paths, + ))) + }), ) } diff --git a/src/ui.rs b/src/ui.rs index 35cb22e..dd29176 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,5 +1,6 @@ pub(crate) mod about_window; pub(crate) mod central_panel; +pub(crate) mod constants; pub(crate) mod find_window; pub(crate) mod menu_bar; pub(crate) mod preferences_window; diff --git a/src/ui/about_window.rs b/src/ui/about_window.rs index 18f2008..5c4f40a 100644 --- a/src/ui/about_window.rs +++ b/src/ui/about_window.rs @@ -1,4 +1,5 @@ use crate::app::TextEditor; +use crate::ui::constants::*; use eframe::egui; pub(crate) fn about_window(app: &mut TextEditor, ctx: &egui::Context) { @@ -16,20 +17,20 @@ pub(crate) fn about_window(app: &mut TextEditor, ctx: &egui::Context) { .frame(egui::Frame { fill: visuals.window_fill, stroke: visuals.window_stroke, - corner_radius: egui::CornerRadius::same(8), + corner_radius: egui::CornerRadius::same(CORNER_RADIUS), shadow: visuals.window_shadow, - inner_margin: egui::Margin::same(16), + inner_margin: egui::Margin::same(INNER_MARGIN), outer_margin: egui::Margin::same(0), }) .show(ctx, |ui| { ui.vertical_centered(|ui| { ui.label( egui::RichText::new("A stupidly simple, responsive text editor.") - .size(14.0) + .size(UI_TEXT_SIZE) .weak(), ); - ui.add_space(12.0); + ui.add_space(LARGE); let visuals = ui.visuals(); let close_button = egui::Button::new("Close") .fill(visuals.widgets.inactive.bg_fill) diff --git a/src/ui/central_panel.rs b/src/ui/central_panel.rs index efb1e0a..187874c 100644 --- a/src/ui/central_panel.rs +++ b/src/ui/central_panel.rs @@ -4,6 +4,7 @@ mod languages; mod line_numbers; use crate::app::TextEditor; +use crate::ui::constants::*; use eframe::egui; use egui::UiKind; @@ -74,13 +75,13 @@ pub(crate) fn central_panel(app: &mut TextEditor, ctx: &egui::Context) { }; let separator_widget = |ui: &mut egui::Ui| { - ui.add_space(3.0); + ui.add_space(SMALL); let separator_x = ui.cursor().left(); let mut y_range = ui.available_rect_before_wrap().y_range(); y_range.max += 2.0 * font_size; ui.painter() .vline(separator_x, y_range, ui.visuals().window_stroke); - ui.add_space(4.0); + ui.add_space(SMALL); }; egui::ScrollArea::vertical() diff --git a/src/ui/central_panel/editor.rs b/src/ui/central_panel/editor.rs index 1c0311e..26784ed 100644 --- a/src/ui/central_panel/editor.rs +++ b/src/ui/central_panel/editor.rs @@ -103,8 +103,8 @@ pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::R let mut layouter = |ui: &egui::Ui, string: &dyn egui::TextBuffer, wrap_width: f32| { // let syntect_theme = // crate::app::theme::create_code_theme_from_visuals(ui.visuals(), font_size); + let theme = egui_extras::syntax_highlighting::CodeTheme::from_style(ui.style()); let text = string.as_str(); - let theme = egui_extras::syntax_highlighting::CodeTheme::dark(font_size); let mut layout_job = if syntax_highlighting_enabled && language != "txt" { // let mut settings = egui_extras::syntax_highlighting::SyntectSettings::default(); // settings.ts = syntect_theme; diff --git a/src/ui/constants.rs b/src/ui/constants.rs new file mode 100644 index 0000000..c8dd319 --- /dev/null +++ b/src/ui/constants.rs @@ -0,0 +1,26 @@ +pub const SMALL: f32 = 4.0; +pub const MEDIUM: f32 = 8.0; +pub const LARGE: f32 = 12.0; +pub const VLARGE: f32 = 16.0; + +pub const UI_HEADER_SIZE: f32 = 18.0; +pub const UI_TEXT_SIZE: f32 = 14.0; + +pub const MIN_FONT_SIZE: f32 = 8.0; +pub const MAX_FONT_SIZE: f32 = 32.0; + +pub const WINDOW_WIDTH_RATIO: f32 = 0.6; +pub const WINDOW_HEIGHT_RATIO: f32 = 0.7; +pub const WINDOW_MIN_WIDTH: f32 = 300.0; +pub const WINDOW_MAX_WIDTH: f32 = 400.0; +pub const WINDOW_MIN_HEIGHT: f32 = 250.0; +pub const WINDOW_MAX_HEIGHT: f32 = 500.0; + +pub const CORNER_RADIUS: u8 = 8; + +pub const FONT_SIZE_INPUT_WIDTH: f32 = 24.0; +pub const DEFAULT_FONT_SIZE_STR: &str = "14"; + +pub const PREVIEW_AREA_MAX_HEIGHT: f32 = 150.0; + +pub const INNER_MARGIN: i8 = 8; \ No newline at end of file diff --git a/src/ui/find_window.rs b/src/ui/find_window.rs index 7fed489..446e5cf 100644 --- a/src/ui/find_window.rs +++ b/src/ui/find_window.rs @@ -1,4 +1,5 @@ use crate::app::TextEditor; +use crate::ui::constants::*; use eframe::egui; pub(crate) fn find_window(app: &mut TextEditor, ctx: &egui::Context) { @@ -37,9 +38,9 @@ pub(crate) fn find_window(app: &mut TextEditor, ctx: &egui::Context) { .frame(egui::Frame { fill: visuals.window_fill, stroke: visuals.window_stroke, - corner_radius: egui::CornerRadius::same(8), + corner_radius: egui::CornerRadius::same(CORNER_RADIUS), shadow: visuals.window_shadow, - inner_margin: egui::Margin::same(16), + inner_margin: egui::Margin::same(INNER_MARGIN), outer_margin: egui::Margin::same(0), }) .show(ctx, |ui| { @@ -78,7 +79,7 @@ pub(crate) fn find_window(app: &mut TextEditor, ctx: &egui::Context) { if app.show_replace_section { ui.horizontal(|ui| { - ui.add_space(4.0); + ui.add_space(SMALL); ui.label("Replace:"); let _replace_response = ui.add( egui::TextEdit::singleline(&mut app.replace_query) @@ -88,7 +89,7 @@ pub(crate) fn find_window(app: &mut TextEditor, ctx: &egui::Context) { }); } - ui.add_space(8.0); + ui.add_space(MEDIUM); ui.horizontal(|ui| { let case_sensitive_changed = ui @@ -98,7 +99,7 @@ pub(crate) fn find_window(app: &mut TextEditor, ctx: &egui::Context) { query_changed = true; } if app.show_replace_section { - ui.add_space(8.0); + ui.add_space(MEDIUM); let replace_current_enabled = !app.find_matches.is_empty() && app.current_match_index.is_some(); @@ -117,7 +118,7 @@ pub(crate) fn find_window(app: &mut TextEditor, ctx: &egui::Context) { } }); - ui.add_space(8.0); + ui.add_space(MEDIUM); ui.horizontal(|ui| { let match_text = if app.find_matches.is_empty() { @@ -139,7 +140,7 @@ pub(crate) fn find_window(app: &mut TextEditor, ctx: &egui::Context) { should_close = true; } - ui.add_space(4.0); + ui.add_space(SMALL); let next_enabled = !app.find_matches.is_empty(); ui.add_enabled_ui(next_enabled, |ui| { diff --git a/src/ui/preferences_window.rs b/src/ui/preferences_window.rs index 54af9ce..b2f94d6 100644 --- a/src/ui/preferences_window.rs +++ b/src/ui/preferences_window.rs @@ -1,11 +1,14 @@ use crate::app::TextEditor; +use crate::ui::constants::*; use eframe::egui; pub(crate) fn preferences_window(app: &mut TextEditor, ctx: &egui::Context) { let visuals = &ctx.style().visuals; let screen_rect = ctx.screen_rect(); - let window_width = (screen_rect.width() * 0.6).clamp(300.0, 400.0); - let window_height = (screen_rect.height() * 0.7).clamp(250.0, 500.0); + let window_width = (screen_rect.width() * WINDOW_WIDTH_RATIO) + .clamp(WINDOW_MIN_WIDTH, WINDOW_MAX_WIDTH); + let window_height = (screen_rect.height() * WINDOW_HEIGHT_RATIO) + .clamp(WINDOW_MIN_HEIGHT, WINDOW_MAX_HEIGHT); let max_size = egui::Vec2::new(window_width, window_height); egui::Window::new("Preferences") @@ -19,169 +22,168 @@ pub(crate) fn preferences_window(app: &mut TextEditor, ctx: &egui::Context) { .frame(egui::Frame { fill: visuals.window_fill, stroke: visuals.window_stroke, - corner_radius: egui::CornerRadius::same(8), + corner_radius: egui::CornerRadius::same(CORNER_RADIUS), shadow: visuals.window_shadow, - inner_margin: egui::Margin::same(16), + inner_margin: egui::Margin::same(INNER_MARGIN), outer_margin: egui::Margin::same(0), }) .show(ctx, |ui| { ui.vertical_centered(|ui| { - ui.heading("General Settings"); - ui.add_space(8.0); + ui.heading("Editor Settings"); + ui.add_space(MEDIUM); ui.horizontal(|ui| { - if ui - .checkbox(&mut app.state_cache, "Cache State") - .on_hover_text("Unsaved changes will be cached between sessions") - .changed() - { - app.save_config(); - if !app.state_cache { - if let Err(e) = TextEditor::clear_state_cache() { - eprintln!("Failed to clear state cache: {e}"); + ui.vertical(|ui| { + if ui + .checkbox(&mut app.state_cache, "Maintain State") + .on_hover_text("Unsaved changes will be cached between sessions") + .changed() + { + app.save_config(); + if !app.state_cache { + if let Err(e) = TextEditor::clear_state_cache() { + eprintln!("Failed to clear state cache: {e}"); + } } } - } + ui.add_space(SMALL); + if ui + .checkbox(&mut app.show_line_numbers, "Show Line Numbers") + .changed() + { + app.save_config(); + } + ui.add_space(SMALL); + if ui + .checkbox(&mut app.auto_hide_toolbar, "Auto Hide Toolbar") + .on_hover_text( + "Hide the top bar until you move your mouse to the upper edge", + ) + .changed() + { + app.save_config(); + } + }); + ui.vertical(|ui| { + if ui.checkbox(&mut app.word_wrap, "Word Wrap").changed() { + app.save_config(); + } + ui.add_space(SMALL); + if ui + .checkbox(&mut app.syntax_highlighting, "Syntax Highlighting") + .changed() + { + app.save_config(); + } + ui.add_space(SMALL); + if ui + .checkbox(&mut app.hide_tab_bar, "Hide Tab Bar") + .on_hover_text( + "Hide the tab bar and show tab title in menu bar instead", + ) + .changed() + { + app.save_config(); + } + }); }); - ui.add_space(4.0); - - ui.horizontal(|ui| { - if ui - .checkbox(&mut app.show_line_numbers, "Show Line Numbers") - .changed() - { - app.save_config(); - } - if ui - .checkbox(&mut app.syntax_highlighting, "Syntax Highlighting") - .changed() - { - app.save_config(); - } - }); - - ui.add_space(4.0); - - ui.horizontal(|ui| { - if ui.checkbox(&mut app.word_wrap, "Word Wrap").changed() { - app.save_config(); - } - }); - - ui.add_space(4.0); - - ui.horizontal(|ui| { - if ui - .checkbox(&mut app.auto_hide_toolbar, "Auto Hide Toolbar") - .on_hover_text("Hide the menu bar until you move your mouse to the top") - .changed() - { - app.save_config(); - } - if ui - .checkbox(&mut app.hide_tab_bar, "Hide Tab Bar") - .on_hover_text("Hide the tab bar and show tab title in menu bar instead") - .changed() - { - app.save_config(); - } - }); - - ui.add_space(12.0); + ui.add_space(SMALL); ui.separator(); + ui.add_space(LARGE); ui.heading("Font Settings"); - ui.add_space(8.0); + ui.add_space(MEDIUM); ui.horizontal(|ui| { - ui.label("Font Family:"); - ui.add_space(5.0); + ui.vertical(|ui| { + ui.label("Font Family:"); + ui.add_space(SMALL); + ui.label("Font Size:"); + }); - let mut changed = false; - egui::ComboBox::from_id_salt("font_family") - .selected_text(&app.font_family) - .show_ui(ui, |ui| { - if ui - .selectable_value( - &mut app.font_family, - "Proportional".to_string(), - "Proportional", - ) - .clicked() - { - changed = true; + ui.vertical(|ui| { + let mut changed = false; + egui::ComboBox::from_id_salt("font_family") + .selected_text(&app.font_family) + .show_ui(ui, |ui| { + if ui + .selectable_value( + &mut app.font_family, + "Proportional".to_string(), + "Proportional", + ) + .clicked() + { + changed = true; + } + if ui + .selectable_value( + &mut app.font_family, + "Monospace".to_string(), + "Monospace", + ) + .clicked() + { + changed = true; + } + }); + + if app.font_size_input.is_none() { + app.font_size_input = Some(app.font_size.to_string()); + } + + let mut font_size_text = app + .font_size_input + .as_ref() + .unwrap_or(&DEFAULT_FONT_SIZE_STR.to_string()) + .to_owned(); + ui.add_space(SMALL); + ui.horizontal(|ui| { + let response = ui.add( + egui::TextEdit::singleline(&mut font_size_text) + .desired_width(FONT_SIZE_INPUT_WIDTH) + .hint_text(DEFAULT_FONT_SIZE_STR) + .id(egui::Id::new("font_size_input")), + ); + + app.font_size_input = Some(font_size_text.to_owned()); + + if response.clicked() { + response.request_focus(); } - if ui - .selectable_value( - &mut app.font_family, - "Monospace".to_string(), - "Monospace", - ) - .clicked() - { - changed = true; + + ui.label("px"); + + if response.lost_focus() { + if let Ok(new_size) = font_size_text.parse::() { + let clamped_size = new_size.clamp(MIN_FONT_SIZE, MAX_FONT_SIZE); + if (app.font_size - clamped_size).abs() > 0.1 { + app.font_size = clamped_size; + app.apply_font_settings(ctx); + } + } + app.font_size_input = None; } - }); - if changed { - app.apply_font_settings(ctx); - } - }); - - ui.add_space(8.0); - - ui.horizontal(|ui| { - ui.label("Font Size:"); - ui.add_space(5.0); - - if app.font_size_input.is_none() { - app.font_size_input = Some(app.font_size.to_string()); - } - - let mut font_size_text = app - .font_size_input - .as_ref() - .unwrap_or(&"14".to_string()) - .to_owned(); - let response = ui.add( - egui::TextEdit::singleline(&mut font_size_text) - .desired_width(50.0) - .hint_text("14") - .id(egui::Id::new("font_size_input")), - ); - - app.font_size_input = Some(font_size_text.to_owned()); - - if response.clicked() { - response.request_focus(); - } - - ui.label("px"); - - if response.lost_focus() { - if let Ok(new_size) = font_size_text.parse::() { - let clamped_size = new_size.clamp(8.0, 32.0); - if (app.font_size - clamped_size).abs() > 0.1 { - app.font_size = clamped_size; + if changed { app.apply_font_settings(ctx); } - } - app.font_size_input = None; - } + }) + }); }); - ui.add_space(8.0); + ui.add_space(MEDIUM); ui.label("Preview:"); - ui.add_space(4.0); + ui.add_space(SMALL); egui::ScrollArea::vertical() - .max_height(150.0) + .max_height(PREVIEW_AREA_MAX_HEIGHT) .show(ui, |ui| { egui::Frame::new() .fill(visuals.code_bg_color) .stroke(visuals.widgets.noninteractive.bg_stroke) - .inner_margin(egui::Margin::same(8)) + .inner_margin(egui::Margin::same(INNER_MARGIN)) .show(ui, |ui| { let preview_font = egui::FontId::new( app.font_size, @@ -211,7 +213,7 @@ pub(crate) fn preferences_window(app: &mut TextEditor, ctx: &egui::Context) { }); }); - ui.add_space(12.0); + ui.add_space(LARGE); if ui.button("Close").clicked() { app.show_preferences = false; diff --git a/src/ui/shortcuts_window.rs b/src/ui/shortcuts_window.rs index a3ca874..d4ee8b4 100644 --- a/src/ui/shortcuts_window.rs +++ b/src/ui/shortcuts_window.rs @@ -1,39 +1,42 @@ use crate::app::TextEditor; +use crate::ui::constants::*; use eframe::egui; fn render_shortcuts_content(ui: &mut egui::Ui) { ui.vertical_centered(|ui| { - ui.label(egui::RichText::new("Navigation").size(18.0).strong()); - ui.label(egui::RichText::new("Ctrl + N: New").size(14.0)); - ui.label(egui::RichText::new("Ctrl + O: Open").size(14.0)); - ui.label(egui::RichText::new("Ctrl + S: Save").size(14.0)); - ui.label(egui::RichText::new("Ctrl + Shift + S: Save As").size(14.0)); - ui.label(egui::RichText::new("Ctrl + T: New Tab").size(14.0)); - ui.label(egui::RichText::new("Ctrl + Tab: Next Tab").size(14.0)); - ui.label(egui::RichText::new("Ctrl + Shift + Tab: Last Tab").size(14.0)); - ui.add_space(16.0); + ui.label(egui::RichText::new("Navigation").size(UI_HEADER_SIZE).strong()); + ui.label(egui::RichText::new("Ctrl + N: New").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + O: Open").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + S: Save").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + Shift + S: Save As").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + T: New Tab").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + W: Close Tab").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + Tab: Next Tab").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + Shift + Tab: Last Tab").size(UI_TEXT_SIZE)); + ui.add_space(VLARGE); ui.separator(); - ui.label(egui::RichText::new("Editing").size(18.0).strong()); - ui.label(egui::RichText::new("Ctrl + Z: Undo").size(14.0)); - ui.label(egui::RichText::new("Ctrl + Shift + Z: Redo").size(14.0)); - ui.label(egui::RichText::new("Ctrl + X: Cut").size(14.0)); - ui.label(egui::RichText::new("Ctrl + C: Copy").size(14.0)); - ui.label(egui::RichText::new("Ctrl + V: Paste").size(14.0)); - ui.label(egui::RichText::new("Ctrl + A: Select All").size(14.0)); - ui.label(egui::RichText::new("Ctrl + D: Delete Line").size(14.0)); - ui.label(egui::RichText::new("Ctrl + F: Find").size(14.0)); + ui.label(egui::RichText::new("Editing").size(UI_HEADER_SIZE).strong()); + ui.label(egui::RichText::new("Ctrl + Z: Undo").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + Shift + Z: Redo").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + X: Cut").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + C: Copy").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + V: Paste").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + A: Select All").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + D: Delete Line").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + F: Find").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + R: Replace").size(UI_TEXT_SIZE)); - ui.add_space(16.0); + ui.add_space(VLARGE); ui.separator(); - ui.label(egui::RichText::new("Views").size(18.0).strong()); - ui.label(egui::RichText::new("Ctrl + L: Toggle Line Numbers").size(14.0)); - ui.label(egui::RichText::new("Ctrl + Shift + L: Change Line Number Side").size(14.0)); - ui.label(egui::RichText::new("Ctrl + K: Toggle Word Wrap").size(14.0)); - ui.label(egui::RichText::new("Ctrl + H: Toggle Auto Hide Toolbar").size(14.0)); - ui.label(egui::RichText::new("Ctrl + P: Preferences").size(14.0)); - ui.label(egui::RichText::new("Ctrl + =/-: Increase/Decrease Font Size").size(14.0)); - ui.label(egui::RichText::new("Ctrl + Shift + =/-: Zoom In/Out").size(14.0)); + ui.label(egui::RichText::new("Views").size(UI_HEADER_SIZE).strong()); + ui.label(egui::RichText::new("Ctrl + L: Toggle Line Numbers").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + Shift + L: Change Line Number Side").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + K: Toggle Word Wrap").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + H: Toggle Auto Hide Toolbar").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + P: Preferences").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + =/-: Increase/Decrease Font Size").size(UI_TEXT_SIZE)); + ui.label(egui::RichText::new("Ctrl + Shift + =/-: Zoom In/Out").size(UI_TEXT_SIZE)); // ui.label( // egui::RichText::new("Ctrl + Shift + .: Toggle Vim Mode") // .size(14.0) @@ -42,7 +45,7 @@ fn render_shortcuts_content(ui: &mut egui::Ui) { // egui::RichText::new("Ctrl + .: Toggle Vim Mode") // .size(14.0) // ); - ui.add_space(16.0); + ui.add_space(VLARGE); ui.separator(); }); } @@ -51,8 +54,8 @@ pub(crate) fn shortcuts_window(app: &mut TextEditor, ctx: &egui::Context) { let visuals = &ctx.style().visuals; let screen_rect = ctx.screen_rect(); - let window_width = (screen_rect.width() * 0.6).clamp(300.0, 400.0); - let window_height = (screen_rect.height() * 0.7).clamp(250.0, 500.0); + let window_width = (screen_rect.width() * WINDOW_WIDTH_RATIO).clamp(WINDOW_MIN_WIDTH, WINDOW_MAX_WIDTH); + let window_height = (screen_rect.height() * WINDOW_HEIGHT_RATIO).clamp(WINDOW_MIN_HEIGHT, WINDOW_MAX_HEIGHT); egui::Window::new("Shortcuts") .collapsible(false) @@ -64,9 +67,9 @@ pub(crate) fn shortcuts_window(app: &mut TextEditor, ctx: &egui::Context) { .frame(egui::Frame { fill: visuals.window_fill, stroke: visuals.window_stroke, - corner_radius: egui::CornerRadius::same(8), + corner_radius: egui::CornerRadius::same(CORNER_RADIUS), shadow: visuals.window_shadow, - inner_margin: egui::Margin::same(16), + inner_margin: egui::Margin::same(INNER_MARGIN), outer_margin: egui::Margin::same(0), }) .show(ctx, |ui| { @@ -85,7 +88,7 @@ pub(crate) fn shortcuts_window(app: &mut TextEditor, ctx: &egui::Context) { ); ui.vertical_centered(|ui| { - ui.add_space(8.0); + ui.add_space(MEDIUM); let visuals = ui.visuals(); let close_button = egui::Button::new("Close") .fill(visuals.widgets.inactive.bg_fill)