From 27d42797ee82a73de7300855a608fb3437b26266 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 11 Nov 2022 20:21:07 +0100 Subject: [PATCH] Add basic dragging gestures to the X-Y pad --- plugins/diopser/src/editor/xy_pad.rs | 158 ++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 2 deletions(-) diff --git a/plugins/diopser/src/editor/xy_pad.rs b/plugins/diopser/src/editor/xy_pad.rs index 49b03560..798c74d8 100644 --- a/plugins/diopser/src/editor/xy_pad.rs +++ b/plugins/diopser/src/editor/xy_pad.rs @@ -17,6 +17,7 @@ use nih_plug::prelude::Param; use nih_plug_vizia::vizia::prelude::*; use nih_plug_vizia::widgets::param_base::ParamWidgetBase; +use nih_plug_vizia::widgets::util::{self, ModifiersExt}; // TODO: Vizia doesn't let you do this -50% translation programmatically yet, so this is hardcoded // for now @@ -25,9 +26,17 @@ const HANDLE_WIDTH_PX: f32 = 20.0; /// An X-Y pad that controlers two parameters at the same time by binding them to one of the two /// axes. This specific implementation has a tooltip for the X-axis parmaeter and allows /// Alt+clicking to enter a specific value. +// +// TODO: Text entry for the x-parameter +// TODO: Tooltip +// TODO: Granular dragging pub struct XyPad { x_param_base: ParamWidgetBase, y_param_base: ParamWidgetBase, + + /// Will be set to `true` if we're dragging the parameter. Resetting the parameter or entering a + /// text value should not initiate a drag. + drag_active: bool, } /// The [`XyPad`]'s handle. This is a separate eleemnt to allow easier positioning. @@ -54,6 +63,8 @@ impl XyPad { Self { x_param_base: ParamWidgetBase::new(cx, params.clone(), params_to_x_param), y_param_base: ParamWidgetBase::new(cx, params.clone(), params_to_y_param), + + drag_active: false, } .build( cx, @@ -91,6 +102,33 @@ impl XyPad { ), ) } + + /// Should be called at the start of a drag operation. + fn begin_set_parameters(&self, cx: &mut EventContext) { + self.x_param_base.begin_set_parameter(cx); + self.y_param_base.begin_set_parameter(cx); + } + + /// Resets both parameters. `begin_set_parameters()` needs to be called first. + fn reset_parameters(&self, cx: &mut EventContext) { + self.x_param_base + .set_normalized_value(cx, self.x_param_base.default_normalized_value()); + self.y_param_base + .set_normalized_value(cx, self.y_param_base.default_normalized_value()); + } + + /// Set a normalized value for both parameters. `begin_set_parameters()` needs to be called + /// first. + fn set_normalized_values(&self, cx: &mut EventContext, (x_value, y_value): (f32, f32)) { + self.x_param_base.set_normalized_value(cx, x_value); + self.y_param_base.set_normalized_value(cx, y_value); + } + + /// Should be called at the end of a drag operation. + fn end_set_parameters(&self, cx: &mut EventContext) { + self.x_param_base.end_set_parameter(cx); + self.y_param_base.end_set_parameter(cx); + } } impl XyPadHandle { @@ -105,12 +143,128 @@ impl View for XyPad { fn element(&self) -> Option<&'static str> { Some("xy-pad") } + + fn event(&mut self, cx: &mut EventContext, event: &mut Event) { + event.map(|window_event, meta| match window_event { + WindowEvent::MouseDown(MouseButton::Left) + | WindowEvent::MouseTripleClick(MouseButton::Left) => { + // TODO: Alt+click text entry + if cx.modifiers.command() { + // Ctrl+Click and double click should reset the parameter instead of initiating + // a drag operation + self.begin_set_parameters(cx); + self.reset_parameters(cx); + self.end_set_parameters(cx); + } else { + self.drag_active = true; + cx.capture(); + // NOTE: Otherwise we don't get key up events + cx.focus(); + cx.set_active(true); + + // When holding down shift while clicking on a parameter we want to granuarly + // edit the parameter without jumping to a new value + self.begin_set_parameters(cx); + // TODO: Granular dragging + // if cx.modifiers.shift() { + // self.granular_drag_start_x_value = Some(( + // cx.mouse.cursorx, + // self.param_base.unmodulated_normalized_value(), + // )); + // } else { + // self.granular_drag_start_x_value = None; + // self.set_normalized_value_drag( + // cx, + // util::remap_current_entity_x_coordinate(cx, cx.mouse.cursorx), + // ); + // } + } + + meta.consume(); + } + WindowEvent::MouseDoubleClick(MouseButton::Left) => { + // Ctrl+Click and double click should reset the parameters instead of initiating a + // drag operation + self.begin_set_parameters(cx); + self.reset_parameters(cx); + self.end_set_parameters(cx); + + meta.consume(); + } + WindowEvent::MouseUp(MouseButton::Left) => { + if self.drag_active { + self.drag_active = false; + cx.release(); + cx.set_active(false); + + self.end_set_parameters(cx); + + meta.consume(); + } + } + WindowEvent::MouseMove(x, y) => { + if self.drag_active { + // If shift is being held then the drag should be more granular instead of + // absolute + // TODO: Granular dragging + // if cx.modifiers.shift() { + // let (drag_start_x, drag_start_value) = + // *self.granular_drag_start_x_value.get_or_insert_with(|| { + // ( + // cx.mouse.cursorx, + // self.param_base.unmodulated_normalized_value(), + // ) + // }); + + // self.set_normalized_value_drag( + // cx, + // util::remap_current_entity_x_coordinate( + // cx, + // // This can be optimized a bit + // util::remap_current_entity_x_t(cx, drag_start_value) + // + (*x - drag_start_x) * GRANULAR_DRAG_MULTIPLIER, + // ), + // ); + // } else { + // self.granular_drag_start_x_value = None; + + // self.set_normalized_value_drag( + // cx, + // util::remap_current_entity_x_coordinate(cx, *x), + // ); + // } + + self.set_normalized_values( + cx, + ( + util::remap_current_entity_x_coordinate(cx, *x), + // We want the top of the widget to be 1.0 and the bottom to be 0.0, + // this is the opposite of how the y-coordinate works + 1.0 - util::remap_current_entity_y_coordinate(cx, *y), + ), + ); + } + } + // TODO: Granular dragging + // WindowEvent::KeyUp(_, Some(Key::Shift)) => { + // // If this happens while dragging, snap back to reality uh I mean the current screen + // // position + // if self.drag_active && self.granular_drag_start_x_value.is_some() { + // self.granular_drag_start_x_value = None; + // self.param_base.set_normalized_value( + // cx, + // util::remap_current_entity_x_coordinate(cx, cx.mouse.cursorx), + // ); + // } + // } + // TODO: Scrolling, because why not. Could be useful on laptops/with touchpads. + _ => {} + }); + } } impl View for XyPadHandle { fn element(&self) -> Option<&'static str> { Some("xy-pad__handle") } - - // TODO: Add a draw() implementation that draws at a -50% translation }