diff --git a/Cargo.lock b/Cargo.lock index eeb97d2..af948c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -998,7 +998,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1013,7 +1013,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "quote", "syn 1.0.109", @@ -1083,7 +1083,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "almost", "cosmic-config", @@ -2612,7 +2612,7 @@ dependencies = [ [[package]] name = "iced" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "iced_accessibility", "iced_core", @@ -2627,7 +2627,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "accesskit", "accesskit_winit", @@ -2636,7 +2636,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "bitflags 1.3.2", "instant", @@ -2652,7 +2652,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "futures", "iced_core", @@ -2665,7 +2665,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2688,7 +2688,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2701,7 +2701,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "iced_core", "iced_futures", @@ -2711,7 +2711,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "iced_core", "once_cell", @@ -2721,7 +2721,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "bytemuck", "cosmic-text", @@ -2739,7 +2739,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2759,7 +2759,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "iced_renderer", "iced_runtime", @@ -2773,7 +2773,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "iced_graphics", "iced_runtime", @@ -3092,7 +3092,7 @@ checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef" +source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e" dependencies = [ "apply", "ashpd", diff --git a/i18n/en/cosmic_edit.ftl b/i18n/en/cosmic_edit.ftl index b63182a..11a9107 100644 --- a/i18n/en/cosmic_edit.ftl +++ b/i18n/en/cosmic_edit.ftl @@ -37,6 +37,9 @@ default-font-size = Default font size keyboard-shortcuts = Keyboard shortcuts enable-vim-bindings = Enable Vim bindings +# Find +find-placeholder = Find... + # Menu ## File diff --git a/res/icons/edit-clear-symbolic.svg b/res/icons/edit-clear-symbolic.svg new file mode 100644 index 0000000..bcf0a70 --- /dev/null +++ b/res/icons/edit-clear-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/icons/go-up-symbolic.svg b/res/icons/go-up-symbolic.svg new file mode 100644 index 0000000..3747d43 --- /dev/null +++ b/res/icons/go-up-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/config.rs b/src/config.rs index 8b3c396..422b72d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,6 +19,8 @@ pub enum Action { CloseProject, Copy, Cut, + Find, + FindAndReplace, NewFile, NewWindow, OpenFileDialog, @@ -42,6 +44,8 @@ impl Action { Self::CloseProject => Message::CloseProject, Self::Copy => Message::Copy, Self::Cut => Message::Cut, + Self::Find => Message::Find(Some(false)), + Self::FindAndReplace => Message::Find(Some(true)), Self::NewFile => Message::NewFile, Self::NewWindow => Message::NewWindow, Self::OpenFileDialog => Message::OpenFileDialog, @@ -111,6 +115,8 @@ impl KeyBind { bind!([Ctrl], W, CloseFile); bind!([Ctrl], X, Cut); bind!([Ctrl], C, Copy); + bind!([Ctrl], F, Find); + bind!([Ctrl], H, FindAndReplace); bind!([Ctrl], V, Paste); bind!([Ctrl], T, NewFile); bind!([Ctrl], N, NewWindow); diff --git a/src/icon_cache.rs b/src/icon_cache.rs index 2008b76..aeca7db 100644 --- a/src/icon_cache.rs +++ b/src/icon_cache.rs @@ -30,9 +30,11 @@ impl IconCache { }; } + bundle!("edit-clear-symbolic", 16); bundle!("folder-open-symbolic", 16); bundle!("go-down-symbolic", 16); bundle!("go-next-symbolic", 16); + bundle!("go-up-symbolic", 16); bundle!("list-add-symbolic", 16); bundle!("object-select-symbolic", 16); bundle!("window-close-symbolic", 16); diff --git a/src/main.rs b/src/main.rs index 248afad..cd53dfd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -181,6 +181,11 @@ pub enum Message { Cut, DefaultFont(usize), DefaultFontSize(usize), + Find(Option), + FindNext, + FindPrevious, + FindReplaceValueChanged(String), + FindSearchValueChanged(String), GitProjectStatus(Vec<(String, PathBuf, Vec)>), Key(keyboard::Modifiers, keyboard::KeyCode), NewFile, @@ -241,6 +246,13 @@ impl ContextPage { } } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Find { + None, + Find, + FindAndReplace, +} + pub struct App { core: Core, nav_model: segmented_button::SingleSelectModel, @@ -253,6 +265,11 @@ pub struct App { font_sizes: Vec, theme_names: Vec, context_page: ContextPage, + text_box_id: widget::Id, + find_opt: Option, + find_replace_value: String, + find_search_id: widget::Id, + find_search_value: String, git_project_status: Option)>>, projects: Vec<(String, PathBuf)>, project_search_id: widget::Id, @@ -440,6 +457,21 @@ impl App { self.update_config() } + fn update_focus(&self) -> Command { + if self.core.window.show_context { + match self.context_page { + ContextPage::ProjectSearch => { + widget::text_input::focus(self.project_search_id.clone()) + } + _ => Command::none(), + } + } else if self.find_opt.is_some() { + widget::text_input::focus(self.find_search_id.clone()) + } else { + widget::text_input::focus(self.text_box_id.clone()) + } + } + fn update_nav_bar_active(&mut self) { let tab_path_opt = match self.active_tab() { Some(Tab::Editor(tab)) => tab.path_opt.clone(), @@ -504,7 +536,7 @@ impl App { let window_title = format!("{title} - COSMIC Text Editor"); self.set_header_title(title.clone()); - self.set_window_title(window_title) + Command::batch([self.set_window_title(window_title), self.update_focus()]) } fn document_statistics(&self) -> Element { @@ -942,6 +974,11 @@ impl Application for App { font_sizes, theme_names, context_page: ContextPage::Settings, + text_box_id: widget::Id::unique(), + find_opt: None, + find_replace_value: String::new(), + find_search_id: widget::Id::unique(), + find_search_value: String::new(), git_project_status: None, projects: Vec::new(), project_search_id: widget::Id::unique(), @@ -1021,6 +1058,25 @@ impl Application for App { Some(&self.nav_model) } + fn on_context_drawer(&mut self) -> Command { + // Focus correct widget + self.update_focus() + } + + //TODO: currently the first escape unfocuses, and the second calls this function + fn on_escape(&mut self) -> Command { + if self.core.window.show_context { + // Close context drawer if open + self.core.window.show_context = false; + } else if self.find_opt.is_some() { + // Close find if open + self.find_opt = None; + } + + // Focus correct widget + self.update_focus() + } + fn on_nav_select(&mut self, id: nav_bar::Id) -> Command { // Toggle open state and get clone of node data let node_opt = match self.nav_model.data_mut::(id) { @@ -1160,6 +1216,38 @@ impl Application for App { log::warn!("failed to find font with index {}", index); } }, + Message::Find(find_opt) => { + self.find_opt = find_opt; + + // Focus correct input + return self.update_focus(); + } + Message::FindNext => { + if !self.find_search_value.is_empty() { + if let Some(Tab::Editor(tab)) = self.active_tab() { + tab.search(&self.find_search_value, true); + } + } + + // Focus correct input + return self.update_focus(); + } + Message::FindPrevious => { + if !self.find_search_value.is_empty() { + if let Some(Tab::Editor(tab)) = self.active_tab() { + tab.search(&self.find_search_value, false); + } + } + + // Focus correct input + return self.update_focus(); + } + Message::FindReplaceValueChanged(value) => { + self.find_replace_value = value; + } + Message::FindSearchValueChanged(value) => { + self.find_search_value = value; + } Message::GitProjectStatus(project_status) => { self.git_project_status = Some(project_status); } @@ -1384,8 +1472,8 @@ impl Application for App { Message::ProjectSearchResult(project_search_result) => { self.project_search_result = Some(project_search_result); - // Ensure input remains focused - return widget::text_input::focus(self.project_search_id.clone()); + // Focus correct input + return self.update_focus(); } Message::ProjectSearchSubmit => { //TODO: Figure out length requirements? @@ -1595,13 +1683,12 @@ impl Application for App { |x| x, ); } - ContextPage::ProjectSearch => { - // Ensure focus of correct input - return widget::text_input::focus(self.project_search_id.clone()); - } _ => {} } } + + // Ensure focus of correct input + return self.update_focus(); } Message::ToggleLineNumbers => { self.config.line_numbers = !self.config.line_numbers; @@ -1713,23 +1800,22 @@ impl Application for App { } }; let mut text_box = text_box(&tab.editor, self.config.metrics()) + .id(self.text_box_id.clone()) .on_changed(Message::TabChanged(tab_id)) + .has_context_menu(tab.context_menu.is_some()) .on_context_menu(move |position_opt| { Message::TabContextMenu(tab_id, position_opt) }); if self.config.line_numbers { text_box = text_box.line_numbers(); } - let tab_element: Element<'_, Message> = match tab.context_menu { - Some(position) => widget::popover( - text_box.context_menu(position), - menu::context_menu(&self.config, tab_id), - ) - .position(position) - .into(), - None => text_box.into(), + let mut popover = + widget::popover(text_box, menu::context_menu(&self.config, tab_id)); + popover = match tab.context_menu { + Some(position) => popover.position(position), + None => popover.show_popup(false), }; - tab_column = tab_column.push(tab_element); + tab_column = tab_column.push(popover); tab_column = tab_column.push(text(status).font(Font::MONOSPACE)); } Some(Tab::GitDiff(tab)) => { @@ -1789,6 +1875,49 @@ impl Application for App { None => {} } + if let Some(replace) = &self.find_opt { + let text_input = + widget::text_input::text_input(fl!("find-placeholder"), &self.find_search_value) + .id(self.find_search_id.clone()) + .on_input(Message::FindSearchValueChanged) + //TODO: shift+enter for FindPrevious + .on_submit(Message::FindNext) + .width(Length::Fixed(320.0)) + .trailing_icon( + button(icon_cache_get("edit-clear-symbolic", 16)) + .on_press(Message::FindSearchValueChanged(String::new())) + .style(style::Button::Icon) + .into(), + ); + let find_widget = widget::row::with_children(vec![ + text_input.into(), + button(icon_cache_get("go-up-symbolic", 16)) + .on_press(Message::FindPrevious) + .padding(space_xxs) + .style(style::Button::Icon) + .into(), + button(icon_cache_get("go-down-symbolic", 16)) + .on_press(Message::FindNext) + .padding(space_xxs) + .style(style::Button::Icon) + .into(), + widget::horizontal_space(Length::Fill).into(), + button(icon_cache_get("window-close-symbolic", 16)) + .on_press(Message::Find(None)) + .padding(space_xxs) + .style(style::Button::Icon) + .into(), + ]) + .align_items(Alignment::Center) + .padding(space_xxs) + .spacing(space_xxs); + + tab_column = tab_column.push( + widget::cosmic_container::container(find_widget) + .layer(cosmic_theme::Layer::Primary), + ); + } + let content: Element<_> = tab_column.into(); // Uncomment to debug layout: diff --git a/src/menu.rs b/src/menu.rs index d3e0eb3..54fea29 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -197,8 +197,8 @@ pub fn menu_bar<'a>(config: &Config) -> Element<'a, Message> { menu_item(fl!("paste"), Message::Paste), menu_item(fl!("select-all"), Message::SelectAll), MenuTree::new(horizontal_rule(1)), - menu_key(fl!("find"), "Ctrl + F", Message::Todo), - menu_key(fl!("replace"), "Ctrl + H", Message::Todo), + menu_item(fl!("find"), Message::Find(Some(false))), + menu_item(fl!("replace"), Message::Find(Some(true))), menu_item( fl!("find-in-project"), Message::ToggleContextPage(ContextPage::ProjectSearch), diff --git a/src/tab.rs b/src/tab.rs index a3bf73e..e61dcfb 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -199,4 +199,58 @@ impl EditorTab { fl!("new-document") } } + + // Code adapted from cosmic-text ViEditor search + pub fn search(&self, value: &str, forwards: bool) -> bool { + let mut editor = self.editor.lock().unwrap(); + let mut cursor = editor.cursor(); + let start_line = cursor.line; + if forwards { + while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) { + if let Some(index) = editor.with_buffer(|buffer| { + buffer.lines[cursor.line] + .text() + .match_indices(value) + .filter_map(|(i, _)| { + if cursor.line != start_line || i > cursor.index { + Some(i) + } else { + None + } + }) + .next() + }) { + cursor.index = index; + editor.set_cursor(cursor); + return true; + } + + cursor.line += 1; + } + } else { + cursor.line += 1; + while cursor.line > 0 { + cursor.line -= 1; + + if let Some(index) = editor.with_buffer(|buffer| { + buffer.lines[cursor.line] + .text() + .rmatch_indices(value) + .filter_map(|(i, _)| { + if cursor.line != start_line || i < cursor.index { + Some(i) + } else { + None + } + }) + .next() + }) { + cursor.index = index; + editor.set_cursor(cursor); + return true; + } + } + } + false + } } diff --git a/src/text_box.rs b/src/text_box.rs index dcdc836..8ec80fc 100644 --- a/src/text_box.rs +++ b/src/text_box.rs @@ -12,7 +12,11 @@ use cosmic::{ image, layout::{self, Layout}, renderer::{self, Quad}, - widget::{self, tree, Widget}, + widget::{ + self, + operation::{self, Operation, OperationOutputWrapper}, + tree, Id, Widget, + }, Shell, }, }; @@ -29,10 +33,11 @@ use crate::{line_number::LineNumberKey, FONT_SYSTEM, LINE_NUMBER_CACHE, SWASH_CA pub struct TextBox<'a, Message> { editor: &'a Mutex>, metrics: Metrics, + id: Option, padding: Padding, on_changed: Option, click_timing: Duration, - context_menu: Option, + has_context_menu: bool, on_context_menu: Option) -> Message + 'a>>, line_numbers: bool, } @@ -45,15 +50,21 @@ where Self { editor, metrics, + id: None, padding: Padding::new(0.0), on_changed: None, click_timing: Duration::from_millis(500), - context_menu: None, + has_context_menu: false, on_context_menu: None, line_numbers: false, } } + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self @@ -69,8 +80,8 @@ where self } - pub fn context_menu(mut self, position: Point) -> Self { - self.context_menu = Some(position); + pub fn has_context_menu(mut self, has_context_menu: bool) -> Self { + self.has_context_menu = has_context_menu; self } @@ -252,6 +263,18 @@ where }) } + fn operate( + &self, + tree: &mut widget::Tree, + _layout: Layout<'_>, + _renderer: &Renderer, + operation: &mut dyn Operation>, + ) { + let state = tree.state.downcast_mut::(); + + operation.focusable(state, self.id.as_ref()); + } + fn mouse_interaction( &self, tree: &widget::Tree, @@ -611,79 +634,92 @@ where Event::Keyboard(KeyEvent::KeyPressed { key_code, modifiers, - }) => match key_code { - KeyCode::Left => { - editor.action(Action::Motion(Motion::Left)); - status = Status::Captured; - } - KeyCode::Right => { - editor.action(Action::Motion(Motion::Right)); - status = Status::Captured; - } - KeyCode::Up => { - editor.action(Action::Motion(Motion::Up)); - status = Status::Captured; - } - KeyCode::Down => { - editor.action(Action::Motion(Motion::Down)); - status = Status::Captured; - } - KeyCode::Home => { - editor.action(Action::Motion(Motion::Home)); - status = Status::Captured; - } - KeyCode::End => { - editor.action(Action::Motion(Motion::End)); - status = Status::Captured; - } - KeyCode::PageUp => { - editor.action(Action::Motion(Motion::PageUp)); - status = Status::Captured; - } - KeyCode::PageDown => { - editor.action(Action::Motion(Motion::PageDown)); - status = Status::Captured; - } - KeyCode::Escape => { - editor.action(Action::Escape); - status = Status::Captured; - } - KeyCode::Enter => { - editor.action(Action::Enter); - status = Status::Captured; - } - KeyCode::Backspace => { - editor.action(Action::Backspace); - status = Status::Captured; - } - KeyCode::Delete => { - editor.action(Action::Delete); - status = Status::Captured; - } - KeyCode::Tab => { - if modifiers.shift() { - editor.action(Action::Unindent); - } else { - editor.action(Action::Indent); + }) => { + // Only parse keys when focused + if state.is_focused { + match key_code { + KeyCode::Left => { + editor.action(Action::Motion(Motion::Left)); + status = Status::Captured; + } + KeyCode::Right => { + editor.action(Action::Motion(Motion::Right)); + status = Status::Captured; + } + KeyCode::Up => { + editor.action(Action::Motion(Motion::Up)); + status = Status::Captured; + } + KeyCode::Down => { + editor.action(Action::Motion(Motion::Down)); + status = Status::Captured; + } + KeyCode::Home => { + editor.action(Action::Motion(Motion::Home)); + status = Status::Captured; + } + KeyCode::End => { + editor.action(Action::Motion(Motion::End)); + status = Status::Captured; + } + KeyCode::PageUp => { + editor.action(Action::Motion(Motion::PageUp)); + status = Status::Captured; + } + KeyCode::PageDown => { + editor.action(Action::Motion(Motion::PageDown)); + status = Status::Captured; + } + KeyCode::Escape => { + editor.action(Action::Escape); + status = Status::Captured; + } + KeyCode::Enter => { + editor.action(Action::Enter); + status = Status::Captured; + } + KeyCode::Backspace => { + editor.action(Action::Backspace); + status = Status::Captured; + } + KeyCode::Delete => { + editor.action(Action::Delete); + status = Status::Captured; + } + KeyCode::Tab => { + if modifiers.shift() { + editor.action(Action::Unindent); + } else { + editor.action(Action::Indent); + } + status = Status::Captured; + } + _ => (), } - status = Status::Captured; } - _ => (), - }, + } Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => { state.modifiers = modifiers; } Event::Keyboard(KeyEvent::CharacterReceived(character)) => { - // Only parse keys when Super, Ctrl, and Alt are not pressed - if !state.modifiers.logo() && !state.modifiers.control() && !state.modifiers.alt() { - if !character.is_control() { - editor.action(Action::Insert(character)); + // Only parse keys when focused + if state.is_focused { + // Only parse keys when Super, Ctrl, and Alt are not pressed + if !state.modifiers.logo() + && !state.modifiers.control() + && !state.modifiers.alt() + { + if !character.is_control() { + editor.action(Action::Insert(character)); + } + status = Status::Captured; } - status = Status::Captured; } } Event::Mouse(MouseEvent::ButtonPressed(button)) => { if let Some(p) = cursor_position.position_in(layout.bounds()) { + state.is_focused = true; + // Handle left click drag if let Button::Left = button { let x_logical = p.x - self.padding.left; @@ -746,12 +782,13 @@ where // Update context menu state if let Some(on_context_menu) = &self.on_context_menu { - shell.publish((on_context_menu)(match self.context_menu { - Some(_) => None, - None => match button { + shell.publish((on_context_menu)(if self.has_context_menu { + None + } else { + match button { Button::Right => Some(p), _ => None, - }, + } })); } @@ -869,6 +906,7 @@ pub struct State { click: Option<(ClickKind, Instant)>, dragging: Option, editor_offset_x: Cell, + is_focused: bool, scale_factor: Cell, scroll_pixels: f32, scrollbar_rect: Cell>, @@ -883,6 +921,7 @@ impl State { click: None, dragging: None, editor_offset_x: Cell::new(0), + is_focused: false, scale_factor: Cell::new(1.0), scroll_pixels: 0.0, scrollbar_rect: Cell::new(Rectangle::default()), @@ -890,3 +929,17 @@ impl State { } } } + +impl operation::Focusable for State { + fn is_focused(&self) -> bool { + self.is_focused + } + + fn focus(&mut self) { + self.is_focused = true; + } + + fn unfocus(&mut self) { + self.is_focused = false; + } +}