1
0
Fork 0

Implement dragging for the parameter slider

This commit is contained in:
Robbert van der Helm 2022-03-14 14:16:47 +01:00
parent df4b56d818
commit ee9e3701f3
5 changed files with 106 additions and 17 deletions

2
Cargo.lock generated
View file

@ -1396,7 +1396,7 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "iced_baseview"
version = "0.0.3"
source = "git+https://github.com/robbert-vdh/iced_baseview.git?branch=feature/update-baseview#b32cc3f2a4dafee4e67b3e482b3794bb16f98752"
source = "git+https://github.com/robbert-vdh/iced_baseview.git?branch=feature/update-baseview#5d09ba42d28923d144c8b7339a9f3f5e1252b37b"
dependencies = [
"baseview",
"copypasta",

View file

@ -7,7 +7,7 @@
use nih_plug::param::internals::ParamPtr;
mod param_slider;
pub mod param_slider;
pub mod util;
pub use param_slider::ParamSlider;

View file

@ -1,8 +1,8 @@
//! A slider that integrates with NIH-plug's [`Param`] types.
use crate::{
alignment, backend, event, layout, mouse, renderer, text, Clipboard, Color, Element, Event,
Layout, Length, Point, Rectangle, Shell, Size, Widget,
alignment, backend, event, layout, mouse, renderer, text, touch, Clipboard, Color, Element,
Event, Layout, Length, Point, Rectangle, Shell, Size, Widget,
};
use nih_plug::prelude::{GuiContext, Param, ParamSetter};
@ -12,7 +12,13 @@ use super::ParamMessage;
/// A slider that integrates with NIH-plug's [`Param`] types.
///
/// TODO: There are currently no styling options at all
/// TODO: Handle Shift+drag for granular drag
/// TODO: Handle Ctrl+click for reset
/// TODO: Handle Double click for reset
/// TODO: Handle Alt+click for text entry
pub struct ParamSlider<'a, P: Param, Renderer: text::Renderer> {
state: &'a mut State,
param: &'a P,
/// We'll visualize the parameter's current value by drawing the difference between the current
/// normalized value and the default normalized value.
@ -24,12 +30,20 @@ pub struct ParamSlider<'a, P: Param, Renderer: text::Renderer> {
font: Renderer::Font,
}
/// State for a [`ParamSlider`].
#[derive(Debug, Default)]
pub struct State {
drag_active: bool,
}
impl<'a, P: Param, Renderer: text::Renderer> ParamSlider<'a, P, Renderer> {
/// Creates a new [`ParamSlider`] for the given parameter.
pub fn new(param: &'a P, context: &'a dyn GuiContext) -> Self {
pub fn new(state: &'a mut State, param: &'a P, context: &'a dyn GuiContext) -> Self {
let setter = ParamSetter::new(context);
Self {
state,
param,
setter,
@ -85,14 +99,54 @@ impl<'a, P: Param, Renderer: text::Renderer> Widget<ParamMessage, Renderer>
fn on_event(
&mut self,
_event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, ParamMessage>,
shell: &mut Shell<'_, ParamMessage>,
) -> event::Status {
// TODO: Handle interaction
let bounds = layout.bounds();
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if bounds.contains(cursor_position) {
shell.publish(ParamMessage::BeginSetParameter(self.param.as_ptr()));
self.state.drag_active = true;
// Immediately trigger a parameter update if the value would be different
self.set_normalized_value(
shell,
util::remap_rect_x_coordinate(&bounds, cursor_position.x),
);
return event::Status::Captured;
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => {
if bounds.contains(cursor_position) {
shell.publish(ParamMessage::EndSetParameter(self.param.as_ptr()));
self.state.drag_active = false;
return event::Status::Captured;
}
}
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if self.state.drag_active && bounds.contains(cursor_position) {
self.set_normalized_value(
shell,
util::remap_rect_x_coordinate(&bounds, cursor_position.x),
);
return event::Status::Captured;
}
}
_ => {}
}
event::Status::Ignored
}
@ -145,11 +199,11 @@ impl<'a, P: Param, Renderer: text::Renderer> Widget<ParamMessage, Renderer>
// We'll visualize the difference between the current value and the default value
let current_value = self.param.normalized_value();
let fill_start_x = util::remap_rect_x(
let fill_start_x = util::remap_rect_x_t(
&bounds,
self.setter.default_normalized_param_value(self.param),
);
let fill_end_x = util::remap_rect_x(&bounds, current_value);
let fill_end_x = util::remap_rect_x_t(&bounds, current_value);
let fill_color = Color::from_rgb8(196, 196, 196);
let fill_rect = Rectangle {
@ -204,6 +258,26 @@ impl<'a, P: Param, Renderer: text::Renderer> Widget<ParamMessage, Renderer>
}
}
impl<'a, P: Param, Renderer: text::Renderer> ParamSlider<'a, P, Renderer> {
/// Set the normalized value for a parameter if that would change the parameter's plain value
/// (to avoid unnecessary duplicate parameter changes). The begin- and end set parameter
/// messages need to be sent before calling this function.
fn set_normalized_value(&self, shell: &mut Shell<'_, ParamMessage>, normalized_value: f32) {
// This snaps to the nearest plain value if the parameter is stepped in some way.
// TODO: As an optimization, we could add a `const CONTINUOUS: bool` to the parameter to
// avoid this normalized->plain->normalized conversion for parameters that don't need
// it
let plain_value = self.param.preview_plain(normalized_value);
let current_plain_value = self.param.plain_value();
if plain_value != current_plain_value {
shell.publish(ParamMessage::SetParameterNormalized(
self.param.as_ptr(),
normalized_value,
));
}
}
}
impl<'a, P: Param> ParamSlider<'a, P, backend::Renderer> {
/// Convert this [`ParamSlider`] into an [`Element`] with the correct message. You should have a
/// variant on your own message type that wraps around [`ParamMessage`] so you can forward those

View file

@ -4,12 +4,24 @@ use crate::Rectangle;
/// Remap a `[0, 1]` value to an x-coordinate within this rectangle. The value will be clamped to
/// `[0, 1]` if it isn't already in that range.
pub fn remap_rect_x(rect: &Rectangle, t: f32) -> f32 {
pub fn remap_rect_x_t(rect: &Rectangle, t: f32) -> f32 {
rect.x + (rect.width * t.clamp(0.0, 1.0))
}
/// Remap a `[0, 1]` value to a y-coordinate within this rectangle. The value will be clamped to
/// `[0, 1]` if it isn't already in that range.
pub fn remap_rect_y(rect: &Rectangle, t: f32) -> f32 {
pub fn remap_rect_y_t(rect: &Rectangle, t: f32) -> f32 {
rect.y + (rect.height * t.clamp(0.0, 1.0))
}
/// Remap an x-coordinate to a `[0, 1]` value within this rectangle. The value will be clamped to
/// `[0, 1]` if it isn't already in that range.
pub fn remap_rect_x_coordinate(rect: &Rectangle, x_coord: f32) -> f32 {
((x_coord - rect.x) / rect.width).clamp(0.0, 1.0)
}
/// Remap a y-coordinate to a `[0, 1]` value within this rectangle. The value will be clamped to
/// `[0, 1]` if it isn't already in that range.
pub fn remap_rect_y_coordinate(rect: &Rectangle, y_coord: f32) -> f32 {
((y_coord - rect.y) / rect.height).clamp(0.0, 1.0)
}

View file

@ -1,5 +1,5 @@
use nih_plug::prelude::{Editor, GuiContext};
use nih_plug_iced::widgets::{ParamMessage, ParamSlider};
use nih_plug_iced::widgets as nih_widgets;
use nih_plug_iced::*;
use std::pin::Pin;
use std::sync::Arc;
@ -22,13 +22,14 @@ struct GainEditor {
params: Pin<Arc<GainParams>>,
context: Arc<dyn GuiContext>,
gain_slider_state: nih_widgets::param_slider::State,
meter_dummy_state: widget::button::State,
}
#[derive(Debug, Clone, Copy)]
enum Message {
/// Update a parameter's value.
ParamUpdate(ParamMessage),
ParamUpdate(nih_widgets::ParamMessage),
}
impl IcedEditor for GainEditor {
@ -43,6 +44,8 @@ impl IcedEditor for GainEditor {
let editor = GainEditor {
params,
context,
gain_slider_state: Default::default(),
meter_dummy_state: widget::button::State::new(),
};
@ -84,7 +87,7 @@ impl IcedEditor for GainEditor {
.vertical_alignment(alignment::Vertical::Center),
)
.push(
ParamSlider::new(&self.params.gain, self.context.as_ref()).map(Message::ParamUpdate)
nih_widgets::ParamSlider::new(&mut self.gain_slider_state, &self.params.gain, self.context.as_ref()).map(Message::ParamUpdate)
// Button::new(&mut self.gain_dummy_state, Text::new("Gain"))
// .height(30.into())
// .width(180.into()),