Skip to main content

libobs_wrapper\scenes/
mod.rs

1//! Scenes, managed scene items, native OBS groups, ordering, and transform state.
2//!
3//! [`ObsSceneRef`] is the scene-level entry point. Add typed sources with [`ObsSceneRef::add`], inspect
4//! actual bottom-to-top libobs order with [`ObsSceneRef::items_in_order`], and create native groups
5//! with [`ObsSceneRef::create_group`].
6//!
7//! Scene-item behavior is exposed through [`SceneItemTrait`]: position/scale/rotation, bounds, edge
8//! crop, scale filtering, blending, visibility, locking, order, and complete transform/state
9//! snapshots. Ordinary source insertion returns [`ObsSceneItemRef<T>`], preserving the source type.
10//! [`ObsSceneItemHandle`] is the type-erased managed handle used when libobs itself replaces an item.
11//!
12//! # Group identity semantics
13//!
14//! libobs reuses child objects when moving them into/out of a group, but *ungrouping the whole group*
15//! creates replacement parent-scene items. [`ObsSceneGroupRef::ungroup`] models that explicitly by
16//! returning [`ObsUngroupedItem`] mappings and marking replaced child handles removed.
17
18mod transform_info;
19pub use transform_info::*;
20
21mod group;
22mod scene_drop_guards;
23mod scene_item;
24
25mod filter_traits;
26pub use filter_traits::*;
27
28pub use group::*;
29pub use scene_item::*;
30
31use std::collections::HashMap;
32use std::fmt::Debug;
33use std::sync::{Arc, RwLock};
34
35use libobs::{obs_scene_t, obs_source_t};
36
37use crate::macros::impl_eq_of_ptr;
38use crate::scenes::scene_drop_guards::_SceneDropGuard;
39use crate::sources::{ObsFilterGuardPair, ObsSourceTrait};
40use crate::unsafe_send::{NativeObjectId, SmartPointerSendable};
41use crate::utils::ObsDropGuard;
42use crate::{
43    impl_signal_manager, run_with_obs,
44    runtime::ObsRuntime,
45    unsafe_send::Sendable,
46    utils::{ObsError, ObsString},
47};
48
49#[derive(Debug)]
50struct _NoOpDropGuard;
51impl ObsDropGuard for _NoOpDropGuard {}
52
53pub(super) type SceneItemsBySource = Arc<
54    RwLock<
55        HashMap<
56            NativeObjectId,
57            (
58                Arc<dyn ObsSourceTrait>,
59                Vec<Arc<dyn SceneItemTrait + 'static>>,
60            ),
61        >,
62    >,
63>;
64
65#[derive(Debug, Clone)]
66/// This struct holds every ObsSourceRef that is attached to the scene by using `add_source`.
67pub struct ObsSceneRef {
68    name: ObsString,
69    attached_scene_items: SceneItemsBySource,
70    attached_groups: group::SceneGroups,
71    attached_filters: Arc<RwLock<Vec<ObsFilterGuardPair>>>,
72    runtime: ObsRuntime,
73    signals: Arc<ObsSceneSignals>,
74    scene: SmartPointerSendable<*mut obs_scene_t>,
75}
76
77impl_eq_of_ptr!(ObsSceneRef);
78
79impl ObsSceneRef {
80    pub(crate) fn new(name: ObsString, runtime: ObsRuntime) -> Result<Self, ObsError> {
81        let scene = run_with_obs!(runtime, (name), move || {
82            let name_ptr = name.as_ptr();
83
84            let scene_ptr = unsafe {
85                // Safety: name_ptr is valid because we have the name variable in scope.
86                libobs::obs_scene_create(name_ptr.0)
87            };
88            if scene_ptr.is_null() {
89                return Err(ObsError::NullPointer(None));
90            }
91
92            let source_ptr = unsafe {
93                // Safety: scene_ptr is valid because we just created it and its not null.
94                libobs::obs_scene_get_source(scene_ptr)
95            };
96
97            if source_ptr.is_null() {
98                unsafe {
99                    // Safety: scene_ptr is valid because we just created it and its not null.
100                    libobs::obs_scene_release(scene_ptr);
101                }
102                return Err(ObsError::NullPointer(None));
103            }
104
105            Ok(Sendable(scene_ptr))
106        })??;
107
108        let drop_guard = Arc::new(_SceneDropGuard::new(scene.clone(), runtime.clone()));
109        let scene = SmartPointerSendable::new(scene.0, drop_guard, runtime.native_registry());
110
111        let signals = Arc::new(ObsSceneSignals::new(&scene, runtime.clone())?);
112        Ok(Self {
113            name,
114            scene,
115            attached_scene_items: Arc::new(RwLock::new(HashMap::new())),
116            attached_groups: Arc::new(RwLock::new(Vec::new())),
117            attached_filters: Arc::new(RwLock::new(Vec::new())),
118            runtime,
119            signals,
120        })
121    }
122
123    #[deprecated = "Use ObsSceneRef::set_to_channel instead"]
124    pub fn add_and_set(&self, channel: u32) -> Result<(), ObsError> {
125        self.set_to_channel(channel)
126    }
127
128    /// Sets this scene to a given output channel.
129    /// There are 64
130    /// channels that you can assign scenes to, which will draw on top of each
131    /// other in ascending index order.
132    pub fn set_to_channel(&self, channel: u32) -> Result<(), ObsError> {
133        if channel >= libobs::MAX_CHANNELS {
134            return Err(ObsError::InvalidOperation(format!(
135                "Channel {} is out of bounds (max {})",
136                channel,
137                libobs::MAX_CHANNELS - 1
138            )));
139        }
140
141        let scene_source_ptr = self.get_scene_source_ptr()?;
142        run_with_obs!(self.runtime, (scene_source_ptr), move || unsafe {
143            // Safety: We are in the runtime and the struct hasn't been dropped yet, therefore the scene source must be valid.
144            // Also we are removing that pointer from the output source if this scene is dropped in the Drop guard
145            libobs::obs_set_output_source(channel, scene_source_ptr.0);
146        })
147    }
148
149    /// Removes a scene from a given output channel, for more info about channels see `set_to_channel`.
150    pub fn remove_from_channel(&self, channel: u32) -> Result<(), ObsError> {
151        if channel >= libobs::MAX_CHANNELS {
152            return Err(ObsError::InvalidOperation(format!(
153                "Channel {} is out of bounds (max {})",
154                channel,
155                libobs::MAX_CHANNELS - 1
156            )));
157        }
158
159        run_with_obs!(self.runtime, (), move || unsafe {
160            // Safety: We are in the runtime
161            libobs::obs_set_output_source(channel, std::ptr::null_mut());
162        })
163    }
164
165    /// Gets the underlying source pointer of this scene, which is used internally when setting it to a channel.
166    pub(crate) fn get_scene_source_ptr(&self) -> Result<Sendable<*mut obs_source_t>, ObsError> {
167        let scene_ptr = self.scene.clone();
168        run_with_obs!(self.runtime, (scene_ptr), move || {
169            unsafe {
170                // Safety: We are in the runtime and the scene ptr must be valid because we are using a smart pointer
171                Sendable(libobs::obs_scene_get_source(scene_ptr.get_ptr()))
172            }
173        })
174    }
175
176    pub(crate) fn as_ptr(&self) -> SmartPointerSendable<*mut obs_scene_t> {
177        self.scene.clone()
178    }
179
180    pub fn object_id(&self) -> NativeObjectId {
181        self.scene.native_id()
182    }
183
184    pub fn name(&self) -> ObsString {
185        self.name.clone()
186    }
187
188    pub fn runtime(&self) -> &ObsRuntime {
189        &self.runtime
190    }
191
192    pub fn signals(&self) -> Arc<ObsSceneSignals> {
193        self.signals.clone()
194    }
195
196    /// Adds an existing typed source and returns its managed scene-item handle.
197    pub fn add<T>(&mut self, source: T) -> Result<ObsSceneItemRef<T>, ObsError>
198    where
199        T: ObsSourceTrait + Clone + 'static,
200    {
201        SceneItemExtSceneTrait::add_source(self, source)
202    }
203
204    /// Creates a source from `SourceInfo`, adds it, and returns the typed scene-item handle.
205    pub fn add_new_source(
206        &mut self,
207        info: crate::utils::SourceInfo,
208    ) -> Result<ObsSceneItemRef<crate::sources::ObsSourceRef>, ObsError> {
209        SceneItemExtSceneTrait::add_and_create_source(self, info)
210    }
211
212    /// Removes a managed item immediately without consuming handles held by the caller.
213    pub fn remove_item<T: SceneItemTrait + ?Sized>(&mut self, item: &T) -> Result<(), ObsError> {
214        SceneItemExtSceneTrait::remove_item(self, item)
215    }
216
217    /// Returns all managed scene items for a source.
218    pub fn items_for_source<T>(&self, source: &T) -> Result<Vec<Arc<dyn SceneItemTrait>>, ObsError>
219    where
220        T: ObsSourceTrait + Clone,
221    {
222        SceneItemExtSceneTrait::items_for_source(self, source)
223    }
224
225    /// Removes every managed item from this scene.
226    pub fn clear(&mut self) -> Result<(), ObsError> {
227        SceneItemExtSceneTrait::remove_all_sources(self)?;
228        self.clear_groups()
229    }
230}
231
232impl_signal_manager!(|scene_ptr: SmartPointerSendable<*mut obs_scene_t>| unsafe {
233    // Safety: This is a smart pointer, so it is fine
234    let source_ptr = libobs::obs_scene_get_source(scene_ptr.get_ptr());
235
236    libobs::obs_source_get_signal_handler(source_ptr)
237}, ObsSceneSignals for *mut obs_scene_t, [
238    "item_add": {
239        struct ItemAddSignal {
240            POINTERS {
241                item: *mut libobs::obs_sceneitem_t,
242            }
243        }
244    },
245    "item_remove": {
246        struct ItemRemoveSignal {
247            POINTERS {
248                item: *mut libobs::obs_sceneitem_t,
249            }
250        }
251    },
252    "reorder": {},
253    "refresh": {},
254    "item_visible": {
255        struct ItemVisibleSignal {
256            visible: bool;
257            POINTERS {
258                item: *mut libobs::obs_sceneitem_t,
259            }
260        }
261    },
262    "item_locked": {
263        struct ItemLockedSignal {
264            locked: bool;
265            POINTERS {
266                item: *mut libobs::obs_sceneitem_t,
267            }
268        }
269    },
270    "item_select": {
271        struct ItemSelectSignal {
272            POINTERS {
273                item: *mut libobs::obs_sceneitem_t,
274            }
275        }
276    },
277    "item_deselect": {
278        struct ItemDeselectSignal {
279            POINTERS {
280                item: *mut libobs::obs_sceneitem_t,
281            }
282        }
283    },
284    "item_transform": {
285        struct ItemTransformSignal {
286            POINTERS {
287                item: *mut libobs::obs_sceneitem_t,
288            }
289        }
290    }
291]);