use waterui::gesture::{DragEvent, DragGesture, GesturePhase};
use waterui::layout::frame::Frame;
use waterui::reactive::{SignalExt as _, binding};
use waterui::shape::{Capsule, ShapeExt as _};
use waterui::{Environment, Str, View, ViewExt as _};
use waterui_core::handler::{Handler, boxed_action};
use crate::color::{OnSurface, Outline};
use crate::semantics::conditional_color;
const WIDTH: f32 = 4.0;
const HEIGHT: f32 = 48.0;
const PRESSED_WIDTH: f32 = 12.0;
const PRESSED_HEIGHT: f32 = 52.0;
const CONTAINER_WIDTH: f32 = 24.0;
#[derive(Debug)]
pub struct VerticalDragHandle<Action = fn(&Environment)> {
accessibility_label: Str,
on_drag: Action,
}
impl VerticalDragHandle {
#[must_use]
pub fn new(accessibility_label: impl Into<Str>) -> Self {
Self {
accessibility_label: accessibility_label.into(),
on_drag: noop,
}
}
}
impl<Action> VerticalDragHandle<Action> {
#[must_use]
pub fn on_drag<F, Args>(self, action: F) -> VerticalDragHandle<impl FnMut(&Environment)>
where
F: Handler<Args, ()> + 'static,
{
VerticalDragHandle {
accessibility_label: self.accessibility_label,
on_drag: boxed_action(action),
}
}
}
impl<Action> View for VerticalDragHandle<Action>
where
Action: FnMut(&Environment) + 'static,
{
fn body(self, _env: &Environment) -> impl View {
let mut on_drag = self.on_drag;
let held = binding(false);
let held_for_gesture = held.clone();
let width = held.map(|held| if held { PRESSED_WIDTH } else { WIDTH });
let height = held.map(|held| if held { PRESSED_HEIGHT } else { HEIGHT });
let color = conditional_color(held, OnSurface, Outline);
Frame::new(Capsule.fill(color))
.width(width)
.height(height)
.width(CONTAINER_WIDTH)
.gesture(DragGesture::new(0.0), move |env: Environment| {
let phase = env
.get::<DragEvent>()
.expect("drag handle gesture is missing its DragEvent")
.phase;
held_for_gesture.set(matches!(
phase,
GesturePhase::Started | GesturePhase::Updated
));
on_drag(&env);
})
.a11y_label(self.accessibility_label)
}
}
const fn noop(_env: &Environment) {}
#[must_use]
pub fn vertical_drag_handle(accessibility_label: impl Into<Str>) -> VerticalDragHandle {
VerticalDragHandle::new(accessibility_label)
}
#[cfg(test)]
mod tests {
use super::{CONTAINER_WIDTH, HEIGHT, PRESSED_HEIGHT, PRESSED_WIDTH, WIDTH};
#[test]
fn drag_handle_tokens_match_compose_drag_handle_tokens() {
assert_eq!(WIDTH, 4.0);
assert_eq!(HEIGHT, 48.0);
assert_eq!(PRESSED_WIDTH, 12.0);
assert_eq!(PRESSED_HEIGHT, 52.0);
assert_eq!(CONTAINER_WIDTH, 24.0);
}
#[test]
fn holding_the_handle_thickens_it_within_its_hit_area() {
const {
assert!(PRESSED_WIDTH > WIDTH);
assert!(PRESSED_HEIGHT > HEIGHT);
assert!(
PRESSED_WIDTH <= CONTAINER_WIDTH,
"the bar must stay inside the hit area it grows within"
);
}
}
}