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 +}