diff --git a/plugins/diopser/src/editor.rs b/plugins/diopser/src/editor.rs
index af49092a..b6647bda 100644
--- a/plugins/diopser/src/editor.rs
+++ b/plugins/diopser/src/editor.rs
@@ -26,6 +26,7 @@ use self::button::SafeModeButton;
use crate::{Diopser, DiopserParams};
mod button;
+mod xy_pad;
const EDITOR_WIDTH: u32 = 600;
const EDITOR_HEIGHT: u32 = 490;
@@ -140,12 +141,14 @@ fn spectrum_analyzer(cx: &mut Context) {
ZStack::new(cx, |cx| {
Label::new(cx, "When I grow up, I want to be a spectrum analyzer!");
- Label::new(
+ xy_pad::XyPad::new(
cx,
- "When I close my eyes sometimes I\npretend to be an X-Y pad.",
+ Data::params,
+ |params| ¶ms.filter_frequency,
+ |params| ¶ms.filter_resonance,
)
- .font_size(25.0)
- .rotate(17.0f32);
+ .width(Percentage(100.0))
+ .height(Percentage(100.0));
})
.child_space(Stretch(1.0))
.width(Percentage(100.0))
diff --git a/plugins/diopser/src/editor/theme.css b/plugins/diopser/src/editor/theme.css
index 25a4ab06..390217c6 100644
--- a/plugins/diopser/src/editor/theme.css
+++ b/plugins/diopser/src/editor/theme.css
@@ -31,3 +31,7 @@ param-button.safe-mode:checked {
/* Can't use width: auto here, but we'll try to roughly match the padding of the top bar buttons */
width: 170px;
}
+
+xy-pad {
+ overflow: hidden;
+}
diff --git a/plugins/diopser/src/editor/xy_pad.rs b/plugins/diopser/src/editor/xy_pad.rs
new file mode 100644
index 00000000..49b03560
--- /dev/null
+++ b/plugins/diopser/src/editor/xy_pad.rs
@@ -0,0 +1,116 @@
+// Diopser: a phase rotation plugin
+// Copyright (C) 2021-2022 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use nih_plug::prelude::Param;
+use nih_plug_vizia::vizia::prelude::*;
+use nih_plug_vizia::widgets::param_base::ParamWidgetBase;
+
+// TODO: Vizia doesn't let you do this -50% translation programmatically yet, so this is hardcoded
+// for now
+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.
+pub struct XyPad {
+ x_param_base: ParamWidgetBase,
+ y_param_base: ParamWidgetBase,
+}
+
+/// The [`XyPad`]'s handle. This is a separate eleemnt to allow easier positioning.
+struct XyPadHandle;
+
+impl XyPad {
+ /// Creates a new [`XyPad`] for the given parameter. See
+ /// [`ParamSlider`][nih_plug_vizia::widgets::ParamSlider] for more information on this
+ /// function's arguments.
+ pub fn new(
+ cx: &mut Context,
+ params: L,
+ params_to_x_param: FMap1,
+ params_to_y_param: FMap2,
+ ) -> Handle
+ where
+ L: Lens + Clone,
+ Params: 'static,
+ P1: Param + 'static,
+ P2: Param + 'static,
+ FMap1: Fn(&Params) -> &P1 + Copy + 'static,
+ FMap2: Fn(&Params) -> &P2 + Copy + 'static,
+ {
+ 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),
+ }
+ .build(
+ cx,
+ // We need to create lenses for both the x-parameter's values and the y-parameter's
+ // values
+ ParamWidgetBase::build_view(
+ params.clone(),
+ params_to_x_param,
+ move |cx, x_param_data| {
+ ParamWidgetBase::view(
+ cx,
+ params,
+ params_to_y_param,
+ move |cx, y_param_data| {
+ let x_position_lens = x_param_data.make_lens(|param| {
+ Percentage(param.unmodulated_normalized_value() * 100.0)
+ });
+ let y_position_lens = y_param_data.make_lens(|param| {
+ // NOTE: The y-axis increments downards, and we want high values at
+ // the top and low values at the bottom
+ Percentage((1.0 - param.unmodulated_normalized_value()) * 100.0)
+ });
+
+ XyPadHandle::new(cx)
+ .top(y_position_lens)
+ .left(x_position_lens)
+ // TODO: It would be much nicer if this could be set in the
+ // stylesheet, but Vizia doesn't support that right now
+ .translate((-(HANDLE_WIDTH_PX / 2.0), -(HANDLE_WIDTH_PX / 2.0)))
+ .width(Pixels(HANDLE_WIDTH_PX))
+ .height(Pixels(HANDLE_WIDTH_PX));
+ },
+ );
+ },
+ ),
+ )
+ }
+}
+
+impl XyPadHandle {
+ fn new(cx: &mut Context) -> Handle {
+ // This doesn't have or need any special behavior, it's just a marker element used for
+ // positioning he handle
+ Self.build(cx, |_| ())
+ }
+}
+
+impl View for XyPad {
+ fn element(&self) -> Option<&'static str> {
+ Some("xy-pad")
+ }
+}
+
+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
+}