use super::*;
pub const AUTOSCROLL_REACH: f32 = 100.0;
pub const AUTOSCROLL_RATE: f32 = 10.0;
pub const CLOCKLESS_FRAME: f64 = 1.0 / 60.0;
const MAX_FRAME: f64 = 0.1;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum Scroller {
Container { key: Key, seen: Vec2 },
Handler { key: Key, seen: Option<u64> },
}
impl Scroller {
fn key(self) -> Key {
match self {
Scroller::Container { key, .. } | Scroller::Handler { key, .. } => key,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct DragFollow {
pub point: Vec2,
pub scroller: Option<Scroller>,
pub stepped: bool,
}
impl Core {
pub(crate) fn arm_follow(&mut self, node: Key, point: Vec2) {
let scroller = self.scroller_of(node);
self.drag_follow = Some(DragFollow {
point,
scroller,
stepped: false,
});
}
pub(crate) fn follow_point(&mut self, point: Vec2) {
let Some(mut f) = self.drag_follow else {
return;
};
f.point = point;
f.scroller = f.scroller.map(|s| self.reading_of(s.key(), s));
self.drag_follow = Some(f);
}
pub(crate) fn end_follow(&mut self) {
if let Some(f) = self.drag_follow.take() {
self.rehit(f.point);
}
}
pub(crate) fn autoscrolling(&self) -> bool {
self.drag_follow.is_some_and(|f| f.stepped)
}
pub(crate) fn autoscroller(&self) -> Option<Key> {
self.drag_follow
.filter(|f| f.stepped)
.and_then(|f| f.scroller)
.map(Scroller::key)
}
fn scroller_of(&self, node: Key) -> Option<Scroller> {
if self.building {
return None;
}
let mut i = self.tree.index_of(node)?;
loop {
let key = self.tree.keys[i];
let spec = &self.tree.specs[i];
if spec.events().on_scroll.is_some() {
return Some(self.reading_of(key, Scroller::Handler { key, seen: None }));
}
if spec.layout.scroll_x || spec.layout.scroll_y {
return Some(self.reading_of(
key,
Scroller::Container {
key,
seen: Vec2::ZERO,
},
));
}
match self.tree.parent[i] {
NIL => return None,
p => i = p as usize,
}
}
}
fn reading_of(&self, key: Key, kind: Scroller) -> Scroller {
match kind {
Scroller::Container { .. } => Scroller::Container {
key,
seen: self.scroll.laid_offset(key).unwrap_or(Vec2::ZERO),
},
Scroller::Handler { .. } => Scroller::Handler {
key,
seen: self.grid_line(key),
},
}
}
fn grid_line(&self, key: Key) -> Option<u64> {
self.cells_id_of_ref(key)
.map(|id| self.cells.origin_line(id, self.building))
}
fn grid_line_height(&mut self, key: Key) -> Option<f32> {
let id = self.cells_id_of_ref(key)?;
let sess = &mut *self.session.state();
let cell = self
.cells
.cell_size(id, self.building, &sess.resources, &mut sess.fonts);
(cell.h > 0.0).then_some(cell.h)
}
pub(crate) fn rehit(&mut self, p: Vec2) {
if let Some((key, _)) = self.edit.dragging {
self.edit_drag_to(key, p);
}
if let Some(drag) = self.select_dragging {
self.extend_select_drag(drag, p);
}
}
pub(crate) fn edit_drag_to(&mut self, key: Key, p: Vec2) {
let origin = self
.interaction
.edit_origin_of(key)
.or_else(|| self.edit.dragging.map(|(_, o)| o));
if let Some(origin) = origin {
let local = Vec2::new(p.x - origin.x, p.y - origin.y);
self.edit_with_fonts(|edit, fs| edit.drag(key, local, fs));
}
}
pub(crate) fn follow_drag(&mut self) {
let now = self.anim.time();
let dt = match (now, self.last_frame_time) {
(Some(n), Some(l)) => (n - l).clamp(0.0, MAX_FRAME),
(Some(_), None) | (None, _) => CLOCKLESS_FRAME,
} as f32;
self.last_frame_time = now;
let Some(mut f) = self.drag_follow else {
return;
};
f.stepped = false;
if let Some(scroller) = f.scroller {
let key = scroller.key();
let now = self.reading_of(key, scroller);
let moved = match scroller {
Scroller::Container { .. } => now != scroller,
Scroller::Handler { seen, .. } => seen.is_none() || now != scroller,
};
if moved {
self.rehit(f.point);
f.scroller = Some(now);
}
if let Some(i) = self.tree.index_of(key) {
let rect = Rect::from_pos_size(self.tree.pos[i], self.tree.size[i]);
f.stepped = match scroller {
Scroller::Container { .. } => {
let spec = self.tree.specs[i].layout;
let delta = edge_step(rect, f.point, spec.scroll_x, spec.scroll_y, dt);
let moves = self.scroll.geometry(key).is_some_and(|g| {
let can =
|o: f32, d: f32, m: f32| (o + d).clamp(0.0, m) != o.clamp(0.0, m);
can(g.offset.x, delta.x, g.max_offset.x)
|| can(g.offset.y, delta.y, g.max_offset.y)
});
if moves {
self.scroll.scroll_by(key, delta);
}
moves
}
Scroller::Handler { .. } => {
let grid = self.grid_line(key).is_some();
let delta = edge_step(rect, f.point, !grid, true, dt);
if delta != Vec2::ZERO {
let wheel = Vec2::new(-delta.x, -delta.y);
if let Some(ev) = self.scroll_event(key, f.point, wheel) {
self.pending.push(ev);
}
true
} else {
false
}
}
};
}
}
self.drag_follow = Some(f);
}
pub(crate) fn scroll_event(&mut self, key: Key, p: Vec2, delta: Vec2) -> Option<UiEvent> {
let i = self.tree.index_of(key)?;
self.tree.specs[i].events().on_scroll.as_ref()?;
let lines = match self.grid_line_height(key) {
Some(h) => {
let carry = match self.line_carry {
Some((k, c)) if k == key => c,
_ => 0.0,
};
let total = carry - delta.y / h;
let whole = total.trunc();
self.line_carry = Some((key, total - whole));
Value::Int(whole as i64)
}
None => Value::Null,
};
let origin = self.tree.origins[i];
let tag = self.tree.specs[i].events().on_scroll.as_ref();
let payload = Value::map([
("kind", Value::str("scroll")),
("x", Value::Float(p.x as f64)),
("y", Value::Float(p.y as f64)),
("dx", Value::Float(delta.x as f64)),
("dy", Value::Float(delta.y as f64)),
("lines", lines),
]);
Some(UiEvent::on(origin, key, payload).tagged(tag))
}
}
pub(crate) fn edge_step(rect: Rect, p: Vec2, x: bool, y: bool, dt: f32) -> Vec2 {
let past = |lo: f32, hi: f32, v: f32| -> f32 {
if v < lo {
-(lo - v).min(AUTOSCROLL_REACH)
} else if v > hi {
(v - hi).min(AUTOSCROLL_REACH)
} else {
0.0
}
};
let k = AUTOSCROLL_RATE * dt;
Vec2::new(
if x {
past(rect.x, rect.x + rect.w, p.x) * k
} else {
0.0
},
if y {
past(rect.y, rect.y + rect.h, p.y) * k
} else {
0.0
},
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_pointer_inside_steps_nothing() {
let r = Rect::new(10.0, 10.0, 100.0, 50.0);
assert_eq!(
edge_step(r, Vec2::new(50.0, 30.0), true, true, 1.0 / 60.0),
Vec2::ZERO
);
}
#[test]
fn the_step_is_proportional_capped_and_per_frame() {
let r = Rect::new(0.0, 0.0, 100.0, 100.0);
let s = edge_step(r, Vec2::new(50.0, 160.0), false, true, 1.0 / 60.0);
assert!((s.y - 10.0).abs() < 1e-4, "{s:?}");
assert_eq!(s.x, 0.0);
let s = edge_step(r, Vec2::new(50.0, -300.0), false, true, 1.0 / 60.0);
assert!((s.y + AUTOSCROLL_REACH / 6.0).abs() < 1e-3, "{s:?}");
let s = edge_step(r, Vec2::new(160.0, 50.0), false, true, 1.0 / 60.0);
assert_eq!(s, Vec2::ZERO);
let s = edge_step(r, Vec2::new(160.0, 50.0), true, true, 1.0 / 60.0);
assert!((s.x - 10.0).abs() < 1e-4, "{s:?}");
}
}