diff --git a/src/cushy/skeleton_canvas.rs b/src/cushy/skeleton_canvas.rs index 84cac6e..6169c0a 100644 --- a/src/cushy/skeleton_canvas.rs +++ b/src/cushy/skeleton_canvas.rs @@ -3,18 +3,21 @@ use core::f32; use crate::{Angle, BoneEnd, BoneId, Coordinate, JointId, Skeleton, Vector}; use cushy::{ - context::{EventContext, GraphicsContext, LayoutContext}, + context::{EventContext, GraphicsContext, LayoutContext, Trackable}, figures::{ units::{Lp, Px, UPx}, FloatConversion, IntoComponents, Point, Rect, Round, ScreenScale, Size, }, kludgine::{ - app::winit::{event::MouseButton, window::CursorIcon}, + app::winit::{ + event::{MouseButton, MouseScrollDelta, TouchPhase}, + window::CursorIcon, + }, shapes::{PathBuilder, Shape, StrokeOptions}, DrawableExt, Origin, }, styles::Color, - value::{Dynamic, DynamicRead}, + value::{Destination, Dynamic, DynamicRead, Source}, widget::{Callback, EventHandling, Widget, HANDLED, IGNORED}, window::DeviceId, ConstraintLimit, @@ -24,9 +27,10 @@ use cushy::{ pub struct SkeletonCanvas { skeleton: Dynamic, hovering: Option, - scale: f32, + scale: Dynamic, handle_size: f32, - maximum_scale: f32, + maximum_scale: Dynamic, + minimum_scale: Dynamic, offset: Point, drag: Option, on_mutate: Option>, @@ -35,18 +39,36 @@ pub struct SkeletonCanvas { impl SkeletonCanvas { #[must_use] pub fn new(skeleton: Dynamic) -> Self { + let maximum_scale = Dynamic::new(0.); + let minimum_scale = maximum_scale.map_each_cloned(|s| s / 100.); Self { skeleton, hovering: None, handle_size: 0.1, - scale: f32::MAX, - maximum_scale: 0., + scale: Dynamic::new(f32::MAX), + maximum_scale, + minimum_scale, offset: Point::default(), drag: None, on_mutate: None, } } + #[must_use] + pub fn maximum_scale(&self) -> &Dynamic { + &self.maximum_scale + } + + #[must_use] + pub fn minimum_scale(&self) -> &Dynamic { + &self.minimum_scale + } + + #[must_use] + pub fn scale(&self) -> &Dynamic { + &self.scale + } + #[must_use] pub fn on_mutate(mut self, on_mutate: F) -> Self where @@ -57,14 +79,17 @@ impl SkeletonCanvas { } fn coordinate_to_point(&self, vector: Coordinate) -> Point { - (vector * self.scale).to_vec::>().map(Px::from) + self.offset + (vector * self.scale.get()) + .to_vec::>() + .map(Px::from) + + self.offset } fn point_to_coordinate(&self, position: Point) -> Coordinate { (position - self.offset) .map(FloatConversion::into_float) .to_vec::() - / self.scale + / self.scale.get() } } @@ -103,18 +128,25 @@ impl Widget for SkeletonCanvas { return; } - self.maximum_scale = if zero_height || width_ratio < height_ratio { + let maximum_scale = if zero_height || width_ratio < height_ratio { width_ratio } else { height_ratio }; - if self.scale > self.maximum_scale { - self.scale = self.maximum_scale; - } + self.maximum_scale.set(maximum_scale); + self.scale.redraw_when_changed(context); + let scale = { + let mut scale = self.scale.lock(); + if *scale > maximum_scale { + *scale = maximum_scale; + scale.prevent_notifications(); + } + *scale + }; let handle_size = Lp::mm(2).into_px(context.gfx.scale()).ceil(); - self.handle_size = handle_size.into_float() / self.scale; + self.handle_size = handle_size.into_float() / scale; - let root = root_start * self.scale; + let root = root_start * scale; self.offset = (middle - root).to_vec::>().map(Px::from).floor(); @@ -329,6 +361,29 @@ impl Widget for SkeletonCanvas { self.drag = None; } + #[allow(clippy::cast_possible_truncation)] + fn mouse_wheel( + &mut self, + _device_id: DeviceId, + delta: MouseScrollDelta, + _phase: TouchPhase, + _context: &mut EventContext<'_>, + ) -> EventHandling { + let maximum_scale = self.maximum_scale.get(); + let minimum_scale = self.minimum_scale.get(); + let mut scale = self.scale.lock(); + let delta = match delta { + MouseScrollDelta::LineDelta(_, y_lines) => y_lines, + MouseScrollDelta::PixelDelta(pt) => pt.y as f32 / 12., + }; + + *scale = (*scale + *scale * delta / 10.) + .min(maximum_scale) + .max(minimum_scale); + + HANDLED + } + fn hit_test(&mut self, _location: Point, _context: &mut EventContext<'_>) -> bool { true } diff --git a/src/funnybones.rs b/src/funnybones.rs index 6050b1a..98c4d48 100644 --- a/src/funnybones.rs +++ b/src/funnybones.rs @@ -88,6 +88,24 @@ fn main() -> cushy::Result { let bones_editor = skeleton_editor(&editing_skeleton, &watcher).make_widget(); let mode = Dynamic::::default(); + let canvas = SkeletonCanvas::new(skeleton).on_mutate({ + move |mutation| match mutation { + SkeletonMutation::SetDesiredEnd { bone, end } => editing_skeleton + .find_bone(bone) + .expect("missing bone") + .desired_end + .set(Some(end)), + SkeletonMutation::SetJointRotation { joint, rotation } => editing_skeleton + .find_joint(joint) + .expect("missing joint") + .joint_angle + .set(rotation), + } + }); + let zoom = canvas + .scale() + .clone() + .slider_between(canvas.minimum_scale(), canvas.maximum_scale()); [(Mode::Bones, "Bones"), (Mode::Animation, "Animation")] .into_iter() @@ -104,24 +122,7 @@ fn main() -> cushy::Result { ) .into_rows() .expand() - .and( - SkeletonCanvas::new(skeleton) - .on_mutate({ - move |mutation| match mutation { - SkeletonMutation::SetDesiredEnd { bone, end } => editing_skeleton - .find_bone(bone) - .expect("missing bone") - .desired_end - .set(Some(end)), - SkeletonMutation::SetJointRotation { joint, rotation } => editing_skeleton - .find_joint(joint) - .expect("missing joint") - .joint_angle - .set(rotation), - } - }) - .expand(), - ) + .and(canvas.expand().and(zoom).into_rows().expand()) .into_columns() .run() }