Add a resize handle widget
This currently only works on Linux, and it causes a random white bar to be added to the bottom of the GUI.
This commit is contained in:
parent
d46169cb18
commit
772c1ecfaf
|
@ -87,3 +87,21 @@ peak-meter .ticks__label {
|
||||||
top: 4px; /* In pixels in an attempt to get this to better align to the grid */
|
top: 4px; /* In pixels in an attempt to get this to better align to the grid */
|
||||||
font-size: 11; /* 14.667px */
|
font-size: 11; /* 14.667px */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resize-handle {
|
||||||
|
bottom: 0;
|
||||||
|
color: #696969;
|
||||||
|
height: 20px;
|
||||||
|
left: 1s;
|
||||||
|
opacity: 0.5;
|
||||||
|
position-type: self-directed;
|
||||||
|
right: 0;
|
||||||
|
top: 1s;
|
||||||
|
transition: opacity 0.1 0;
|
||||||
|
width: 20px;
|
||||||
|
z-index: 1337;
|
||||||
|
}
|
||||||
|
resize-handle:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: opacity 0.1 0;
|
||||||
|
}
|
||||||
|
|
|
@ -8,18 +8,20 @@
|
||||||
use nih_plug::prelude::{GuiContext, Param, ParamPtr};
|
use nih_plug::prelude::{GuiContext, Param, ParamPtr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use vizia::{Context, Model, WindowEvent};
|
use vizia::{Context, Lens, Model, WindowEvent};
|
||||||
|
|
||||||
use super::ViziaState;
|
use super::ViziaState;
|
||||||
|
|
||||||
mod generic_ui;
|
mod generic_ui;
|
||||||
mod param_slider;
|
mod param_slider;
|
||||||
mod peak_meter;
|
mod peak_meter;
|
||||||
|
mod resize_handle;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
pub use generic_ui::GenericUi;
|
pub use generic_ui::GenericUi;
|
||||||
pub use param_slider::{ParamSlider, ParamSliderExt, ParamSliderStyle};
|
pub use param_slider::{ParamSlider, ParamSliderExt, ParamSliderStyle};
|
||||||
pub use peak_meter::PeakMeter;
|
pub use peak_meter::PeakMeter;
|
||||||
|
pub use resize_handle::ResizeHandle;
|
||||||
|
|
||||||
/// Register the default theme for the widgets exported by this module. This is automatically called
|
/// Register the default theme for the widgets exported by this module. This is automatically called
|
||||||
/// for you when using [`create_vizia_editor()`][super::create_vizia_editor()].
|
/// for you when using [`create_vizia_editor()`][super::create_vizia_editor()].
|
||||||
|
@ -67,6 +69,7 @@ pub(crate) struct ParamModel {
|
||||||
|
|
||||||
/// Handles interactions through `WindowEvent` for VIZIA GUIs by updating the `ViziaState`.
|
/// Handles interactions through `WindowEvent` for VIZIA GUIs by updating the `ViziaState`.
|
||||||
/// Registered in [`ViziaEditor::spawn()`][super::ViziaEditor::spawn()].
|
/// Registered in [`ViziaEditor::spawn()`][super::ViziaEditor::spawn()].
|
||||||
|
#[derive(Lens)]
|
||||||
pub(crate) struct WindowModel {
|
pub(crate) struct WindowModel {
|
||||||
pub context: Arc<dyn GuiContext>,
|
pub context: Arc<dyn GuiContext>,
|
||||||
pub vizia_state: Arc<ViziaState>,
|
pub vizia_state: Arc<ViziaState>,
|
||||||
|
|
|
@ -74,7 +74,7 @@ impl GenericUi {
|
||||||
cx: &mut Context,
|
cx: &mut Context,
|
||||||
params: L,
|
params: L,
|
||||||
mut make_widget: impl FnMut(&mut Context, ParamPtr),
|
mut make_widget: impl FnMut(&mut Context, ParamPtr),
|
||||||
) -> Handle<'_, GenericUi>
|
) -> Handle<Self>
|
||||||
where
|
where
|
||||||
L: Lens<Target = Pin<PsPtr>> + Copy,
|
L: Lens<Target = Pin<PsPtr>> + Copy,
|
||||||
PsPtr: 'static + Deref<Target = Ps>,
|
PsPtr: 'static + Deref<Target = Ps>,
|
||||||
|
|
|
@ -100,11 +100,7 @@ impl ParamSlider {
|
||||||
/// the VIZIA wrapper.
|
/// the VIZIA wrapper.
|
||||||
///
|
///
|
||||||
/// See [`ParamSliderExt`] for additonal options.
|
/// See [`ParamSliderExt`] for additonal options.
|
||||||
pub fn new<L, Params, P, F>(
|
pub fn new<L, Params, P, F>(cx: &mut Context, params: L, params_to_param: F) -> Handle<Self>
|
||||||
cx: &mut Context,
|
|
||||||
params: L,
|
|
||||||
params_to_param: F,
|
|
||||||
) -> Handle<'_, ParamSlider>
|
|
||||||
where
|
where
|
||||||
L: Lens<Target = Params> + Copy,
|
L: Lens<Target = Params> + Copy,
|
||||||
F: 'static + Fn(&Params) -> &P + Copy,
|
F: 'static + Fn(&Params) -> &P + Copy,
|
||||||
|
|
|
@ -39,11 +39,7 @@ where
|
||||||
impl PeakMeter {
|
impl PeakMeter {
|
||||||
/// Creates a new [`PeakMeter`] for the given value in decibel, optionally holding the peak
|
/// Creates a new [`PeakMeter`] for the given value in decibel, optionally holding the peak
|
||||||
/// value for a certain amount of time.
|
/// value for a certain amount of time.
|
||||||
pub fn new<L>(
|
pub fn new<L>(cx: &mut Context, level_dbfs: L, hold_time: Option<Duration>) -> Handle<Self>
|
||||||
cx: &mut Context,
|
|
||||||
level_dbfs: L,
|
|
||||||
hold_time: Option<Duration>,
|
|
||||||
) -> Handle<'_, PeakMeter>
|
|
||||||
where
|
where
|
||||||
L: Lens<Target = f32>,
|
L: Lens<Target = f32>,
|
||||||
{
|
{
|
||||||
|
|
193
nih_plug_vizia/src/widgets/resize_handle.rs
Normal file
193
nih_plug_vizia/src/widgets/resize_handle.rs
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
//! A resize handle for uniformly scaling a plugin GUI.
|
||||||
|
|
||||||
|
use femtovg::{Paint, Path};
|
||||||
|
use vizia::*;
|
||||||
|
|
||||||
|
use super::WindowModel;
|
||||||
|
|
||||||
|
/// A resize handle placed at the bottom right of the window that lets you resize the window.
|
||||||
|
pub struct ResizeHandle {
|
||||||
|
/// 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 scale factor when we started dragging. This is kept track of separately to avoid
|
||||||
|
/// accumulating rounding errors.
|
||||||
|
start_scale_factor: f64,
|
||||||
|
/// The cursor position in physical screen pixels when the drag started.
|
||||||
|
start_physical_coordinates: (f32, f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResizeHandle {
|
||||||
|
/// Create a resize handle at the bottom right of the window. This should be created at the top
|
||||||
|
/// level. Dragging this handle around will cause the window to be resized.
|
||||||
|
pub fn new(cx: &mut Context) -> Handle<Self> {
|
||||||
|
// Styling is done in the style sheet
|
||||||
|
ResizeHandle {
|
||||||
|
drag_active: false,
|
||||||
|
start_scale_factor: 1.0,
|
||||||
|
start_physical_coordinates: (0.0, 0.0),
|
||||||
|
}
|
||||||
|
.build(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for ResizeHandle {
|
||||||
|
fn element(&self) -> Option<String> {
|
||||||
|
Some(String::from("resize-handle"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, cx: &mut Context, event: &mut Event) {
|
||||||
|
if let Some(window_event) = event.message.downcast() {
|
||||||
|
match *window_event {
|
||||||
|
WindowEvent::MouseDown(MouseButton::Left) => {
|
||||||
|
cx.capture();
|
||||||
|
cx.current.set_active(cx, true);
|
||||||
|
|
||||||
|
let vizia_state = WindowModel::vizia_state.get(cx);
|
||||||
|
self.drag_active = true;
|
||||||
|
self.start_scale_factor = vizia_state.user_scale_factor();
|
||||||
|
self.start_physical_coordinates = (
|
||||||
|
cx.mouse.cursorx * cx.style.dpi_factor as f32,
|
||||||
|
cx.mouse.cursory * cx.style.dpi_factor as f32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
WindowEvent::MouseUp(MouseButton::Left) => {
|
||||||
|
if self.drag_active {
|
||||||
|
cx.release();
|
||||||
|
cx.current.set_active(cx, false);
|
||||||
|
|
||||||
|
self.drag_active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::MouseMove(x, y) => {
|
||||||
|
// TODO: Filter the hover color and dragging to the actual triangle
|
||||||
|
if self.drag_active {
|
||||||
|
let vizia_state = WindowModel::vizia_state.get(cx);
|
||||||
|
|
||||||
|
// We need to convert our measurements into physical pixels relative to the
|
||||||
|
// initial drag to be able to keep a consistent ratio. This 'relative to the
|
||||||
|
// start' bit is important because otherwise we would be comparing the
|
||||||
|
// position to the same absoltue screen spotion.
|
||||||
|
// TODO: This may start doing fun things when the window grows so large that
|
||||||
|
// it gets pushed upwards or leftwards
|
||||||
|
let (compensated_physical_x, compensated_physical_y) = (
|
||||||
|
x * self.start_scale_factor as f32,
|
||||||
|
y * self.start_scale_factor as f32,
|
||||||
|
);
|
||||||
|
let (start_physical_x, start_physical_y) = self.start_physical_coordinates;
|
||||||
|
let new_scale_factor = self.start_scale_factor
|
||||||
|
* (compensated_physical_x / start_physical_x)
|
||||||
|
.max(compensated_physical_y / start_physical_y)
|
||||||
|
as f64;
|
||||||
|
if new_scale_factor != vizia_state.user_scale_factor() {
|
||||||
|
cx.emit(WindowEvent::SetScale(new_scale_factor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, cx: &mut Context, canvas: &mut Canvas) {
|
||||||
|
// We'll draw the handle directly as styling elements for this is going to be a bit tricky
|
||||||
|
|
||||||
|
// These basics are taken directly from the default implementation of this function
|
||||||
|
let entity = cx.current;
|
||||||
|
let bounds = cx.cache.get_bounds(entity);
|
||||||
|
if bounds.w == 0.0 || bounds.h == 0.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let background_color = cx
|
||||||
|
.style
|
||||||
|
.background_color
|
||||||
|
.get(entity)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let border_color = cx
|
||||||
|
.style
|
||||||
|
.border_color
|
||||||
|
.get(entity)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let opacity = cx.cache.get_opacity(entity);
|
||||||
|
let mut background_color: femtovg::Color = background_color.into();
|
||||||
|
background_color.set_alphaf(background_color.a * opacity);
|
||||||
|
let mut border_color: femtovg::Color = border_color.into();
|
||||||
|
border_color.set_alphaf(border_color.a * opacity);
|
||||||
|
|
||||||
|
let border_width = match cx
|
||||||
|
.style
|
||||||
|
.border_width
|
||||||
|
.get(entity)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
Units::Pixels(val) => val,
|
||||||
|
Units::Percentage(val) => bounds.w.min(bounds.h) * (val / 100.0),
|
||||||
|
_ => 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut path = Path::new();
|
||||||
|
let x = bounds.x + border_width / 2.0;
|
||||||
|
let y = bounds.y + border_width / 2.0;
|
||||||
|
let w = bounds.w - border_width;
|
||||||
|
let h = bounds.h - border_width;
|
||||||
|
path.move_to(x, y);
|
||||||
|
path.line_to(x, y + h);
|
||||||
|
path.line_to(x + w, y + h);
|
||||||
|
path.line_to(x + w, y);
|
||||||
|
path.line_to(x, y);
|
||||||
|
path.close();
|
||||||
|
|
||||||
|
// Fill with background color
|
||||||
|
let paint = Paint::color(background_color);
|
||||||
|
canvas.fill_path(&mut path, paint);
|
||||||
|
|
||||||
|
// Borders are only supported to make debugging easier
|
||||||
|
let mut paint = Paint::color(border_color);
|
||||||
|
paint.set_line_width(border_width);
|
||||||
|
canvas.stroke_path(&mut path, paint);
|
||||||
|
|
||||||
|
// We'll draw a simple triangle, since we're going flat everywhere anyways and that style
|
||||||
|
// tends to not look too tacky
|
||||||
|
let mut path = Path::new();
|
||||||
|
let x = bounds.x + border_width / 2.0;
|
||||||
|
let y = bounds.y + border_width / 2.0;
|
||||||
|
let w = bounds.w - border_width;
|
||||||
|
let h = bounds.h - border_width;
|
||||||
|
path.move_to(x, y + h);
|
||||||
|
path.line_to(x + w, y + h);
|
||||||
|
path.line_to(x + w, y);
|
||||||
|
path.move_to(x, y + h);
|
||||||
|
path.close();
|
||||||
|
|
||||||
|
// Yeah this looks nowhere as good
|
||||||
|
// path.move_to(x, y + h);
|
||||||
|
// path.line_to(x + (w / 3.0), y + h);
|
||||||
|
// path.line_to(x + w, y + h / 3.0);
|
||||||
|
// path.line_to(x + w, y);
|
||||||
|
// path.move_to(x, y + h);
|
||||||
|
// path.close();
|
||||||
|
|
||||||
|
// path.move_to(x + (w / 3.0 * 1.5), y + h);
|
||||||
|
// path.line_to(x + (w / 3.0 * 2.5), y + h);
|
||||||
|
// path.line_to(x + w, y + (h / 3.0 * 2.5));
|
||||||
|
// path.line_to(x + w, y + (h / 3.0 * 1.5));
|
||||||
|
// path.move_to(x + (w / 3.0 * 1.5), y + h);
|
||||||
|
// path.close();
|
||||||
|
|
||||||
|
let mut color: femtovg::Color = cx
|
||||||
|
.style
|
||||||
|
.font_color
|
||||||
|
.get(entity)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(crate::Color::white())
|
||||||
|
.into();
|
||||||
|
color.set_alphaf(color.a * opacity);
|
||||||
|
let paint = Paint::color(color);
|
||||||
|
canvas.fill_path(&mut path, paint);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue