diff --git a/src/mouse_area.rs b/src/mouse_area.rs index 4ab6c05..22fe1bc 100644 --- a/src/mouse_area.rs +++ b/src/mouse_area.rs @@ -50,6 +50,7 @@ pub struct MouseArea<'a, Message> { on_enter: Option>>, on_exit: Option>>, show_drag_rect: bool, + cursor_offset: Option } impl<'a, Message> MouseArea<'a, Message> { @@ -185,6 +186,11 @@ impl<'a, Message> MouseArea<'a, Message> { self } + pub fn cursor_offset(mut self, offset: Option) -> Self { + self.cursor_offset = offset; + self + } + /// Sets the widget's unique identifier. #[must_use] pub fn with_id(mut self, id: Id) -> Self { @@ -215,6 +221,8 @@ impl<'a, Message, F> OnEnterExit<'a, Message> for F where F: Fn() -> Message + ' #[derive(Default)] struct State { last_position: Option, + last_virtual_position: Option, + last_in_bounds_position: Option, drag_initiated: Option, modifiers: Modifiers, prev_click: Option<(mouse::Click, Instant)>, @@ -224,7 +232,7 @@ struct State { impl State { fn drag_rect(&self, cursor: mouse::Cursor) -> Option { if let Some(drag_source) = self.drag_initiated { - if let Some(position) = cursor.position() { + if let Some(position) = cursor.position().or(self.last_virtual_position) { if position.distance(drag_source) > 1.0 { let min_x = drag_source.x.min(position.x); let max_x = drag_source.x.max(position.x); @@ -292,6 +300,7 @@ impl<'a, Message> MouseArea<'a, Message> { on_exit: None, on_scroll: None, show_drag_rect: false, + cursor_offset: None } } } @@ -522,6 +531,42 @@ fn update( _ => {} } state.last_position = position_in; + + // if we have a cursor_offset, we need to calculate our "virtual" position + // (where we think the ABSOLUTE cursor is) - we'll take the last in bounds position and + // clamp it to the layout bounds + if let Some(offset) = widget.cursor_offset { + if let Some(in_bounds_pos) = state.last_in_bounds_position { + let mut new_virtual_pos = Point { + x: in_bounds_pos.x + offset.x, + y: in_bounds_pos.y + offset.y + }; + + // clamp to the viewport + new_virtual_pos.x = if new_virtual_pos.x > (layout_bounds.width + layout_bounds.x) { + layout_bounds.width + layout_bounds.x + } else if new_virtual_pos.x < 0.0 { + 0.0 + } else { + new_virtual_pos.x + }; + + new_virtual_pos.y = if new_virtual_pos.y > (layout_bounds.height + layout_bounds.y) { + layout_bounds.height + layout_bounds.y + } else if new_virtual_pos.y < 0.0 { + 0.0 + } else { + new_virtual_pos.y + }; + + state.last_virtual_position = Some(new_virtual_pos); + } + } + + // set the last in bounds position to be the ABSOLUTE version of position_in + if position_in.is_some() { + state.last_in_bounds_position = cursor.position_over(layout_bounds); + } } if state.drag_initiated.is_none() && !cursor.is_over(layout_bounds) { diff --git a/src/tab.rs b/src/tab.rs index f6b68e3..c135f15 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -83,7 +83,7 @@ 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 = 5; +const DRAG_SCROLL_DISTANCE: u8 = 1; //TODO: adjust for locales? const DATE_TIME_FORMAT: &str = "%b %-d, %-Y, %-I:%M %p"; @@ -1779,6 +1779,7 @@ pub struct Tab { global_cursor_position: Option, current_drag_rect: Option, viewport_rect: Option, + virtual_cursor_offset: Option, } fn calculate_dir_size(path: &Path, controller: Controller) -> Result { @@ -1866,6 +1867,7 @@ impl Tab { global_cursor_position: None, current_drag_rect: None, viewport_rect: None, + virtual_cursor_offset: None } } @@ -2216,23 +2218,56 @@ impl Tab { if self.current_drag_rect.is_some() { if let Some(viewport) = self.viewport_rect { if !viewport.contains(pos) { - // 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 - viewport.y; - let scroll_y: i8 = if diff_y > 0.0 { - DRAG_SCROLL_DISTANCE as i8 - } else if diff_y < 0.0 { - DRAG_SCROLL_DISTANCE as i8 * -1 - } else { - 0 - }; + if pos.y < viewport.y || pos.y > (viewport.y + viewport.height) { + // if our mouse is above the scrollable viewport, we want to scroll up + let drag_start_point = Point { + x: viewport.x, + y: viewport.y + }; + // 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 + } else if diff_y < 0.0 { + DRAG_SCROLL_DISTANCE as i8 * -1 + } else { + 0 + }; - commands.push(Command::Iced( - scrollable::scroll_by(self.scrollable_id.clone(), AbsoluteOffset { + let mut new_offset = Point { x: 0.0, y: scroll_y as f32 - }).into(), - )); + }; + + if let Some(offset) = self.virtual_cursor_offset { + new_offset = Point { + x: new_offset.x + offset.x, + y: new_offset.y + offset.y, + }; + } + + self.virtual_cursor_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(), + )); + } + else { + if self.virtual_cursor_offset.is_none() { + self.virtual_cursor_offset = Some(Point { + x: 0.0, + y: 0.0 + }); + } + } + } + else { + // reset our virtual cursor offset when we're back in bounds + self.virtual_cursor_offset = None; } } } @@ -4148,6 +4183,7 @@ impl Tab { .show_drag_rect(true) .on_release(|_| Message::ClickRelease(None)) .on_resize(Message::MouseAreaResized) + .cursor_offset(self.virtual_cursor_offset) .into(), true, ) @@ -4479,6 +4515,7 @@ impl Tab { .show_drag_rect(true) .on_release(|_| Message::ClickRelease(None)) .on_resize(Message::MouseAreaResized) + .cursor_offset(self.virtual_cursor_offset) .into(), true, )