cranpose-liquid 0.9.39

Glass controls, themes and motion for Cranpose apps
Documentation
use std::{cell::Cell, rc::Rc};

use cranpose_foundation::{PointerEvent, PointerEventKind};
use cranpose_ui::PointerInputScope;

use super::{
    control_motion::ControlContactMotion,
    slider_motion::{SliderDrag, SliderFling},
};
use crate::motion::{LiquidDragAxis, LiquidMotion};

#[derive(Clone)]
pub(super) struct SliderGesture {
    pub(super) active: Rc<Cell<Option<SliderDrag>>>,
    pub(super) axis: Rc<LiquidDragAxis>,
    pub(super) contact: Rc<ControlContactMotion>,
    pub(super) fling: Rc<SliderFling>,
    pub(super) on_change: Rc<dyn Fn(f32)>,
    pub(super) value: f32,
    pub(super) width: f32,
    pub(super) thumb_width: f32,
}

impl SliderGesture {
    pub(super) async fn run(self, scope: PointerInputScope) {
        scope
            .await_pointer_event_scope(|events| async move {
                loop {
                    self.handle(events.await_pointer_event().await);
                }
            })
            .await;
    }

    fn handle(&self, event: PointerEvent) {
        let usable = (self.width - self.thumb_width).max(1.0);
        let input_time = event.time_ms.or_else(|| {
            event
                .animation_time_nanos
                .map(|time| (time / 1_000_000) as i64)
        });
        if event.kind == PointerEventKind::Down && self.active.get().is_none() {
            let center = usable * self.value + self.thumb_width * 0.5;
            if (event.position.x - center).abs() <= self.thumb_width * 0.5 {
                self.fling.cancel();
                self.active.set(Some(SliderDrag::new(
                    event.id,
                    event.position.x,
                    self.value,
                    input_time,
                )));
                self.axis.begin(usable * self.value, event.time_ms);
                self.contact.pressed(true, event.animation_time_nanos);
                event.consume();
            }
            return;
        }
        let Some(mut drag) = self.active.get().filter(|drag| drag.id == event.id) else {
            return;
        };
        match event.kind {
            PointerEventKind::Move | PointerEventKind::Up => {
                let previous = drag.value;
                if event.kind == PointerEventKind::Move {
                    drag.moved(event.position.x, input_time);
                }
                let fraction = drag.fraction(event.position.x, self.width);
                if event.kind == PointerEventKind::Move {
                    self.active.set(Some(drag));
                    self.axis.move_to(usable * fraction, event.time_ms);
                } else {
                    self.fling.release(
                        fraction,
                        drag.release_velocity(),
                        self.width,
                        event.animation_time_nanos,
                    );
                    self.axis.finish_at(usable * fraction, event.time_ms);
                    self.finish(&event);
                }
                if fraction != previous {
                    (self.on_change)(fraction);
                }
                event.consume();
            }
            PointerEventKind::Cancel => {
                self.fling.cancel();
                self.axis
                    .release_to(usable * self.value, event.time_ms, LiquidMotion::snappy());
                self.finish(&event);
                event.consume();
            }
            _ => {}
        }
    }

    fn finish(&self, event: &PointerEvent) {
        self.active.set(None);
        self.contact.pressed(false, event.animation_time_nanos);
    }
}