Skip to main content

ferroui_native/
screen_impl.rs

1//! The screens of the system.
2
3use crate::frn_string::frn_string_to_string;
4use crate::helpers::*;
5use crate::interop::*;
6use crate::top_level_impl::top_level_of;
7use ferroui_controls::platform::{
8    ITopLevelImpl, PlatformHandle, PlatformScreen, Screen, ScreenOrientation, ScreensBase, ScreensBaseImpl,
9    ScreensBaseImplExt,
10};
11use ferroui_microcom::{ComPtr, HResult};
12use std::cell::RefCell;
13use std::rc::{Rc, Weak};
14
15/// The orientation of a native screen; `None` for a value the toolkit does
16/// not know.
17fn to_screen_orientation(orientation: FrnScreenOrientation) -> Option<ScreenOrientation> {
18    Some(match orientation.0 {
19        0 => ScreenOrientation::None,
20        1 => ScreenOrientation::Landscape,
21        2 => ScreenOrientation::Portrait,
22        3 => ScreenOrientation::LandscapeFlipped,
23        4 => ScreenOrientation::PortraitFlipped,
24        _ => return None,
25    })
26}
27
28/// A screen identified by its `CGDirectDisplayID`.
29pub struct NativeScreen {
30    base: PlatformScreen,
31    display_id: u32,
32}
33
34impl NativeScreen {
35    fn new(display_id: u32) -> Self {
36        Self {
37            base: PlatformScreen::new(Rc::new(PlatformHandle::new(display_id as isize, Some("CGDirectDisplayID")))),
38            display_id,
39        }
40    }
41
42    fn refresh(&self, native: &IFrnScreens) {
43        let mut localized_name: *mut std::ffi::c_void = std::ptr::null_mut();
44        // SAFETY: `localized_name` is a valid out pointer; the native side
45        // stores null or a string with one reference owned by the caller.
46        let screen = unsafe { native.get_screen(self.display_id, &mut localized_name) }.check();
47
48        self.base.set_is_primary(screen.is_primary);
49        self.base.set_scaling(screen.scaling as f64);
50        self.base.set_bounds(to_ferro_pixel_rect(screen.bounds));
51        self.base.set_working_area(to_ferro_pixel_rect(screen.working_area));
52        self.base.set_current_orientation(match to_screen_orientation(screen.orientation) {
53            Some(orientation) => orientation,
54            None => panic!("Specified argument was out of the range of valid values: {:?}", screen.orientation),
55        });
56
57        // SAFETY: see above; the reference is released when `name` drops.
58        let name = unsafe { ComPtr::from_raw(localized_name as *mut IFrnString) };
59        self.base.set_display_name(name.and_then(|name| frn_string_to_string(&name)));
60    }
61}
62
63impl AsRef<PlatformScreen> for NativeScreen {
64    fn as_ref(&self) -> &PlatformScreen {
65        &self.base
66    }
67}
68
69/// The screens implementation of the macOS backend.
70pub struct ScreenImpl {
71    base: ScreensBase<u32, NativeScreen>,
72    native: RefCell<Option<ComPtr<IFrnScreens>>>,
73}
74
75struct ScreenEvents(Weak<ScreenImpl>);
76
77impl IFrnScreenEventsImpl for ScreenEvents {
78    fn on_changed(&self) -> Result<(), HResult> {
79        crate::callback_base::guard(Ok(()), || {
80            if let Some(screen_impl) = self.0.upgrade() {
81                screen_impl.on_changed();
82            }
83            Ok(())
84        })
85    }
86}
87
88impl ScreenImpl {
89    /// Creates the screens; `factory` creates the native screens object for
90    /// the events callback it is given.
91    pub fn new(factory: impl FnOnce(&IFrnScreenEvents) -> Option<ComPtr<IFrnScreens>>) -> Rc<ScreenImpl> {
92        Rc::new_cyclic(|weak_self| {
93            let events = IFrnScreenEvents::from_impl(ScreenEvents(weak_self.clone()));
94            ScreenImpl { base: ScreensBase::new(), native: RefCell::new(factory(&events)) }
95        })
96    }
97
98    #[track_caller]
99    fn native(&self) -> ComPtr<IFrnScreens> {
100        match self.native.borrow().clone() {
101            Some(native) => native,
102            None => panic!("Cannot access a disposed object: the native screens"),
103        }
104    }
105
106    /// Releases the native screens object.
107    pub fn dispose(&self) {
108        let native = self.native.borrow_mut().take();
109        drop(native);
110    }
111}
112
113impl ScreensBaseImpl for ScreenImpl {
114    type Key = u32;
115    type Screen = NativeScreen;
116
117    fn screens_base(&self) -> &ScreensBase<u32, NativeScreen> {
118        &self.base
119    }
120
121    fn get_screen_count(&self) -> i32 {
122        // SAFETY: a null pointer asks for the count only.
123        unsafe { self.native().get_screen_ids(std::ptr::null_mut()) }.check()
124    }
125
126    fn get_all_screen_keys(&self) -> Vec<u32> {
127        let native = self.native();
128        // SAFETY: a null pointer asks for the count only.
129        let screen_count = unsafe { native.get_screen_ids(std::ptr::null_mut()) }.check();
130        let mut display_ids = vec![0u32; screen_count.max(0) as usize];
131        if !display_ids.is_empty() {
132            // SAFETY: the buffer holds as many ids as the native side just
133            // reported; it writes at most that many.
134            unsafe { native.get_screen_ids(display_ids.as_mut_ptr()) }.check();
135        }
136
137        display_ids
138    }
139
140    fn create_screen_from_key(&self, key: &u32) -> Rc<NativeScreen> {
141        Rc::new(NativeScreen::new(*key))
142    }
143
144    fn screen_changed(&self, screen: &Rc<NativeScreen>) {
145        screen.refresh(&self.native());
146    }
147
148    fn screen_from_top_level_core(&self, top_level: &dyn ITopLevelImpl) -> Option<Rc<Screen>> {
149        let Some(top_level) = top_level_of(top_level) else {
150            panic!("The top-level belongs to a different windowing platform.");
151        };
152        let display_id = top_level.native()?.get_current_display_id().check();
153        let screen = self.try_get_screen(&display_id)?;
154        Some((*screen).as_ref().screen().clone())
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn orientations() {
164        assert_eq!(to_screen_orientation(FrnScreenOrientation::UnknownOrientation), Some(ScreenOrientation::None));
165        assert_eq!(to_screen_orientation(FrnScreenOrientation::Landscape), Some(ScreenOrientation::Landscape));
166        assert_eq!(to_screen_orientation(FrnScreenOrientation::Portrait), Some(ScreenOrientation::Portrait));
167        assert_eq!(
168            to_screen_orientation(FrnScreenOrientation::LandscapeFlipped),
169            Some(ScreenOrientation::LandscapeFlipped)
170        );
171        assert_eq!(
172            to_screen_orientation(FrnScreenOrientation::PortraitFlipped),
173            Some(ScreenOrientation::PortraitFlipped)
174        );
175        assert_eq!(to_screen_orientation(FrnScreenOrientation(5)), None);
176    }
177}