use super::*;
pub const DRIFT_EDGE: Pixels = px(96.0);
pub const DRIFT_SPEED: f32 = 1320.0;
pub(super) const DRIFT_STEP: Duration = Duration::from_millis(50);
pub fn drift_velocity(pointer: Pixels, start: Pixels, end: Pixels) -> f32 {
let edge = DRIFT_EDGE.as_f32().min((end - start).as_f32() / 2.0);
if edge <= 0.0 {
return 0.0;
}
let (from_start, from_end) = ((pointer - start).as_f32(), (end - pointer).as_f32());
let near = from_start.min(from_end);
if near >= edge {
return 0.0;
}
let ramp = 1.0 - near.max(0.0) / edge;
match from_start < from_end {
true => DRIFT_SPEED * ramp,
false => -DRIFT_SPEED * ramp,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Beyond {
Nothing,
Neighbour,
}
pub fn pane_velocity(
bounds: Bounds<Pixels>,
pointer: Point<Pixels>,
axes: Axes,
beyond: Beyond,
) -> Point<f32> {
if beyond == Beyond::Neighbour && !bounds.contains(&pointer) {
return point(0.0, 0.0);
}
let mut velocity = point(0.0, 0.0);
if axes.horizontal() && (bounds.top()..=bounds.bottom()).contains(&pointer.y) {
velocity.x = drift_velocity(pointer.x, bounds.left(), bounds.right());
}
if axes.vertical() && (bounds.left()..=bounds.right()).contains(&pointer.x) {
velocity.y = drift_velocity(pointer.y, bounds.top(), bounds.bottom());
}
velocity
}
#[derive(Clone, Copy, Default)]
pub(super) struct Drift {
aim: Option<Point<Pixels>>,
since: Option<Instant>,
}
#[derive(Clone, Default)]
pub struct DriftState(Rc<Cell<Drift>>);
impl DriftState {
pub fn new() -> Self {
Self::default()
}
pub fn aim(&self, pointer: Point<Pixels>) {
let drift = self.0.get();
self.0.set(Drift {
aim: Some(pointer),
..drift
});
}
}
pub fn drift(
handle: &ScrollHandle,
state: &DriftState,
axes: Axes,
beyond: Beyond,
) -> gpui::AnyElement {
let handle = handle.clone();
let state = state.clone();
canvas(
move |_, window, cx: &mut App| {
let drift = state.0.get();
let Some(pointer) = drift.aim.filter(|_| cx.has_active_drag()) else {
state.0.set(Drift::default());
return;
};
let velocity = pane_velocity(handle.bounds(), pointer, axes, beyond);
if velocity.x == 0.0 && velocity.y == 0.0 {
state.0.set(Drift {
aim: Some(pointer),
since: None,
});
return;
}
let now = cx.background_executor().now();
state.0.set(Drift {
aim: Some(pointer),
since: Some(now),
});
let Some(last) = drift.since else {
window.request_animation_frame();
return;
};
let step = (now - last).min(DRIFT_STEP).as_secs_f32();
let (offset, max) = (handle.offset(), handle.max_offset());
let moved = point(
(offset.x + px(velocity.x * step)).clamp(-max.x, px(0.0)),
(offset.y + px(velocity.y * step)).clamp(-max.y, px(0.0)),
);
if moved == offset {
return;
}
handle.set_offset(moved);
window.request_animation_frame();
},
|_, _, _, _| {},
)
.absolute()
.size_full()
.into_any_element()
}