Implement most of the iced ParamSlider for Vizia
This commit is contained in:
parent
d8e8d80402
commit
64950055ea
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -660,9 +660,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.3"
|
version = "0.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fdbfe11fe19ff083c48923cf179540e8cd0535903dc35e178a1fdeeb59aef51f"
|
checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
|
@ -3605,7 +3605,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vizia"
|
name = "vizia"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#d8c36fcc91516492d5d43e43b77f2a64738d4d28"
|
source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#e65871171bbc0ef589a228497cf8a32624d979c8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"vizia_baseview",
|
"vizia_baseview",
|
||||||
"vizia_core",
|
"vizia_core",
|
||||||
|
@ -3614,7 +3614,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vizia_baseview"
|
name = "vizia_baseview"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#d8c36fcc91516492d5d43e43b77f2a64738d4d28"
|
source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#e65871171bbc0ef589a228497cf8a32624d979c8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"baseview",
|
"baseview",
|
||||||
"femtovg",
|
"femtovg",
|
||||||
|
@ -3626,7 +3626,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vizia_core"
|
name = "vizia_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#d8c36fcc91516492d5d43e43b77f2a64738d4d28"
|
source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#e65871171bbc0ef589a228497cf8a32624d979c8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"copypasta",
|
"copypasta",
|
||||||
|
@ -3649,7 +3649,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vizia_derive"
|
name = "vizia_derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#d8c36fcc91516492d5d43e43b77f2a64738d4d28"
|
source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#e65871171bbc0ef589a228497cf8a32624d979c8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -1 +1,24 @@
|
||||||
/* Default styling for the widgets included in nih_plug_vizia */
|
/* Default styling for the widgets included in nih_plug_vizia */
|
||||||
|
|
||||||
|
param-slider {
|
||||||
|
height: 30px;
|
||||||
|
width: 180px;
|
||||||
|
border-color: #0a0a0a;
|
||||||
|
border-width: 1px;
|
||||||
|
background-color: transparent;
|
||||||
|
transition: background-color 0.1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WTB Sass */
|
||||||
|
param-slider:active {
|
||||||
|
background-color: #8080801a;
|
||||||
|
transition: background-color 0.1 0;
|
||||||
|
}
|
||||||
|
param-slider:hover {
|
||||||
|
background-color: #8080801a;
|
||||||
|
transition: background-color 0.1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
param-slider .fill {
|
||||||
|
background-color: #c4c4c4;
|
||||||
|
}
|
||||||
|
|
|
@ -11,8 +11,11 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use vizia::{Context, Model};
|
use vizia::{Context, Model};
|
||||||
|
|
||||||
|
mod param_slider;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
|
pub use param_slider::ParamSlider;
|
||||||
|
|
||||||
/// 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()].
|
||||||
pub fn register_theme(cx: &mut Context) {
|
pub fn register_theme(cx: &mut Context) {
|
||||||
|
|
220
nih_plug_vizia/src/widgets/param_slider.rs
Normal file
220
nih_plug_vizia/src/widgets/param_slider.rs
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
//! A slider that integrates with NIH-plug's [`Param`] types.
|
||||||
|
|
||||||
|
use nih_plug::param::internals::ParamPtr;
|
||||||
|
use nih_plug::prelude::{Param, ParamSetter};
|
||||||
|
use vizia::*;
|
||||||
|
|
||||||
|
use super::util::{self, ModifiersExt};
|
||||||
|
use super::RawParamEvent;
|
||||||
|
|
||||||
|
/// When shift+dragging a parameter, one pixel dragged corresponds to this much change in the
|
||||||
|
/// noramlized parameter.
|
||||||
|
const GRANULAR_DRAG_MULTIPLIER: f32 = 0.1;
|
||||||
|
|
||||||
|
/// A slider that integrates with NIH-plug's [`Param`] types.
|
||||||
|
///
|
||||||
|
/// TODO: Handle scrolling for steps (and shift+scroll for smaller steps?)
|
||||||
|
/// TODO: We may want to add a couple dedicated event handlers if it seems like those would be
|
||||||
|
/// useful, having a completely self contained widget is perfectly fine for now though
|
||||||
|
/// TODO: Implement ALt+Click text input in this version
|
||||||
|
pub struct ParamSlider {
|
||||||
|
// We're not allowed to store a reference to the parameter internally, at least not in the
|
||||||
|
// struct that implements [`View`]
|
||||||
|
param_ptr: ParamPtr,
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
/// Whether the next click is a double click. Vizia will send a double click event followed by a
|
||||||
|
/// regular mouse down event when double clicking.
|
||||||
|
is_double_click: bool,
|
||||||
|
/// We keep track of the start coordinate holding down Shift while dragging for higher precision
|
||||||
|
/// dragging. This is a `None` value when granular dragging is not active.
|
||||||
|
granular_drag_start_x: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParamSlider {
|
||||||
|
/// Creates a new [`ParamSlider`] for the given parameter. To accomdate VIZIA's mapping system,
|
||||||
|
/// you'll need to provide a lens containing your `Params` implementation object (check out how
|
||||||
|
/// the `Data` struct is used in `gain_gui_vizia`), the `ParamSetter` for retrieving the
|
||||||
|
/// parameter's default value, and a projection function that maps the `Params` object to the
|
||||||
|
/// parameter you want to display a widget for.
|
||||||
|
pub fn new<'a, L, Params, P, F>(
|
||||||
|
cx: &'a mut Context,
|
||||||
|
params: L,
|
||||||
|
setter: &ParamSetter,
|
||||||
|
params_to_param: F,
|
||||||
|
) -> Handle<'a, Self>
|
||||||
|
where
|
||||||
|
L: Lens<Target = Params>,
|
||||||
|
F: 'static + Fn(&Params) -> &P + Copy,
|
||||||
|
Params: 'static,
|
||||||
|
P: Param,
|
||||||
|
{
|
||||||
|
let param_display_value_lens = params
|
||||||
|
.clone()
|
||||||
|
.map(move |params| params_to_param(params).to_string());
|
||||||
|
let normalized_param_value_lens = params
|
||||||
|
.clone()
|
||||||
|
.map(move |params| params_to_param(params).normalized_value());
|
||||||
|
|
||||||
|
// We'll visualize the difference between the current value and the default value if the
|
||||||
|
// default value lies somewhere in the middle and the parameter is continuous. Otherwise
|
||||||
|
// this appraoch looks a bit jarring.
|
||||||
|
// We need to do a bit of a nasty and erase the lifetime bound by going through the raw
|
||||||
|
// GuiContext and a ParamPtr.
|
||||||
|
let param_ptr = *params
|
||||||
|
.clone()
|
||||||
|
.map(move |params| params_to_param(params).as_ptr())
|
||||||
|
.get(cx);
|
||||||
|
let default_value = unsafe {
|
||||||
|
setter
|
||||||
|
.raw_context
|
||||||
|
.raw_default_normalized_param_value(param_ptr)
|
||||||
|
};
|
||||||
|
let step_count = *params
|
||||||
|
.map(move |params| params_to_param(params).step_count())
|
||||||
|
.get(cx);
|
||||||
|
let draw_fill_from_default = step_count.is_none() && (0.45..=0.55).contains(&default_value);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
param_ptr,
|
||||||
|
|
||||||
|
drag_active: false,
|
||||||
|
is_double_click: false,
|
||||||
|
granular_drag_start_x: None,
|
||||||
|
}
|
||||||
|
.build2(cx, |cx| {
|
||||||
|
ZStack::new(cx, move |cx| {
|
||||||
|
// The filled bar portion
|
||||||
|
Element::new(cx).class("fill").height(Stretch(1.0)).bind(
|
||||||
|
normalized_param_value_lens,
|
||||||
|
move |handle, value| {
|
||||||
|
let current_value = *value.get(handle.cx);
|
||||||
|
if draw_fill_from_default {
|
||||||
|
handle
|
||||||
|
.left(Percentage(default_value.min(current_value) * 100.0))
|
||||||
|
.right(Percentage(
|
||||||
|
100.0 - (default_value.max(current_value) * 100.0),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
handle
|
||||||
|
.left(Percentage(0.0))
|
||||||
|
.right(Percentage(100.0 - (current_value * 100.0)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only draw the text input widget when it gets focussed. Otherwise, overlay the label with
|
||||||
|
// the slider.
|
||||||
|
// TODO: Text entry stuff
|
||||||
|
Label::new(cx, param_display_value_lens)
|
||||||
|
.height(Stretch(1.0))
|
||||||
|
.width(Stretch(1.0));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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, cx: &mut Context, 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 = unsafe { self.param_ptr.preview_plain(normalized_value) };
|
||||||
|
let current_plain_value = unsafe { self.param_ptr.plain_value() };
|
||||||
|
if plain_value != current_plain_value {
|
||||||
|
// For the aforementioned snapping
|
||||||
|
let normalized_plain_value = unsafe { self.param_ptr.preview_normalized(plain_value) };
|
||||||
|
cx.emit(RawParamEvent::SetParameterNormalized(
|
||||||
|
self.param_ptr,
|
||||||
|
normalized_plain_value,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for ParamSlider {
|
||||||
|
fn element(&self) -> Option<String> {
|
||||||
|
Some(String::from("param-slider"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, cx: &mut Context, event: &mut Event) {
|
||||||
|
if let Some(window_event) = event.message.downcast() {
|
||||||
|
// FIXME: Handle shift releasing, I don't see an event for that here
|
||||||
|
match window_event {
|
||||||
|
WindowEvent::MouseDown(MouseButton::Left) => {
|
||||||
|
// Ctrl+Click and double click should reset the parameter instead of initiating
|
||||||
|
// a drag operation
|
||||||
|
// TODO: Handle Alt+Click for text entry
|
||||||
|
if cx.modifiers.command() || self.is_double_click {
|
||||||
|
self.is_double_click = false;
|
||||||
|
|
||||||
|
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
||||||
|
cx.emit(RawParamEvent::ResetParameter(self.param_ptr));
|
||||||
|
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
||||||
|
} else {
|
||||||
|
self.drag_active = true;
|
||||||
|
cx.capture();
|
||||||
|
cx.current.set_active(cx, true);
|
||||||
|
|
||||||
|
// When holding down shift while clicking on a parameter we want to
|
||||||
|
// granuarly edit the parameter without jumping to a new value
|
||||||
|
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
||||||
|
if cx.modifiers.shift() {
|
||||||
|
self.granular_drag_start_x = Some(cx.mouse.cursorx);
|
||||||
|
} else {
|
||||||
|
self.granular_drag_start_x = None;
|
||||||
|
self.set_normalized_value(
|
||||||
|
cx,
|
||||||
|
util::remap_current_entity_x_coordinate(cx, cx.mouse.cursorx),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::MouseDoubleClick(MouseButton::Left) => {
|
||||||
|
// Vizia will send a regular mouse down after this, so we'll handle the reset
|
||||||
|
// there
|
||||||
|
self.is_double_click = true;
|
||||||
|
}
|
||||||
|
WindowEvent::MouseUp(MouseButton::Left) => {
|
||||||
|
if self.drag_active {
|
||||||
|
self.drag_active = false;
|
||||||
|
cx.release();
|
||||||
|
cx.current.set_active(cx, false);
|
||||||
|
|
||||||
|
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::MouseMove(x, _y) => {
|
||||||
|
if self.drag_active {
|
||||||
|
// If shift is being held then the drag should be more granular instead of
|
||||||
|
// absolute
|
||||||
|
if cx.modifiers.shift() {
|
||||||
|
let drag_start_x =
|
||||||
|
*self.granular_drag_start_x.get_or_insert(cx.mouse.cursorx);
|
||||||
|
|
||||||
|
self.set_normalized_value(
|
||||||
|
cx,
|
||||||
|
util::remap_current_entity_x_coordinate(
|
||||||
|
cx,
|
||||||
|
drag_start_x + (*x - drag_start_x) * GRANULAR_DRAG_MULTIPLIER,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.granular_drag_start_x = None;
|
||||||
|
|
||||||
|
self.set_normalized_value(
|
||||||
|
cx,
|
||||||
|
util::remap_current_entity_x_coordinate(cx, *x),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
//! Utilities for writing VIZIA widgets.
|
//! Utilities for writing VIZIA widgets.
|
||||||
|
|
||||||
use vizia::Modifiers;
|
use vizia::{Context, Modifiers};
|
||||||
|
|
||||||
/// An extension trait for [`Modifiers`] that adds platform-independent getters.
|
/// An extension trait for [`Modifiers`] that adds platform-independent getters.
|
||||||
pub trait ModifiersExt {
|
pub trait ModifiersExt {
|
||||||
|
@ -33,3 +33,21 @@ impl ModifiersExt for Modifiers {
|
||||||
self.contains(Modifiers::SHIFT)
|
self.contains(Modifiers::SHIFT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remap an x-coordinate to a `[0, 1]` value within the current entity's bounding box. The value
|
||||||
|
/// will be clamped to `[0, 1]` if it isn't already in that range.
|
||||||
|
///
|
||||||
|
/// FIXME: These functions probably include borders, we dont' want that
|
||||||
|
pub fn remap_current_entity_x_coordinate(cx: &Context, x_coord: f32) -> f32 {
|
||||||
|
let x_pos = cx.cache.get_posx(cx.current);
|
||||||
|
let width = cx.cache.get_width(cx.current);
|
||||||
|
((x_coord - x_pos) / width).clamp(0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remap an y-coordinate to a `[0, 1]` value within the current entity's bounding box. The value
|
||||||
|
/// will be clamped to `[0, 1]` if it isn't already in that range.
|
||||||
|
pub fn remap_current_entity_y_coordinate(cx: &Context, y_coord: f32) -> f32 {
|
||||||
|
let y_pos = cx.cache.get_posy(cx.current);
|
||||||
|
let height = cx.cache.get_height(cx.current);
|
||||||
|
((y_coord - y_pos) / height).clamp(0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ pub struct Transport {
|
||||||
/// the host and reflected in the plugin's [`Params`][crate::param::internals::Params] object. These
|
/// the host and reflected in the plugin's [`Params`][crate::param::internals::Params] object. These
|
||||||
/// functions should only be called from the main thread.
|
/// functions should only be called from the main thread.
|
||||||
pub struct ParamSetter<'a> {
|
pub struct ParamSetter<'a> {
|
||||||
context: &'a dyn GuiContext,
|
pub raw_context: &'a dyn GuiContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: These conversions have not really been tested yet, there might be an error in there somewhere
|
// TODO: These conversions have not really been tested yet, there might be an error in there somewhere
|
||||||
|
@ -335,13 +335,15 @@ impl Transport {
|
||||||
|
|
||||||
impl<'a> ParamSetter<'a> {
|
impl<'a> ParamSetter<'a> {
|
||||||
pub fn new(context: &'a dyn GuiContext) -> Self {
|
pub fn new(context: &'a dyn GuiContext) -> Self {
|
||||||
Self { context }
|
Self {
|
||||||
|
raw_context: context,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inform the host that you will start automating a parmater. This needs to be called before
|
/// Inform the host that you will start automating a parmater. This needs to be called before
|
||||||
/// calling [`set_parameter()`][Self::set_parameter()] for the specified parameter.
|
/// calling [`set_parameter()`][Self::set_parameter()] for the specified parameter.
|
||||||
pub fn begin_set_parameter<P: Param>(&self, param: &P) {
|
pub fn begin_set_parameter<P: Param>(&self, param: &P) {
|
||||||
unsafe { self.context.raw_begin_set_parameter(param.as_ptr()) };
|
unsafe { self.raw_context.raw_begin_set_parameter(param.as_ptr()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a parameter to the specified parameter value. You will need to call
|
/// Set a parameter to the specified parameter value. You will need to call
|
||||||
|
@ -356,7 +358,10 @@ impl<'a> ParamSetter<'a> {
|
||||||
pub fn set_parameter<P: Param>(&self, param: &P, value: P::Plain) {
|
pub fn set_parameter<P: Param>(&self, param: &P, value: P::Plain) {
|
||||||
let ptr = param.as_ptr();
|
let ptr = param.as_ptr();
|
||||||
let normalized = param.preview_normalized(value);
|
let normalized = param.preview_normalized(value);
|
||||||
unsafe { self.context.raw_set_parameter_normalized(ptr, normalized) };
|
unsafe {
|
||||||
|
self.raw_context
|
||||||
|
.raw_set_parameter_normalized(ptr, normalized)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a parameter to an already normalized value. Works exactly the same as
|
/// Set a parameter to an already normalized value. Works exactly the same as
|
||||||
|
@ -368,21 +373,24 @@ impl<'a> ParamSetter<'a> {
|
||||||
/// normalized value known to the host matches `param.normalized_value()`.
|
/// normalized value known to the host matches `param.normalized_value()`.
|
||||||
pub fn set_parameter_normalized<P: Param>(&self, param: &P, normalized: f32) {
|
pub fn set_parameter_normalized<P: Param>(&self, param: &P, normalized: f32) {
|
||||||
let ptr = param.as_ptr();
|
let ptr = param.as_ptr();
|
||||||
unsafe { self.context.raw_set_parameter_normalized(ptr, normalized) };
|
unsafe {
|
||||||
|
self.raw_context
|
||||||
|
.raw_set_parameter_normalized(ptr, normalized)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inform the host that you are done automating a parameter. This needs to be called after one
|
/// Inform the host that you are done automating a parameter. This needs to be called after one
|
||||||
/// or more [`set_parameter()`][Self::set_parameter()] calls for a parameter so the host knows
|
/// or more [`set_parameter()`][Self::set_parameter()] calls for a parameter so the host knows
|
||||||
/// the automation gesture has finished.
|
/// the automation gesture has finished.
|
||||||
pub fn end_set_parameter<P: Param>(&self, param: &P) {
|
pub fn end_set_parameter<P: Param>(&self, param: &P) {
|
||||||
unsafe { self.context.raw_end_set_parameter(param.as_ptr()) };
|
unsafe { self.raw_context.raw_end_set_parameter(param.as_ptr()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the default value for a parameter, in case you forgot. The value is already
|
/// Retrieve the default value for a parameter, in case you forgot. The value is already
|
||||||
/// normalized to `[0, 1]`. This is useful when implementing GUIs, and it does not perform a callback.
|
/// normalized to `[0, 1]`. This is useful when implementing GUIs, and it does not perform a callback.
|
||||||
pub fn default_normalized_param_value<P: Param>(&self, param: &P) -> f32 {
|
pub fn default_normalized_param_value<P: Param>(&self, param: &P) -> f32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.context
|
self.raw_context
|
||||||
.raw_default_normalized_param_value(param.as_ptr())
|
.raw_default_normalized_param_value(param.as_ptr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue