obs_wrapper/
properties.rs

1#![allow(non_upper_case_globals)]
2#![allow(clippy::not_unsafe_ptr_arg_deref)]
3
4use crate::{native_enum, string::ObsString, wrapper::PtrWrapper};
5use num_traits::{one, Bounded, Float, Num, NumCast, PrimInt, ToPrimitive};
6use obs_sys::{
7    obs_combo_format, obs_combo_format_OBS_COMBO_FORMAT_FLOAT,
8    obs_combo_format_OBS_COMBO_FORMAT_INT, obs_combo_format_OBS_COMBO_FORMAT_INVALID,
9    obs_combo_format_OBS_COMBO_FORMAT_STRING, obs_combo_type,
10    obs_combo_type_OBS_COMBO_TYPE_EDITABLE, obs_combo_type_OBS_COMBO_TYPE_INVALID,
11    obs_combo_type_OBS_COMBO_TYPE_LIST, obs_editable_list_type,
12    obs_editable_list_type_OBS_EDITABLE_LIST_TYPE_FILES,
13    obs_editable_list_type_OBS_EDITABLE_LIST_TYPE_FILES_AND_URLS,
14    obs_editable_list_type_OBS_EDITABLE_LIST_TYPE_STRINGS, obs_path_type,
15    obs_path_type_OBS_PATH_DIRECTORY, obs_path_type_OBS_PATH_FILE,
16    obs_path_type_OBS_PATH_FILE_SAVE, obs_properties_add_bool, obs_properties_add_color,
17    obs_properties_add_editable_list, obs_properties_add_float, obs_properties_add_float_slider,
18    obs_properties_add_font, obs_properties_add_int, obs_properties_add_int_slider,
19    obs_properties_add_list, obs_properties_add_path, obs_properties_add_text,
20    obs_properties_create, obs_properties_destroy, obs_properties_t, obs_property_list_add_float,
21    obs_property_list_add_int, obs_property_list_add_string, obs_property_list_insert_float,
22    obs_property_list_insert_int, obs_property_list_insert_string, obs_property_list_item_disable,
23    obs_property_list_item_remove, obs_property_t, obs_text_type, obs_text_type_OBS_TEXT_DEFAULT,
24    obs_text_type_OBS_TEXT_MULTILINE, obs_text_type_OBS_TEXT_PASSWORD, size_t,
25};
26
27use std::{marker::PhantomData, ops::RangeBounds, os::raw::c_int};
28
29native_enum!(TextType, obs_text_type {
30    Default => OBS_TEXT_DEFAULT,
31    Password => OBS_TEXT_PASSWORD,
32    Multiline => OBS_TEXT_MULTILINE
33});
34
35native_enum!(PathType, obs_path_type {
36    File => OBS_PATH_FILE,
37    FileSave => OBS_PATH_FILE_SAVE,
38    Directory => OBS_PATH_DIRECTORY
39});
40
41native_enum!(ComboFormat, obs_combo_format {
42    Invalid => OBS_COMBO_FORMAT_INVALID,
43    Int => OBS_COMBO_FORMAT_INT,
44    Float => OBS_COMBO_FORMAT_FLOAT,
45    String => OBS_COMBO_FORMAT_STRING
46});
47
48native_enum!(ComboType, obs_combo_type {
49    Invalid => OBS_COMBO_TYPE_INVALID,
50    Editable => OBS_COMBO_TYPE_EDITABLE,
51    List => OBS_COMBO_TYPE_LIST
52});
53
54native_enum!(EditableListType, obs_editable_list_type {
55    Strings => OBS_EDITABLE_LIST_TYPE_STRINGS,
56    Files => OBS_EDITABLE_LIST_TYPE_FILES,
57    FilesAndUrls => OBS_EDITABLE_LIST_TYPE_FILES_AND_URLS
58});
59
60/// Wrapper around [`obs_properties_t`], which is used by
61/// OBS to generate a user-friendly configuration UI.
62pub struct Properties {
63    pointer: *mut obs_properties_t,
64}
65
66impl PtrWrapper for Properties {
67    type Pointer = obs_properties_t;
68
69    unsafe fn from_raw(raw: *mut Self::Pointer) -> Self {
70        Self { pointer: raw }
71    }
72
73    fn as_ptr(&self) -> *const Self::Pointer {
74        self.pointer
75    }
76}
77
78impl Default for Properties {
79    fn default() -> Self {
80        Properties::new()
81    }
82}
83
84impl Properties {
85    pub fn new() -> Self {
86        unsafe {
87            let ptr = obs_properties_create();
88            Self::from_raw(ptr)
89        }
90    }
91
92    pub fn add<T: ObsProp>(
93        &mut self,
94        name: ObsString,
95        description: ObsString,
96        prop: T,
97    ) -> &mut Self {
98        unsafe {
99            prop.add_to_props(self.pointer, name, description);
100        }
101        self
102    }
103
104    pub fn add_list<T: ListType>(
105        &mut self,
106        name: ObsString,
107        description: ObsString,
108        editable: bool,
109    ) -> ListProp<T> {
110        unsafe {
111            let raw = obs_properties_add_list(
112                self.pointer,
113                name.as_ptr(),
114                description.as_ptr(),
115                if editable {
116                    ComboType::Editable
117                } else {
118                    ComboType::List
119                }
120                .into(),
121                T::format().into(),
122            );
123            ListProp::from_raw(raw)
124        }
125    }
126}
127
128impl Drop for Properties {
129    fn drop(&mut self) {
130        unsafe { obs_properties_destroy(self.pointer) }
131    }
132}
133
134/// Wrapper around [`obs_property_t`], which is a list of possible values for a
135/// property.
136pub struct ListProp<'props, T> {
137    raw: *mut obs_property_t,
138    _props: PhantomData<&'props mut Properties>,
139    _type: PhantomData<T>,
140}
141
142impl<T> PtrWrapper for ListProp<'_, T> {
143    type Pointer = obs_property_t;
144
145    unsafe fn from_raw(raw: *mut Self::Pointer) -> Self {
146        Self {
147            raw,
148            _props: PhantomData,
149            _type: PhantomData,
150        }
151    }
152
153    fn as_ptr(&self) -> *const Self::Pointer {
154        self.raw
155    }
156}
157
158impl<T: ListType> ListProp<'_, T> {
159    pub fn push(&mut self, name: impl Into<ObsString>, value: T) {
160        value.push_into(self.raw, name.into());
161    }
162
163    pub fn insert(&mut self, index: usize, name: impl Into<ObsString>, value: T) {
164        value.insert_into(self.raw, name.into(), index);
165    }
166
167    pub fn remove(&mut self, index: usize) {
168        unsafe {
169            obs_property_list_item_remove(self.raw, index as size_t);
170        }
171    }
172
173    pub fn disable(&mut self, index: usize, disabled: bool) {
174        unsafe {
175            obs_property_list_item_disable(self.raw, index as size_t, disabled);
176        }
177    }
178}
179
180pub trait ListType {
181    fn format() -> ComboFormat;
182    fn push_into(self, ptr: *mut obs_property_t, name: ObsString);
183    fn insert_into(self, ptr: *mut obs_property_t, name: ObsString, index: usize);
184}
185
186impl ListType for ObsString {
187    fn format() -> ComboFormat {
188        ComboFormat::String
189    }
190
191    fn push_into(self, ptr: *mut obs_property_t, name: ObsString) {
192        unsafe {
193            obs_property_list_add_string(ptr, name.as_ptr(), self.as_ptr());
194        }
195    }
196
197    fn insert_into(self, ptr: *mut obs_property_t, name: ObsString, index: usize) {
198        unsafe {
199            obs_property_list_insert_string(ptr, index as size_t, name.as_ptr(), self.as_ptr());
200        }
201    }
202}
203
204impl ListType for i64 {
205    fn format() -> ComboFormat {
206        ComboFormat::Int
207    }
208
209    fn push_into(self, ptr: *mut obs_property_t, name: ObsString) {
210        unsafe {
211            obs_property_list_add_int(ptr, name.as_ptr(), self);
212        }
213    }
214
215    fn insert_into(self, ptr: *mut obs_property_t, name: ObsString, index: usize) {
216        unsafe {
217            obs_property_list_insert_int(ptr, index as size_t, name.as_ptr(), self);
218        }
219    }
220}
221
222impl ListType for f64 {
223    fn format() -> ComboFormat {
224        ComboFormat::Float
225    }
226
227    fn push_into(self, ptr: *mut obs_property_t, name: ObsString) {
228        unsafe {
229            obs_property_list_add_float(ptr, name.as_ptr(), self);
230        }
231    }
232
233    fn insert_into(self, ptr: *mut obs_property_t, name: ObsString, index: usize) {
234        unsafe {
235            obs_property_list_insert_float(ptr, index as size_t, name.as_ptr(), self);
236        }
237    }
238}
239
240enum NumberType {
241    Integer,
242    Float,
243}
244/// ## Panics
245/// This type of property may cause panic when being added to the properties
246/// if the provided `min`, `max` or `step` exceeds [`c_int`].
247pub struct NumberProp<T> {
248    min: T,
249    max: T,
250    step: T,
251    slider: bool,
252    typ: NumberType,
253}
254
255impl<T: PrimInt> NumberProp<T> {
256    /// Creates a new integer property with step set to 1.
257    pub fn new_int() -> Self {
258        Self {
259            min: T::min_value(),
260            max: T::max_value(),
261            step: one(),
262            slider: false,
263            typ: NumberType::Integer,
264        }
265    }
266}
267
268impl<T: Float> NumberProp<T> {
269    /// Creates a new float property with a certain step.
270    pub fn new_float(step: T) -> Self {
271        Self {
272            min: T::min_value(),
273            max: T::max_value(),
274            step,
275            slider: false,
276            typ: NumberType::Float,
277        }
278    }
279}
280
281impl<T: Num + Bounded + Copy> NumberProp<T> {
282    /// Sets the step of the property.
283    pub fn with_step(mut self, step: T) -> Self {
284        self.step = step;
285        self
286    }
287    /// Sets the range of the property, inclusion and exclustion are calucated
288    /// based on the **current step**.
289    pub fn with_range<R: RangeBounds<T>>(mut self, range: R) -> Self {
290        use std::ops::Bound::*;
291        self.min = match range.start_bound() {
292            Included(min) => *min,
293            Excluded(min) => *min + self.step,
294            std::ops::Bound::Unbounded => T::min_value(),
295        };
296
297        self.max = match range.end_bound() {
298            Included(max) => *max,
299            Excluded(max) => *max - self.step,
300            std::ops::Bound::Unbounded => T::max_value(),
301        };
302
303        self
304    }
305    /// Sets this property as a slider.
306    pub fn with_slider(mut self) -> Self {
307        self.slider = true;
308        self
309    }
310}
311
312pub trait ObsProp {
313    /// Callback to add this property to a [`obs_properties_t`].
314    ///
315    /// # Safety
316    ///
317    /// Must call with a valid pointer.
318    unsafe fn add_to_props(self, p: *mut obs_properties_t, name: ObsString, description: ObsString);
319}
320
321impl<T: ToPrimitive> ObsProp for NumberProp<T> {
322    unsafe fn add_to_props(
323        self,
324        p: *mut obs_properties_t,
325        name: ObsString,
326        description: ObsString,
327    ) {
328        match self.typ {
329            NumberType::Integer => {
330                let min: c_int = NumCast::from(self.min).unwrap();
331                let max: c_int = NumCast::from(self.max).unwrap();
332                let step: c_int = NumCast::from(self.step).unwrap();
333
334                if self.slider {
335                    obs_properties_add_int_slider(
336                        p,
337                        name.as_ptr(),
338                        description.as_ptr(),
339                        min,
340                        max,
341                        step,
342                    );
343                } else {
344                    obs_properties_add_int(p, name.as_ptr(), description.as_ptr(), min, max, step);
345                }
346            }
347            NumberType::Float => {
348                let min: f64 = NumCast::from(self.min).unwrap();
349                let max: f64 = NumCast::from(self.max).unwrap();
350                let step: f64 = NumCast::from(self.step).unwrap();
351
352                if self.slider {
353                    obs_properties_add_float_slider(
354                        p,
355                        name.as_ptr(),
356                        description.as_ptr(),
357                        min,
358                        max,
359                        step,
360                    );
361                } else {
362                    obs_properties_add_float(
363                        p,
364                        name.as_ptr(),
365                        description.as_ptr(),
366                        min,
367                        max,
368                        step,
369                    );
370                }
371            }
372        }
373    }
374}
375
376pub struct BoolProp;
377
378impl ObsProp for BoolProp {
379    unsafe fn add_to_props(
380        self,
381        p: *mut obs_properties_t,
382        name: ObsString,
383        description: ObsString,
384    ) {
385        obs_properties_add_bool(p, name.as_ptr(), description.as_ptr());
386    }
387}
388pub struct TextProp {
389    typ: TextType,
390}
391
392impl TextProp {
393    pub fn new(typ: TextType) -> Self {
394        Self { typ }
395    }
396}
397
398impl ObsProp for TextProp {
399    unsafe fn add_to_props(
400        self,
401        p: *mut obs_properties_t,
402        name: ObsString,
403        description: ObsString,
404    ) {
405        obs_properties_add_text(p, name.as_ptr(), description.as_ptr(), self.typ.into());
406    }
407}
408
409pub struct ColorProp;
410
411impl ObsProp for ColorProp {
412    unsafe fn add_to_props(
413        self,
414        p: *mut obs_properties_t,
415        name: ObsString,
416        description: ObsString,
417    ) {
418        obs_properties_add_color(p, name.as_ptr(), description.as_ptr());
419    }
420}
421
422/// Adds a font selection property.
423///
424/// A font is an obs_data sub-object which contains the following items:
425/// * face:   face name string
426/// * style:  style name string
427/// * size:   size integer
428/// * flags:  font flags integer (OBS_FONT_* defined above)
429pub struct FontProp;
430
431impl ObsProp for FontProp {
432    unsafe fn add_to_props(
433        self,
434        p: *mut obs_properties_t,
435        name: ObsString,
436        description: ObsString,
437    ) {
438        obs_properties_add_font(p, name.as_ptr(), description.as_ptr());
439    }
440}
441
442/// Adds a 'path' property.  Can be a directory or a file.
443///
444/// If target is a file path, the filters should be this format, separated by
445/// double semi-colens, and extensions separated by space:
446///
447/// "Example types 1 and 2 (*.ex1 *.ex2);;Example type 3 (*.ex3)"
448///
449/// Arguments
450/// * `props`: Properties object
451/// * `name`: Settings name
452/// * `description`: Description (display name) of the property
453/// * `type`: Type of path (directory or file)
454/// * `filter`: If type is a file path, then describes the file filter that the
455///   user can browse.  Items are separated via double semi-colens.  If multiple
456///   file types in a filter, separate with space.
457pub struct PathProp {
458    typ: PathType,
459    filter: Option<ObsString>,
460    default_path: Option<ObsString>,
461}
462
463impl PathProp {
464    pub fn new(typ: PathType) -> Self {
465        Self {
466            typ,
467            filter: None,
468            default_path: None,
469        }
470    }
471
472    pub fn with_filter(mut self, f: ObsString) -> Self {
473        self.filter = Some(f);
474        self
475    }
476
477    pub fn with_default_path(mut self, d: ObsString) -> Self {
478        self.default_path = Some(d);
479        self
480    }
481}
482
483impl ObsProp for PathProp {
484    unsafe fn add_to_props(
485        self,
486        p: *mut obs_properties_t,
487        name: ObsString,
488        description: ObsString,
489    ) {
490        obs_properties_add_path(
491            p,
492            name.as_ptr(),
493            description.as_ptr(),
494            self.typ.into(),
495            ObsString::ptr_or_null(&self.filter),
496            ObsString::ptr_or_null(&self.default_path),
497        );
498    }
499}
500
501pub struct EditableListProp {
502    typ: EditableListType,
503    filter: Option<ObsString>,
504    default_path: Option<ObsString>,
505}
506
507impl EditableListProp {
508    pub fn new(typ: EditableListType) -> Self {
509        Self {
510            typ,
511            filter: None,
512            default_path: None,
513        }
514    }
515
516    pub fn with_filter(mut self, f: ObsString) -> Self {
517        self.filter = Some(f);
518        self
519    }
520
521    pub fn with_default_path(mut self, d: ObsString) -> Self {
522        self.default_path = Some(d);
523        self
524    }
525}
526
527impl ObsProp for EditableListProp {
528    unsafe fn add_to_props(
529        self,
530        p: *mut obs_properties_t,
531        name: ObsString,
532        description: ObsString,
533    ) {
534        obs_properties_add_editable_list(
535            p,
536            name.as_ptr(),
537            description.as_ptr(),
538            self.typ.into(),
539            ObsString::ptr_or_null(&self.filter),
540            ObsString::ptr_or_null(&self.default_path),
541        );
542    }
543}