ferroui_browser/
browser_screens.rs1use 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
17fn 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
28fn 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#[derive(Clone)]
39pub struct BrowserScreenKey(JsObject);
40
41impl PartialEq for BrowserScreenKey {
42 fn eq(&self, other: &Self) -> bool {
43 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 }
54}
55
56pub 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 pub(crate) fn is_current(&self) -> bool {
74 self.is_current.get()
75 }
76
77 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
101pub struct BrowserScreens {
104 base: ScreensBase<BrowserScreenKey, BrowserScreen>,
105 weak_self: Weak<BrowserScreens>,
106 is_extended: Rc<Cell<bool>>,
107}
108
109impl BrowserScreens {
110 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 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}