1
0
Fork 0

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:
Robbert van der Helm 2022-03-28 00:32:53 +02:00
parent d46169cb18
commit 772c1ecfaf
6 changed files with 218 additions and 12 deletions

View file

@ -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;
}

View file

@ -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>,

View file

@ -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>,

View file

@ -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,

View file

@ -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>,
{ {

View 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);
}
}