From a192d93f4b90295915ec992c6c0fb47837639d0c Mon Sep 17 00:00:00 2001 From: Justin Gross Date: Sat, 14 Sep 2024 02:11:06 -0400 Subject: [PATCH] chore: collapse scroll_area into mouse_area --- src/app.rs | 5 +- src/lib.rs | 1 - src/mouse_area.rs | 87 +++++++++++++++++- src/scroll_area.rs | 220 --------------------------------------------- src/tab.rs | 116 ++++++++++-------------- 5 files changed, 133 insertions(+), 296 deletions(-) delete mode 100644 src/scroll_area.rs diff --git a/src/app.rs b/src/app.rs index 79a7eea..e15bfd2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3766,17 +3766,16 @@ pub(crate) mod test_utils { ); } - pub fn assert_scroll_affects_item_zoom( + pub fn assert_zoom_affects_item_size( tab: &mut Tab, message: tab::Message, - modifiers: Modifiers, should_zoom: bool, ) { let grid_icon_size = tab.config.icon_sizes.grid; let list_icon_size = tab.config.icon_sizes.list; debug!("Emitting {:?}", message); - tab.update(message, modifiers); + tab.update(message, Modifiers::empty()); let grid_size_changed = grid_icon_size != tab.config.icon_sizes.grid; let list_size_changed = list_icon_size != tab.config.icon_sizes.list; diff --git a/src/lib.rs b/src/lib.rs index 1a60a6b..f23d24e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,6 @@ pub mod mime_icon; mod mounter; mod mouse_area; mod operation; -mod scroll_area; mod spawn_detached; use tab::Location; pub mod tab; diff --git a/src/mouse_area.rs b/src/mouse_area.rs index 71a049e..2de7aa7 100644 --- a/src/mouse_area.rs +++ b/src/mouse_area.rs @@ -6,8 +6,14 @@ use cosmic::{ iced_core::{ border::Border, event::{self, Event}, + keyboard::{ + self, + key::{self, Key}, + Event::{KeyPressed, KeyReleased}, + Modifiers, + }, layout, - mouse::{self, click}, + mouse::{self, click, Event as MouseEvent}, overlay, renderer::{self, Quad, Renderer as _}, touch, @@ -39,6 +45,7 @@ pub struct MouseArea<'a, Message> { on_back_release: Option) -> Message + 'a>>, on_forward_press: Option) -> Message + 'a>>, on_forward_release: Option) -> Message + 'a>>, + on_scroll: Option Option + 'a>>, show_drag_rect: bool, } @@ -144,6 +151,16 @@ impl<'a, Message> MouseArea<'a, Message> { self } + /// The message to emit on a scroll. + #[must_use] + pub fn on_scroll( + mut self, + message: impl Fn(mouse::ScrollDelta, Modifiers) -> Option + 'a, + ) -> Self { + self.on_scroll = Some(Box::new(message)); + self + } + #[must_use] pub fn show_drag_rect(mut self, show_drag_rect: bool) -> Self { self.show_drag_rect = show_drag_rect; @@ -163,7 +180,7 @@ impl<'a, Message> MouseArea<'a, Message> { struct State { // TODO: Support on_mouse_enter and on_mouse_exit drag_initiated: Option, - + modifiers: Modifiers, prev_click: Option<(mouse::Click, Instant)>, } @@ -227,6 +244,7 @@ impl<'a, Message> MouseArea<'a, Message> { on_back_release: None, on_forward_press: None, on_forward_release: None, + on_scroll: None, show_drag_rect: false, } } @@ -578,6 +596,21 @@ fn update( } } + if let Some(message) = widget.on_scroll.as_ref() { + if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event { + if let Some(on_scroll) = widget.on_scroll.as_ref() { + if let Some(message) = on_scroll(delta.clone(), state.modifiers) { + shell.publish(message); + return event::Status::Captured; + } + } + } + } + + if let Event::Keyboard(key_event) = event { + handle_key_event(key_event, state) + }; + if let Some((message, drag_rect)) = widget.on_drag.as_ref().zip(state.drag_rect(cursor)) { shell.publish(message(drag_rect.intersection(&layout_bounds).map( |mut rect| { @@ -590,3 +623,53 @@ fn update( event::Status::Ignored } + +fn handle_key_event(key_event: &keyboard::Event, state: &mut State) { + if let KeyPressed { + key: Key::Named(key::Named::Control), + .. + } = key_event + { + state.modifiers.insert(Modifiers::CTRL); + } + + if let KeyReleased { + key: Key::Named(key::Named::Control), + .. + } = key_event + { + state.modifiers.remove(Modifiers::CTRL); + } + + if let KeyPressed { + key: Key::Named(key::Named::Shift), + .. + } = key_event + { + state.modifiers.insert(Modifiers::SHIFT); + } + + if let KeyReleased { + key: Key::Named(key::Named::Shift), + .. + } = key_event + { + state.modifiers.remove(Modifiers::SHIFT); + } + + if let KeyPressed { + key: Key::Named(key::Named::Alt), + .. + } = key_event + { + state.modifiers.insert(Modifiers::ALT); + } + + if let KeyReleased { + key: Key::Named(key::Named::Alt), + .. + } = key_event + { + state.modifiers.remove(Modifiers::ALT); + } +} diff --git a/src/scroll_area.rs b/src/scroll_area.rs deleted file mode 100644 index ac72a59..0000000 --- a/src/scroll_area.rs +++ /dev/null @@ -1,220 +0,0 @@ -//! A container for reacting to scroll events. - -use cosmic::{ - iced_core::{ - event::{self, Event}, - layout, - mouse::{self}, - overlay, - renderer::{self}, - widget::{Operation, OperationOutputWrapper, Tree}, - Clipboard, Layout, Length, Rectangle, Shell, Size, Widget, - }, - widget::Id, - Element, Renderer, Theme, -}; - -/// Emit messages on scroll events. Optionally continue propogating scroll events. -#[allow(missing_debug_implementations)] -pub struct ScrollArea<'a, Message> { - id: Id, - content: Element<'a, Message>, - on_scroll: Option) -> Option + 'a>>, - should_propogate_events: bool, -} - -impl<'a, Message> ScrollArea<'a, Message> { - /// The message to emit on a scroll. - #[must_use] - pub fn on_scroll( - mut self, - message: impl Fn(Option) -> Option + 'a, - ) -> Self { - self.on_scroll = Some(Box::new(message)); - self - } - - /// Sets the widget's unique identifier. - #[must_use] - pub fn with_id(mut self, id: Id) -> Self { - self.id = id; - self - } -} - -impl<'a, Message> ScrollArea<'a, Message> { - /// Creates a [`ScrollArea`] with the given content. - pub fn new(content: impl Into>, should_propogate_events: bool) -> Self { - ScrollArea { - id: Id::unique(), - content: content.into(), - on_scroll: None, - should_propogate_events, - } - } -} - -impl<'a, Message> Widget for ScrollArea<'a, Message> -where - Message: Clone, -{ - fn children(&self) -> Vec { - vec![Tree::new(&self.content)] - } - - fn diff(&mut self, tree: &mut Tree) { - tree.diff_children(std::slice::from_mut(&mut self.content)); - } - - fn size(&self) -> Size { - self.content.as_widget().size() - } - - fn layout( - &self, - tree: &mut Tree, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.content - .as_widget() - .layout(&mut tree.children[0], renderer, limits) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn Operation>, - ) { - self.content - .as_widget() - .operate(&mut tree.children[0], layout, renderer, operation); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - viewport: &Rectangle, - ) -> event::Status { - if let event::Status::Captured = self.content.as_widget_mut().on_event( - &mut tree.children[0], - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - viewport, - ) { - return event::Status::Captured; - } - - update(self, &event, layout, cursor, shell) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.content.as_widget().mouse_interaction( - &tree.children[0], - layout, - cursor, - viewport, - renderer, - ) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Theme, - renderer_style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - ) { - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - renderer_style, - layout, - cursor, - viewport, - ); - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - self.content - .as_widget_mut() - .overlay(&mut tree.children[0], layout, renderer) - } - - fn id(&self) -> Option { - Some(self.id.clone()) - } - - fn set_id(&mut self, id: Id) { - self.id = id; - } -} - -impl<'a, Message> From> for Element<'a, Message> -where - Message: 'a + Clone, - Renderer: 'a + renderer::Renderer, - Theme: 'a, -{ - fn from(area: ScrollArea<'a, Message>) -> Element<'a, Message> { - Element::new(area) - } -} - -/// Processes the given [`Event`] and emit any messages produced by [ScrollArea::on_scroll]. -fn update( - widget: &mut ScrollArea<'_, Message>, - event: &Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, -) -> event::Status { - let layout_bounds = layout.bounds(); - if !cursor.is_over(layout_bounds) { - return event::Status::Ignored; - } - - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - if let Some(on_scroll) = widget.on_scroll.as_ref() { - if let Some(message) = on_scroll(Some(delta.clone())) { - shell.publish(message); - if !widget.should_propogate_events { - return event::Status::Captured; - } - } - } - } - _ => {} - } - - event::Status::Ignored -} diff --git a/src/tab.rs b/src/tab.rs index 646c298..93b8db2 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -7,8 +7,9 @@ use cosmic::{ }, alignment::{Horizontal, Vertical}, clipboard::dnd::DndAction, + event, futures::SinkExt, - keyboard::Modifiers, + keyboard::{self, Modifiers}, subscription::{self, Subscription}, //TODO: export in cosmic::widget widget::{ @@ -24,7 +25,7 @@ use cosmic::{ Rectangle, Size, }, - iced_core::widget::tree, + iced_core::{mouse::ScrollDelta, widget::tree}, iced_style::rule, theme, widget::{ @@ -64,7 +65,6 @@ use crate::{ mime_app::{mime_apps, MimeApp}, mime_icon::{mime_for_path, mime_icon}, mouse_area, - scroll_area::ScrollArea, }; use unix_permissions_ext::UNIXPermissionsExt; use uzers::{get_group_by_gid, get_user_by_uid}; @@ -829,8 +829,6 @@ pub enum Message { ZoomDefault, ZoomIn, ZoomOut, - ScrollUp, - ScrollDown, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -1549,16 +1547,6 @@ impl Tab { Message::AddNetworkDrive => { commands.push(Command::AddNetworkDrive); } - Message::ScrollUp => { - if mod_ctrl { - self.update(Message::ZoomIn, modifiers); - } - } - Message::ScrollDown => { - if mod_ctrl { - self.update(Message::ZoomOut, modifiers); - } - } Message::ClickRelease(click_i_opt) => { if click_i_opt == self.clicked.take() { return commands; @@ -3365,7 +3353,8 @@ impl Tab { .on_press(move |_point_opt| Message::Click(None)) .on_release(|_| Message::ClickRelease(None)) .on_back_press(move |_point_opt| Message::GoPrevious) - .on_forward_press(move |_point_opt| Message::GoNext); + .on_forward_press(move |_point_opt| Message::GoNext) + .on_scroll(respond_to_scroll_direction); if self.context_menu.is_some() { mouse_area = mouse_area.on_right_press(move |_point_opt| Message::ContextMenu(None)); @@ -3374,9 +3363,6 @@ impl Tab { } let should_propogate_events = true; - let mouse_area = ScrollArea::new(mouse_area, should_propogate_events) - .on_scroll(respond_to_scroll_direction); - let mut popover = widget::popover(mouse_area); if let Some(point) = self.context_menu { @@ -3582,21 +3568,22 @@ impl Tab { } } -fn respond_to_scroll_direction( - delta: Option, -) -> Option { +pub fn respond_to_scroll_direction(delta: ScrollDelta, modifiers: Modifiers) -> Option { + if !modifiers.control() { + return None; + } + let delta_y = match delta { - Some(cosmic::iced_core::mouse::ScrollDelta::Lines { y, .. }) => y, - Some(cosmic::iced_core::mouse::ScrollDelta::Pixels { y, .. }) => y, - None => 0.0, + ScrollDelta::Lines { y, .. } => y, + ScrollDelta::Pixels { y, .. } => y, }; if delta_y > 0.0 { - return Some(Message::ScrollUp); + return Some(Message::ZoomIn); } if delta_y < 0.0 { - return Some(Message::ScrollDown); + return Some(Message::ZoomOut); } None @@ -3606,15 +3593,15 @@ fn respond_to_scroll_direction( mod tests { use std::{fs, io, path::PathBuf}; - use cosmic::iced_runtime::keyboard::Modifiers; + use cosmic::{iced::mouse::ScrollDelta, iced_runtime::keyboard::Modifiers}; use log::{debug, trace}; use tempfile::TempDir; use test_log::test; - use super::{scan_path, Location, Message, Tab}; + use super::{scan_path, respond_to_scroll_direction, Location, Message, Tab}; use crate::{ app::test_utils::{ - assert_eq_tab_path, assert_scroll_affects_item_zoom, empty_fs, eq_path_item, + assert_eq_tab_path, assert_zoom_affects_item_size, empty_fs, eq_path_item, filter_dirs, read_dir_sorted, simple_fs, tab_click_new, NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, }, @@ -3850,66 +3837,55 @@ mod tests { } #[test] - fn tab_scroll_up_with_ctrl_modifier_zooms() -> io::Result<()> { + fn tab_zoom_in_increases_item_view_size() -> io::Result<()> { let fs = simple_fs(0, NUM_NESTED, NUM_DIRS, 0, NAME_LEN)?; let path = fs.path(); let mut tab = Tab::new(Location::Path(path.into()), TabConfig::default()); - let should_zoom = true; - assert_scroll_affects_item_zoom(&mut tab, Message::ScrollUp, Modifiers::CTRL, should_zoom); + let should_affect_size = true; + assert_zoom_affects_item_size(&mut tab, Message::ZoomIn, should_affect_size); + Ok(()) + } + fn tab_zoom_out_decreases_item_view_size() -> io::Result<()> { + let fs = simple_fs(0, NUM_NESTED, NUM_DIRS, 0, NAME_LEN)?; + let path = fs.path(); + + let mut tab = Tab::new(Location::Path(path.into()), TabConfig::default()); + + let should_affect_size = true; + assert_zoom_affects_item_size(&mut tab, Message::ZoomOut, should_affect_size); + Ok(()) + } + + #[test] + fn tab_scroll_up_with_ctrl_modifier_zooms() -> io::Result<()> { + let message_maybe = respond_to_scroll_direction(ScrollDelta::Pixels { x: 0.0, y: 1.0 }, Modifiers::CTRL); + assert!(!message_maybe.is_none()); + assert!(matches!(message_maybe.unwrap(), Message::ZoomIn)); Ok(()) } #[test] fn tab_scroll_up_without_ctrl_modifier_does_not_zoom() -> io::Result<()> { - let fs = simple_fs(0, NUM_NESTED, NUM_DIRS, 0, NAME_LEN)?; - let path = fs.path(); - - let mut tab = Tab::new(Location::Path(path.into()), TabConfig::default()); - let should_not_zoom = false; - assert_scroll_affects_item_zoom( - &mut tab, - Message::ScrollUp, - Modifiers::empty(), - should_not_zoom, - ); - + let message_maybe = respond_to_scroll_direction(ScrollDelta::Pixels { x: 0.0, y: 1.0 }, Modifiers::empty()); + assert!(message_maybe.is_none()); Ok(()) } #[test] fn tab_scroll_down_with_ctrl_modifier_zooms() -> io::Result<()> { - let fs = simple_fs(0, NUM_NESTED, NUM_DIRS, 0, NAME_LEN)?; - let path = fs.path(); - - let mut tab = Tab::new(Location::Path(path.into()), TabConfig::default()); - let should_zoom = true; - assert_scroll_affects_item_zoom( - &mut tab, - Message::ScrollDown, - Modifiers::CTRL, - should_zoom, - ); - + let message_maybe = respond_to_scroll_direction(ScrollDelta::Pixels { x: 0.0, y: -1.0 }, Modifiers::CTRL); + assert!(!message_maybe.is_none()); + assert!(matches!(message_maybe.unwrap(), Message::ZoomOut)); Ok(()) } #[test] fn tab_scroll_down_without_ctrl_modifier_does_not_zoom() -> io::Result<()> { - let fs = simple_fs(0, NUM_NESTED, NUM_DIRS, 0, NAME_LEN)?; - let path = fs.path(); - - let mut tab = Tab::new(Location::Path(path.into()), TabConfig::default()); - let should_not_zoom = false; - assert_scroll_affects_item_zoom( - &mut tab, - Message::ScrollDown, - Modifiers::empty(), - should_not_zoom, - ); - + let message_maybe = respond_to_scroll_direction(ScrollDelta::Pixels { x: 0.0, y: -1.0 }, Modifiers::empty()); + assert!(message_maybe.is_none()); Ok(()) } #[test] @@ -4059,7 +4035,7 @@ impl Widget for ArcElementWrapper { _clipboard: &mut dyn cosmic::iced_core::Clipboard, _shell: &mut cosmic::iced_core::Shell<'_, M>, _viewport: &Rectangle, - ) -> cosmic::iced_core::event::Status { + ) -> event::Status { self.0.lock().unwrap().as_widget_mut().on_event( _state, _event, _layout, _cursor, _renderer, _clipboard, _shell, _viewport, )