Skip to main content

ferroui_browser/
browser_screens.rs

1use crate::interop::screen_helper as browser_screen_helper;
2use crate::interop::JsObject;
3use crate::js_object_control_handle::JsObjectPlatformHandle;
4use crate::windowing_platform::BrowserWindowingPlatform;
5use ferroui_base::logging::{LogArea, LogEventLevel, Logger};
6use ferroui_base::{PixelPoint, PixelRect};
7use ferroui_controls::platform::{
8    IScreenImpl, ITopLevelImpl, PlatformScreen, Screen, ScreenHelper, ScreenOrientation, ScreensBase,
9    ScreensBaseImpl, ScreensBaseImplExt,
10};
11use std::cell::{Cell, RefCell};
12use std::future::Future;
13use std::hash::{Hash, Hasher};
14use std::pin::Pin;
15use std::rc::{Rc, Weak};
16
17/// The orientation of a value of `ScreenOrientation` of the page.
18fn to_screen_orientation(orientation: i32) -> ScreenOrientation {
19    match orientation {
20        1 => ScreenOrientation::Landscape,
21        2 => ScreenOrientation::Portrait,
22        4 => ScreenOrientation::LandscapeFlipped,
23        8 => ScreenOrientation::PortraitFlipped,
24        _ => ScreenOrientation::None,
25    }
26}
27
28/// A rectangle of the page given as x, y, width and height; empty when the
29/// page gives fewer numbers.
30fn to_pixel_rect(values: &[f64]) -> PixelRect {
31    match values {
32        [x, y, width, height, ..] => PixelRect::new(*x as i32, *y as i32, *width as i32, *height as i32),
33        _ => PixelRect::default(),
34    }
35}
36
37/// Identifies a screen of the page by its object.
38#[derive(Clone)]
39pub struct BrowserScreenKey(JsObject);
40
41impl PartialEq for BrowserScreenKey {
42    fn eq(&self, other: &Self) -> bool {
43        // The same object of the page (`===`).
44        self.0 == other.0
45    }
46}
47
48impl Eq for BrowserScreenKey {}
49
50impl Hash for BrowserScreenKey {
51    fn hash<H: Hasher>(&self, _state: &mut H) {
52        // An object of the page has nothing to hash; a page has a handful of screens.
53    }
54}
55
56/// A screen of the page.
57pub struct BrowserScreen {
58    base: PlatformScreen,
59    screen: JsObject,
60    is_current: Cell<bool>,
61}
62
63impl BrowserScreen {
64    fn new(screen: JsObject) -> Self {
65        Self {
66            base: PlatformScreen::new(Rc::new(JsObjectPlatformHandle::new(screen.clone()))),
67            screen,
68            is_current: Cell::new(false),
69        }
70    }
71
72    /// Whether the window is on the screen.
73    pub(crate) fn is_current(&self) -> bool {
74        self.is_current.get()
75    }
76
77    /// Reads the properties of the screen from the page.
78    pub fn refresh(&self) {
79        self.is_current.set(browser_screen_helper::is_current(&self.screen));
80        self.base.set_display_name(browser_screen_helper::get_display_name(&self.screen));
81        self.base.set_scaling(browser_screen_helper::get_scaling(&self.screen));
82        self.base.set_is_primary(browser_screen_helper::is_primary(&self.screen));
83        self.base.set_current_orientation(to_screen_orientation(browser_screen_helper::get_current_orientation(
84            &self.screen,
85        )));
86        self.base.set_bounds(to_pixel_rect(&browser_screen_helper::get_bounds(&self.screen)));
87        self.base.set_working_area(to_pixel_rect(&browser_screen_helper::get_working_area(&self.screen)));
88    }
89}
90
91impl AsRef<PlatformScreen> for BrowserScreen {
92    fn as_ref(&self) -> &PlatformScreen {
93        &self.base
94    }
95}
96
97ferroui_base::ferro_thread_local! {
98    static INSTANCE: RefCell<Option<Rc<BrowserScreens>>> = const { RefCell::new(None) };
99}
100
101/// The screens of the page: the screen of the window, and every screen
102/// once the page has been given the details of the screens.
103pub struct BrowserScreens {
104    base: ScreensBase<BrowserScreenKey, BrowserScreen>,
105    weak_self: Weak<BrowserScreens>,
106    is_extended: Rc<Cell<bool>>,
107}
108
109impl BrowserScreens {
110    /// The screens of the page, created on first use.
111    pub fn instance() -> Rc<BrowserScreens> {
112        if let Some(instance) = INSTANCE.with(|instance| instance.borrow().clone()) {
113            return instance;
114        }
115
116        let instance = Self::new();
117        INSTANCE.with(|slot| *slot.borrow_mut() = Some(instance.clone()));
118        instance
119    }
120
121    fn new() -> Rc<Self> {
122        let this = Rc::new_cyclic(|weak_self| Self {
123            base: ScreensBase::new(),
124            weak_self: weak_self.clone(),
125            is_extended: Rc::new(Cell::new(false)),
126        });
127
128        browser_screen_helper::subscribe_on_changed(&BrowserWindowingPlatform::global_this());
129        browser_screen_helper::check_permissions(&BrowserWindowingPlatform::global_this());
130
131        this
132    }
133
134    /// The page reported a change of the screens.
135    pub(crate) fn on_changed(&self) {
136        if let Some(this) = self.weak_self.upgrade() {
137            ScreensBaseImplExt::on_changed(&this);
138        }
139    }
140}
141
142impl ScreensBaseImpl for BrowserScreens {
143    type Key = BrowserScreenKey;
144    type Screen = BrowserScreen;
145
146    fn screens_base(&self) -> &ScreensBase<BrowserScreenKey, BrowserScreen> {
147        &self.base
148    }
149
150    fn get_all_screen_keys(&self) -> Vec<BrowserScreenKey> {
151        browser_screen_helper::get_all_screens(&BrowserWindowingPlatform::global_this())
152            .into_iter()
153            .map(BrowserScreenKey)
154            .collect()
155    }
156
157    fn create_screen_from_key(&self, key: &BrowserScreenKey) -> Rc<BrowserScreen> {
158        Rc::new(BrowserScreen::new(key.0.clone()))
159    }
160
161    fn screen_changed(&self, screen: &Rc<BrowserScreen>) {
162        screen.refresh();
163    }
164
165    fn screen_from_top_level_core(&self, _top_level: &dyn ITopLevelImpl) -> Option<Rc<Screen>> {
166        self.all_platform_screens().into_iter().find(|screen| screen.is_current()).map(|screen| screen.base.screen().clone())
167    }
168
169    fn screen_from_point_core(&self, point: PixelPoint) -> Option<Rc<Screen>> {
170        if self.is_extended.get() {
171            ScreenHelper::screen_from_point(point, &IScreenImpl::all_screens(self))
172        } else {
173            None
174        }
175    }
176
177    fn screen_from_rect_core(&self, rect: PixelRect) -> Option<Rc<Screen>> {
178        if self.is_extended.get() {
179            ScreenHelper::screen_from_rect(rect, &IScreenImpl::all_screens(self))
180        } else {
181            None
182        }
183    }
184
185    fn request_screen_details_core(&self) -> Pin<Box<dyn Future<Output = bool>>> {
186        let is_extended = self.is_extended.clone();
187        Box::pin(async move {
188            match browser_screen_helper::request_detailed_screens(&BrowserWindowingPlatform::global_this()).await {
189                Ok(result) => {
190                    is_extended.set(result);
191                    result
192                }
193                Err(error) => {
194                    if let Some(logger) = Logger::try_get(LogEventLevel::Warning, LogArea::BROWSER_PLATFORM) {
195                        logger.log(None, &format!("Failed to get extended screen details: {error}"));
196                    }
197                    false
198                }
199            }
200        })
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn the_orientations_of_the_page_are_the_screen_orientations() {
210        assert_eq!(ScreenOrientation::None, to_screen_orientation(0));
211        assert_eq!(ScreenOrientation::Landscape, to_screen_orientation(1));
212        assert_eq!(ScreenOrientation::Portrait, to_screen_orientation(2));
213        assert_eq!(ScreenOrientation::LandscapeFlipped, to_screen_orientation(4));
214        assert_eq!(ScreenOrientation::PortraitFlipped, to_screen_orientation(8));
215        assert_eq!(ScreenOrientation::None, to_screen_orientation(3));
216    }
217
218    #[test]
219    fn rectangles_are_read_as_x_y_width_height() {
220        assert_eq!(PixelRect::new(10, 20, 1920, 1080), to_pixel_rect(&[10.0, 20.0, 1920.0, 1080.0]));
221        assert_eq!(PixelRect::new(-1920, 0, 1920, 1080), to_pixel_rect(&[-1920.5, 0.0, 1920.0, 1080.0]));
222        assert_eq!(PixelRect::default(), to_pixel_rect(&[1.0, 2.0]));
223    }
224}