1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! Generic UIs for NIH-plug using VIZIA.

use nih_plug::prelude::{ParamFlags, ParamPtr, Params};
use vizia::prelude::*;

use super::{ParamSlider, ParamSliderExt, ParamSliderStyle};

/// Shows a generic UI for a [`Params`] object. For additional flexibility you can either use the
/// [`new()`][`Self::new()`] method to have the generic UI decide which widget to use for your
/// parameters, or you can use the [`new_custom()`][`Self::new_custom()`] method to determine this
/// yourself.
pub struct GenericUi;

impl GenericUi {
    /// Creates a new [`GenericUi`] for all provided parameters. Use
    /// [`new_custom()`][Self::new_custom()] to decide which widget gets used for each parameter.
    ///
    /// Wrap this in a [`ScrollView`] for plugins with longer parameter lists:
    ///
    /// ```ignore
    /// ScrollView::new(cx, 0.0, 0.0, false, true, |cx| {
    ///     GenericUi::new(cx, Data::params);
    /// })
    /// .width(Percentage(100.0));
    ///```
    pub fn new<L, PsRef, Ps>(cx: &mut Context, params: L) -> Handle<'_, GenericUi>
    where
        L: Lens<Target = PsRef> + Clone,
        PsRef: AsRef<Ps> + 'static,
        Ps: Params + 'static,
    {
        // Basic styling is done in the `theme.css` style sheet
        Self::new_custom(cx, params.clone(), move |cx, param_ptr| {
            let params = params.clone();
            HStack::new(cx, move |cx| {
                // Align this on the right
                Label::new(cx, unsafe { param_ptr.name() }).class("label");

                Self::draw_widget(cx, params, param_ptr);
            })
            .class("row");
        })
    }

    /// Creates a new [`GenericUi`] for all provided parameters using a custom closure that receives
    /// a function that should draw some widget for each parameter.
    pub fn new_custom<L, PsRef, Ps>(
        cx: &mut Context,
        params: L,
        mut make_widget: impl FnMut(&mut Context, ParamPtr),
    ) -> Handle<Self>
    where
        L: Lens<Target = PsRef>,
        PsRef: AsRef<Ps> + 'static,
        Ps: Params + 'static,
    {
        // Basic styling is done in the `theme.css` style sheet
        Self.build(cx, |cx| {
            // Rust does not have existential types, otherwise we could have passed functions that
            // map `params` to some `impl Param` and everything would have been a lot neater
            let param_map = params.map(|params| params.as_ref().param_map()).get(cx);
            for (_, param_ptr, _) in param_map {
                let flags = unsafe { param_ptr.flags() };
                if flags.contains(ParamFlags::HIDE_IN_GENERIC_UI) {
                    continue;
                }

                make_widget(cx, param_ptr);
            }
        })
    }

    /// The standard widget drawing function. This can be used together with `.new_custom()` to only
    /// draw the labels differently.
    pub fn draw_widget<L, PsRef, Ps>(cx: &mut Context, params: L, param_ptr: ParamPtr)
    where
        L: Lens<Target = PsRef>,
        PsRef: AsRef<Ps> + 'static,
        Ps: Params + 'static,
    {
        unsafe {
            match param_ptr {
                ParamPtr::FloatParam(p) => ParamSlider::new(cx, params, move |_| &*p),
                ParamPtr::IntParam(p) => ParamSlider::new(cx, params, move |_| &*p),
                ParamPtr::BoolParam(p) => ParamSlider::new(cx, params, move |_| &*p),
                ParamPtr::EnumParam(p) => ParamSlider::new(cx, params, move |_| &*p),
            }
        }
        .set_style(match unsafe { param_ptr.step_count() } {
            // This looks nice for boolean values, but it's too crowded for anything beyond
            // that without making the widget wider
            Some(step_count) if step_count <= 1 => {
                ParamSliderStyle::CurrentStepLabeled { even: true }
            }
            Some(step_count) if step_count <= 2 => ParamSliderStyle::CurrentStep { even: true },
            Some(_) => ParamSliderStyle::FromLeft,
            // This is already the default, but continuous parameters should be drawn from
            // the center if the default is also centered, or from the left if it is not
            None => ParamSliderStyle::Centered,
        })
        .class("widget");
    }
}

impl View for GenericUi {
    fn element(&self) -> Option<&'static str> {
        Some("generic-ui")
    }
}