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
60pub 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
134pub 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}
244pub 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 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 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 pub fn with_step(mut self, step: T) -> Self {
284 self.step = step;
285 self
286 }
287 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 pub fn with_slider(mut self) -> Self {
307 self.slider = true;
308 self
309 }
310}
311
312pub trait ObsProp {
313 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
422pub 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
442pub 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}