diff --git a/src/app.rs b/src/app.rs index 0651bbe..5bce184 100644 --- a/src/app.rs +++ b/src/app.rs @@ -18,6 +18,7 @@ use cosmic::{ app::{self, context_drawer, message, Core, Task}, cosmic_config, cosmic_theme, executor, iced::{ + self, clipboard::dnd::DndAction, event, futures::{self, SinkExt}, @@ -332,6 +333,7 @@ pub enum Message { Rename(Option), ReplaceResult(ReplaceResult), RestoreFromTrash(Option), + ScrollTab(f32), SearchActivate, SearchClear, SearchInput(String), @@ -550,6 +552,7 @@ pub struct App { tab_dnd_hover: Option<(Entity, Instant)>, nav_drag_id: DragId, tab_drag_id: DragId, + auto_scroll_speed: Option } impl App { @@ -1628,6 +1631,7 @@ impl Application for App { tab_dnd_hover: None, nav_drag_id: DragId::new(), tab_drag_id: DragId::new(), + auto_scroll_speed: None, }; let mut commands = vec![app.update_config()]; @@ -2794,6 +2798,10 @@ impl Application for App { self.operation(Operation::Restore { items: trash_items }); } } + Message::ScrollTab(scroll_speed) => { + let entity = self.tab_model.active(); + return self.update(Message::TabMessage(Some(entity), tab::Message::ScrollTab(scroll_speed))); + } Message::SearchActivate => { return if self.search_get().is_none() { self.search_set_active(Some(String::new())) @@ -2931,6 +2939,9 @@ impl Application for App { config_set!(favorites, favorites); commands.push(self.update_config()); } + tab::Command::AutoScroll(scroll_speed) => { + self.auto_scroll_speed = scroll_speed; + } tab::Command::ChangeLocation(tab_title, tab_path, selection_paths) => { self.activate_nav_model_location(&tab_path); @@ -4714,6 +4725,13 @@ impl Application for App { ), ]; + if let Some(scroll_speed) = self.auto_scroll_speed { + subscriptions.push( + iced::time::every(time::Duration::from_millis(10)) + .map(move |_| Message::ScrollTab(scroll_speed)) + ); + } + for (key, mounter) in MOUNTERS.iter() { subscriptions.push( mounter.subscription().with(*key).map( diff --git a/src/tab.rs b/src/tab.rs index 78daf42..71a1304 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -83,7 +83,8 @@ const MAX_SEARCH_RESULTS: usize = 200; //TODO: configurable thumbnail size? const THUMBNAIL_SIZE: u32 = (ICON_SIZE_GRID as u32) * (ICON_SCALE_MAX as u32); -const DRAG_SCROLL_DISTANCE: u8 = 1; +const DRAG_SCROLL_DISTANCE: f32 = 15.0; +const DRAG_SCROLL_RATIO_MAXIMUM: f32 = 3.0; //TODO: adjust for locales? const DATE_TIME_FORMAT: &str = "%b %-d, %-Y, %-I:%M %p"; @@ -1100,6 +1101,7 @@ pub enum Command { Action(Action), AddNetworkDrive, AddToSidebar(PathBuf), + AutoScroll(Option), ChangeLocation(String, Location, Option>), DropFiles(PathBuf, ClipboardPaste), EmptyTrash, @@ -1157,6 +1159,7 @@ pub enum Message { RightClick(Option), MiddleClick(usize), Scroll(Viewport), + ScrollTab(f32), ScrollToFocus, SearchContext(Location, SearchContextWrapper), SearchReady(bool), @@ -2231,17 +2234,36 @@ impl Tab { // diff_y should be NEGATIVE here when close to y=0 (above the MouseArea) // and positive when below the viewport let diff_y = pos.y - drag_start_point.y; - let scroll_y: i8 = if diff_y > 0.0 { - DRAG_SCROLL_DISTANCE as i8 + let mut scroll_y: f32 = if diff_y > 0.0 { + DRAG_SCROLL_DISTANCE } else if diff_y < 0.0 { - DRAG_SCROLL_DISTANCE as i8 * -1 + DRAG_SCROLL_DISTANCE * -1.0 } else { - 0 + 0.0 }; + + // estimate distance and use that to control speed + // go up to 3x speed + let quarter_height = viewport.height / 4.0; + let cursor_y_distance = if diff_y > 0.0 { + pos.y - (viewport.y + viewport.height) + } else if diff_y < 0.0 { + pos.y - viewport.y + } else { + 0.0 + }.abs(); + + let mut speed_ratio = (cursor_y_distance / quarter_height) + 1.0; + if speed_ratio > DRAG_SCROLL_RATIO_MAXIMUM { + speed_ratio = DRAG_SCROLL_RATIO_MAXIMUM; + } + + scroll_y = scroll_y * speed_ratio; + let mut new_offset = Point { x: 0.0, - y: scroll_y as f32 + y: scroll_y }; if let Some(virtual_cursor_offset) = self.virtual_cursor_offset { @@ -2258,12 +2280,7 @@ impl Tab { self.virtual_cursor_offset = Some(new_offset); self.last_scroll_offset = Some(new_offset); - commands.push(Command::Iced( - scrollable::scroll_by(self.scrollable_id.clone(), AbsoluteOffset { - x: 0.0, - y: scroll_y as f32 - }).into(), - )); + commands.push(Command::AutoScroll(Some(scroll_y))); } else { if let Some(last_scroll_offset) = self.last_scroll_offset { @@ -2288,8 +2305,9 @@ impl Tab { y: 0.0 }); } - } + + commands.push(Command::AutoScroll(None)); } } else { @@ -2297,6 +2315,8 @@ impl Tab { self.virtual_cursor_offset = None; self.last_scroll_position = Some(pos); self.last_scroll_offset = None; + + commands.push(Command::AutoScroll(None)); } } } @@ -2329,6 +2349,8 @@ impl Tab { item.overlaps_drag_rect = false; } } + + commands.push(Command::AutoScroll(None)); } Message::DoubleClick(click_i_opt) => { if let Some(clicked_item) = self @@ -2959,6 +2981,14 @@ impl Tab { Message::Scroll(viewport) => { self.scroll_opt = Some(viewport.absolute_offset()); } + Message::ScrollTab(scroll_speed) => { + commands.push(Command::Iced( + scrollable::scroll_by(self.scrollable_id.clone(), AbsoluteOffset { + x: 0.0, + y: scroll_speed + }).into(), + )); + } Message::ScrollToFocus => { if let Some(offset) = self.select_focus_scroll() { commands.push(Command::Iced(