From 67b9a9fecddffb578238bc06eb0e15ccce34601c Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 17 Apr 2025 21:02:33 +0200 Subject: [PATCH] Implement menubar checkboxes --- src/buffer.rs | 10 ++++++---- src/main.rs | 33 ++++++++++++++++++--------------- src/tui.rs | 42 +++++++++++++++++++++++++++++++++++------- 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index d69fe07..60e4eee 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -354,10 +354,12 @@ impl TextBuffer { // NOTE: It's expected that the tui code calls `set_width()` sometime after this. // This will then trigger the actual recalculation of the cursor position. - pub fn toggle_word_wrap(&mut self) { - self.word_wrap_enabled = !self.word_wrap_enabled; - self.width = 0; // Force a reflow. - self.make_cursor_visible(); + pub fn set_word_wrap(&mut self, enabled: bool) { + if self.word_wrap_enabled != enabled { + self.word_wrap_enabled = enabled; + self.width = 0; // Force a reflow. + self.make_cursor_visible(); + } } pub fn set_width(&mut self, width: CoordType) -> bool { diff --git a/src/main.rs b/src/main.rs index 417fe4e..9e66628 100644 --- a/src/main.rs +++ b/src/main.rs @@ -528,46 +528,46 @@ fn draw_menubar(ctx: &mut Context, state: &mut State) { } fn draw_menu_file(ctx: &mut Context, state: &mut State) { - if ctx.menubar_menu_item(loc(LocId::FileOpen), 'O', kbmod::CTRL | vk::O) { + if ctx.menubar_menu_button(loc(LocId::FileOpen), 'O', kbmod::CTRL | vk::O) { state.wants_file_picker = StateFilePicker::Open; } - if ctx.menubar_menu_item(loc(LocId::FileSave), 'S', kbmod::CTRL | vk::S) { + if ctx.menubar_menu_button(loc(LocId::FileSave), 'S', kbmod::CTRL | vk::S) { state.wants_file_picker = StateFilePicker::Save; } - if ctx.menubar_menu_item(loc(LocId::FileSaveAs), 'A', vk::NULL) { + if ctx.menubar_menu_button(loc(LocId::FileSaveAs), 'A', vk::NULL) { state.wants_file_picker = StateFilePicker::SaveAs; } - if ctx.menubar_menu_item(loc(LocId::FileExit), 'X', kbmod::CTRL | vk::Q) { + if ctx.menubar_menu_button(loc(LocId::FileExit), 'X', kbmod::CTRL | vk::Q) { state.wants_exit = true; } ctx.menubar_menu_end(); } fn draw_menu_edit(ctx: &mut Context, state: &mut State) { - if ctx.menubar_menu_item(loc(LocId::EditUndo), 'U', kbmod::CTRL | vk::Z) { + if ctx.menubar_menu_button(loc(LocId::EditUndo), 'U', kbmod::CTRL | vk::Z) { state.buffer.borrow_mut().undo(); ctx.needs_rerender(); } - if ctx.menubar_menu_item(loc(LocId::EditRedo), 'R', kbmod::CTRL | vk::Y) { + if ctx.menubar_menu_button(loc(LocId::EditRedo), 'R', kbmod::CTRL | vk::Y) { state.buffer.borrow_mut().redo(); ctx.needs_rerender(); } - if ctx.menubar_menu_item(loc(LocId::EditCut), 'T', kbmod::CTRL | vk::X) { + if ctx.menubar_menu_button(loc(LocId::EditCut), 'T', kbmod::CTRL | vk::X) { ctx.set_clipboard(state.buffer.borrow_mut().extract_selection(true)); } - if ctx.menubar_menu_item(loc(LocId::EditCopy), 'C', kbmod::CTRL | vk::C) { + if ctx.menubar_menu_button(loc(LocId::EditCopy), 'C', kbmod::CTRL | vk::C) { ctx.set_clipboard(state.buffer.borrow_mut().extract_selection(false)); } - if ctx.menubar_menu_item(loc(LocId::EditPaste), 'P', kbmod::CTRL | vk::V) { + if ctx.menubar_menu_button(loc(LocId::EditPaste), 'P', kbmod::CTRL | vk::V) { state.buffer.borrow_mut().write(ctx.get_clipboard(), true); ctx.needs_rerender(); } if state.wants_search.kind != StateSearchKind::Disabled { - if ctx.menubar_menu_item(loc(LocId::EditFind), 'F', kbmod::CTRL | vk::F) { + if ctx.menubar_menu_button(loc(LocId::EditFind), 'F', kbmod::CTRL | vk::F) { state.wants_search.kind = StateSearchKind::Search; state.wants_search.focus = true; } - if ctx.menubar_menu_item(loc(LocId::EditReplace), 'R', kbmod::CTRL | vk::R) { + if ctx.menubar_menu_button(loc(LocId::EditReplace), 'R', kbmod::CTRL | vk::R) { state.wants_search.kind = StateSearchKind::Replace; state.wants_search.focus = true; } @@ -576,18 +576,21 @@ fn draw_menu_edit(ctx: &mut Context, state: &mut State) { } fn draw_menu_view(ctx: &mut Context, state: &mut State) { - if ctx.menubar_menu_item(loc(LocId::ViewFocusStatusbar), 'S', vk::NULL) { + let mut tb = state.buffer.borrow_mut(); + let word_wrap = tb.is_word_wrap_enabled(); + + if ctx.menubar_menu_button(loc(LocId::ViewFocusStatusbar), 'S', vk::NULL) { state.wants_statusbar_focus = true; } - if ctx.menubar_menu_item(loc(LocId::ViewWordWrap), 'W', kbmod::ALT | vk::Z) { - state.buffer.borrow_mut().toggle_word_wrap(); + if ctx.menubar_menu_checkbox(loc(LocId::ViewWordWrap), 'W', kbmod::ALT | vk::Z, word_wrap) { + tb.set_word_wrap(!word_wrap); ctx.needs_rerender(); } ctx.menubar_menu_end(); } fn draw_menu_help(ctx: &mut Context, state: &mut State) { - if ctx.menubar_menu_item(loc(LocId::HelpAbout), 'A', vk::NULL) { + if ctx.menubar_menu_button(loc(LocId::HelpAbout), 'A', vk::NULL) { state.wants_about = true; } ctx.menubar_menu_end(); diff --git a/src/tui.rs b/src/tui.rs index a2b8fd2..010b63d 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -2140,7 +2140,7 @@ impl<'a> Context<'a, '_> { vk::Z => match modifiers { kbmod::CTRL => tb.undo(), kbmod::CTRL_SHIFT => tb.redo(), - kbmod::ALT => tb.toggle_word_wrap(), + kbmod::ALT => tb.set_word_wrap(tb.is_word_wrap_enabled()), _ => return false, }, _ => return false, @@ -2458,7 +2458,7 @@ impl<'a> Context<'a, '_> { pub fn menubar_menu_begin(&mut self, text: &str, accelerator: char) -> bool { self.next_block_id_mixin(self.tree.current_node.borrow().child_count as u64); - self.menubar_label(text, accelerator); + self.menubar_label(text, accelerator, None); self.attr_focusable(); self.attr_padding(Rect::two(0, 1)); @@ -2485,7 +2485,22 @@ impl<'a> Context<'a, '_> { false } - pub fn menubar_menu_item(&mut self, text: &str, accelerator: char, shortcut: InputKey) -> bool { + pub fn menubar_menu_button( + &mut self, + text: &str, + accelerator: char, + shortcut: InputKey, + ) -> bool { + self.menubar_menu_checkbox(text, accelerator, shortcut, false) + } + + pub fn menubar_menu_checkbox( + &mut self, + text: &str, + accelerator: char, + shortcut: InputKey, + checked: bool, + ) -> bool { self.table_next_row(); self.attr_focusable(); if self.is_focused() { @@ -2496,7 +2511,7 @@ impl<'a> Context<'a, '_> { let clicked = self.button_activated() || self.consume_shortcut(InputKey::new(accelerator as u32)); - self.menubar_label(text, accelerator); + self.menubar_label(text, accelerator, Some(checked)); self.menubar_shortcut(shortcut); if clicked { @@ -2573,7 +2588,7 @@ impl<'a> Context<'a, '_> { self.set_input_consumed(); } - fn menubar_label(&mut self, text: &str, accelerator: char) { + fn menubar_label(&mut self, text: &str, accelerator: char, checked: Option) { if !accelerator.is_ascii_uppercase() { self.label("label", Overflow::Clip, text); return; @@ -2594,6 +2609,9 @@ impl<'a> Context<'a, '_> { } self.styled_label_begin("label", Overflow::Clip); + if let Some(checked) = checked { + self.styled_label_add_text(if checked { "▣ " } else { " " }); + } if off < text.len() { // Highlight the accelerator in red. @@ -2614,7 +2632,12 @@ impl<'a> Context<'a, '_> { } self.styled_label_end(); - self.attr_padding(Rect::two(0, 1)); + self.attr_padding(Rect { + left: 0, + top: 0, + right: 2, + bottom: 0, + }); } fn menubar_shortcut(&mut self, shortcut: InputKey) { @@ -2633,11 +2656,16 @@ impl<'a> Context<'a, '_> { shortcut_text.push(shortcut_letter); self.label("shortcut", Overflow::Clip, &shortcut_text); - self.attr_padding(Rect::two(0, 1)); } else { self.block_begin("shortcut"); self.block_end(); } + self.attr_padding(Rect { + left: 0, + top: 0, + right: 2, + bottom: 0, + }); } fn arena(&self) -> &'a Arena {