1use crate::platform::{ITopLevelImpl, IWindowBaseImpl};
2use crate::primitives::TemplatedControlImpl;
3use crate::{
4 ContentControlImpl, ControlImpl, PixelPointEventArgs, Screens, TopLevel, TopLevelImpl, TopLevelImplExt,
5 WindowResizeReason, WindowResizedEventArgs,
6};
7use ferroui_base::input::InputElementImpl;
8use ferroui_base::interactivity::InteractiveImpl;
9use ferroui_base::layout::{ILayoutManager, LayoutHelper, LayoutableImpl};
10use ferroui_base::reactive::{Disposable, IDisposable};
11use ferroui_base::styling::Container;
12use ferroui_base::utilities::HandlerList;
13use ferroui_base::{
14 ferro_class, ferro_impl_classes, ferro_property, instantiate, DirectProperty, FerroObjectImpl,
15 FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, IFerroDependencyResolver, PixelPoint, Rect,
16 Ref, Size, StyledElementImpl, StyledProperty, Visual, VisualImpl, WeakRef,
17};
18use std::cell::{Cell, RefCell};
19use std::rc::Rc;
20
21#[repr(C)]
27pub struct WindowBase {
28 base: TopLevel,
29 window_base_impl: RefCell<Option<Rc<dyn IWindowBaseImpl>>>,
30 default_resolver: Cell<bool>,
33 has_executed_initial_layout_pass: Cell<bool>,
34 is_active: Cell<bool>,
35 ignore_visibility_changes: Cell<i32>,
36 owner: RefCell<Option<WeakRef<WindowBase>>>,
37 desktop_scaling_override: Cell<Option<f64>>,
38 activated: HandlerList<dyn Fn()>,
39 deactivated: HandlerList<dyn Fn()>,
40 position_changed: HandlerList<dyn Fn(&PixelPointEventArgs)>,
41 resized: HandlerList<dyn Fn(&WindowResizedEventArgs)>,
42}
43
44ferro_class! {
45 WindowBase: TopLevel, virtuals WindowBaseImpl: TopLevelImpl {
46 fn hide(this);
48 fn show(this);
50 fn on_resized(this, e: &WindowResizedEventArgs);
52 fn arrange_set_bounds(this, size: Size) -> Size;
55 fn is_visible_changed(this, e: &FerroPropertyChangedEventArgs<'_>);
57 }
58}
59
60ferro_impl_classes!(
61 WindowBase: StyledElementImpl,
62 VisualImpl,
63 InteractiveImpl,
64 InputElementImpl,
65 ControlImpl,
66 TemplatedControlImpl,
67 ContentControlImpl
68);
69
70impl FerroObjectImpl for WindowBase {
71 fn constructed(this: &Self) {
72 Self::parent_constructed(this);
73
74 let window_base_impl = this.window_base_impl.borrow().clone().expect("the window has a platform implementation");
75 let weak = this.to_ref().downgrade();
76 window_base_impl.set_activated(Some(Rc::new({
77 let weak = weak.clone();
78 move || {
79 if let Some(this) = weak.upgrade() {
80 this.handle_activated();
81 }
82 }
83 })));
84 window_base_impl.set_deactivated(Some(Rc::new({
85 let weak = weak.clone();
86 move || {
87 if let Some(this) = weak.upgrade() {
88 this.handle_deactivated();
89 this.raise_platform_deactivated();
90 }
91 }
92 })));
93 window_base_impl.set_position_changed(Some(Rc::new({
94 let weak = weak.clone();
95 move |pos| {
96 if let Some(this) = weak.upgrade() {
97 this.handle_position_changed(pos);
98 this.raise_platform_position_changed(pos);
99 }
100 }
101 })));
102
103 if this.default_resolver.get() {
104 this.create_platform_impl_binding(Self::topmost_property(), move |topmost: bool| {
105 if let Some(platform_impl) = weak.upgrade().and_then(|this| this.platform_impl()) {
106 platform_impl.set_topmost(topmost);
107 }
108 });
109 this.set_frame_size(window_base_impl.frame_size());
110 }
111 }
112
113 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
114 Self::parent_on_property_changed(this, change);
115
116 if change.property() == Visual::is_visible_property().as_property() {
117 let (_, new_value) = change.get_old_and_new_value::<bool>();
118 if let Some(visual_root) = this.visual_root() {
119 visual_root.set_is_visible(new_value);
120 }
121 this.is_visible_changed(change);
122 }
123 }
124}
125
126impl LayoutableImpl for WindowBase {
127 fn measure_core(this: &Self, available_size: Size) -> Size {
128 this.apply_styling();
129 this.apply_template();
130
131 let constraint = LayoutHelper::apply_layout_constraints(this, available_size);
132
133 Container::set_query_provider_size(this, constraint.width, constraint.height, Container::get_sizing(this));
134
135 this.measure_override(constraint)
136 }
137
138 fn arrange_core(this: &Self, final_rect: Rect) {
139 let constraint = this.arrange_set_bounds(final_rect.size());
140 let arrange_size = this.arrange_override(constraint);
141 this.set_bounds(Rect::from_position_size(final_rect.position(), arrange_size));
142 }
143}
144
145impl TopLevelImpl for WindowBase {
146 fn on_closed(this: &Self) {
147 this.on_unloaded_core();
150
151 Self::parent_on_closed(this);
152 }
153
154 fn on_opened(this: &Self) {
155 this.set_frame_size(this.platform_impl().and_then(|platform_impl| platform_impl.frame_size()));
156
157 this.schedule_on_loaded_core();
160
161 Self::parent_on_opened(this);
162 }
163
164 fn handle_closed(this: &Self) {
165 let _freeze = this.freeze_visibility_change_handling();
166
167 this.set_is_visible(false);
168
169 if this.is_active() {
170 this.handle_deactivated();
171 }
172
173 if this.is_focus_scope() {
174 this.focus_manager().remove_focus_root(this);
175 }
176
177 Self::parent_handle_closed(this);
178 *this.window_base_impl.borrow_mut() = None;
179 }
180
181 fn handle_resized(this: &Self, client_size: Size, reason: WindowResizeReason) {
182 this.set_frame_size(this.platform_impl().and_then(|platform_impl| platform_impl.frame_size()));
183
184 let client_size_changed = this.client_size() != client_size;
185
186 this.set_client_size(client_size);
187
188 this.on_resized(&WindowResizedEventArgs::new(client_size, reason));
189
190 if client_size_changed {
191 this.layout_manager().execute_layout_pass();
192 this.renderer().resized(client_size);
193 }
194 }
195}
196
197impl WindowBaseImpl for WindowBase {
198 fn hide(this: &Self) {
199 let _freeze = this.freeze_visibility_change_handling();
200
201 this.stop_rendering();
202 if let Some(platform_impl) = this.platform_impl() {
203 platform_impl.hide();
204 }
205 this.set_is_visible(false);
206 }
207
208 fn show(this: &Self) {
209 let _freeze = this.freeze_visibility_change_handling();
210
211 this.ensure_initialized();
212 this.apply_styling();
213 this.set_is_visible(true);
214
215 if !this.has_executed_initial_layout_pass.get() {
216 this.layout_manager().execute_initial_layout_pass();
217 this.has_executed_initial_layout_pass.set(true);
218 }
219
220 if let Some(platform_impl) = this.platform_impl() {
221 platform_impl.show(true, false);
222 }
223 this.start_rendering();
224 this.on_opened();
225 }
226
227 fn on_resized(this: &Self, e: &WindowResizedEventArgs) {
228 for (_, handler) in this.resized.snapshot().iter() {
229 handler(e);
230 }
231 }
232
233 fn arrange_set_bounds(_this: &Self, size: Size) -> Size {
234 size
235 }
236
237 fn is_visible_changed(this: &Self, e: &FerroPropertyChangedEventArgs<'_>) {
238 if this.ignore_visibility_changes.get() == 0 {
239 let (_, new_value) = e.get_old_and_new_value::<bool>();
240 if new_value {
241 this.show();
242 } else {
243 this.hide();
244 }
245 }
246 }
247}
248
249pub struct IgnoreVisibilityChangesGuard {
252 window_base: Ref<WindowBase>,
253}
254
255impl Drop for IgnoreVisibilityChangesGuard {
256 fn drop(&mut self) {
257 let count = &self.window_base.ignore_visibility_changes;
258 count.set(count.get() - 1);
259 }
260}
261
262ferroui_base::ferro_properties! { impl WindowBase {
263 ferro_property!(
264 pub fn is_active_property() -> DirectProperty<WindowBase, bool> {
266 FerroProperty::register_direct::<WindowBase, _>("IsActive", |o| o.is_active(), None, false)
267 }
268 );
269
270 ferro_property!(
271 pub fn owner_property() -> DirectProperty<WindowBase, Option<Ref<WindowBase>>> {
273 FerroProperty::register_direct::<WindowBase, _>("Owner", |o| o.owner(), None, None)
274 }
275 );
276
277 ferro_property!(
278 pub fn topmost_property() -> StyledProperty<bool> {
280 FerroProperty::register::<WindowBase, _>("Topmost", false)
281 }
282 );
283} }
284
285impl WindowBase {
286 fn static_constructor() {
287 Visual::is_visible_property().override_default_value::<WindowBase>(false);
288 }
289
290 pub fn construct(platform_impl: Rc<dyn IWindowBaseImpl>) -> Self {
293 let mut this = Self::construct_with_resolver(platform_impl, None);
294 this.default_resolver = Cell::new(true);
295 this
296 }
297
298 pub fn construct_with_resolver(
304 platform_impl: Rc<dyn IWindowBaseImpl>,
305 dependency_resolver: Option<Rc<dyn IFerroDependencyResolver>>,
306 ) -> Self {
307 let top_level_impl: Rc<dyn ITopLevelImpl> = platform_impl.clone();
308 Self {
309 base: TopLevel::construct_with_resolver(top_level_impl, dependency_resolver),
310 window_base_impl: RefCell::new(Some(platform_impl)),
311 default_resolver: Cell::new(false),
312 has_executed_initial_layout_pass: Cell::new(false),
313 is_active: Cell::new(false),
314 ignore_visibility_changes: Cell::new(0),
315 owner: RefCell::new(None),
316 desktop_scaling_override: Cell::new(None),
317 activated: HandlerList::new(),
318 deactivated: HandlerList::new(),
319 position_changed: HandlerList::new(),
320 resized: HandlerList::new(),
321 }
322 }
323
324 pub fn new(platform_impl: Rc<dyn IWindowBaseImpl>) -> Ref<Self> {
327 instantiate(Self::construct(platform_impl))
328 }
329
330 pub fn ignore_visibility_changes(&self) -> bool {
333 self.ignore_visibility_changes.get() > 0
334 }
335
336 pub fn freeze_visibility_change_handling(&self) -> IgnoreVisibilityChangesGuard {
339 self.ignore_visibility_changes.set(self.ignore_visibility_changes.get() + 1);
340 IgnoreVisibilityChangesGuard { window_base: self.to_ref() }
341 }
342
343 fn subscribe<F: ?Sized + 'static>(
344 &self,
345 select: fn(&WindowBase) -> &HandlerList<F>,
346 handler: Rc<F>,
347 ) -> Rc<dyn IDisposable> {
348 let token = select(self).add(handler);
349 let weak = self.to_ref().downgrade();
350 Disposable::create(move || {
351 if let Some(this) = weak.upgrade() {
352 select(&this).remove(token);
353 }
354 })
355 }
356
357 pub fn activated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
360 self.subscribe::<dyn Fn()>(|w| &w.activated, Rc::new(handler))
361 }
362
363 pub fn deactivated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
366 self.subscribe::<dyn Fn()>(|w| &w.deactivated, Rc::new(handler))
367 }
368
369 pub fn position_changed(&self, handler: impl Fn(&PixelPointEventArgs) + 'static) -> Rc<dyn IDisposable> {
372 self.subscribe::<dyn Fn(&PixelPointEventArgs)>(|w| &w.position_changed, Rc::new(handler))
373 }
374
375 pub fn resized(&self, handler: impl Fn(&WindowResizedEventArgs) + 'static) -> Rc<dyn IDisposable> {
378 self.subscribe::<dyn Fn(&WindowResizedEventArgs)>(|w| &w.resized, Rc::new(handler))
379 }
380
381 pub fn platform_impl(&self) -> Option<Rc<dyn IWindowBaseImpl>> {
384 TopLevel::platform_impl(self)?;
385 self.window_base_impl.borrow().clone()
386 }
387
388 pub fn is_active(&self) -> bool {
390 self.is_active.get()
391 }
392
393 fn set_is_active(&self, value: bool) {
394 self.set_and_raise_cell(Self::is_active_property(), &self.is_active, value);
395 }
396
397 pub fn screens(&self) -> Rc<Screens> {
402 TopLevel::screens(self).expect("Windowing backend wasn't properly initialized.")
403 }
404
405 pub fn owner(&self) -> Option<Ref<WindowBase>> {
407 self.owner.borrow().as_ref().and_then(WeakRef::upgrade)
408 }
409
410 pub fn set_owner(&self, value: Option<Ref<WindowBase>>) {
413 let old = self.owner();
414 if old == value {
415 return;
416 }
417 *self.owner.borrow_mut() = value.as_ref().map(Ref::downgrade);
418 self.raise_direct_property_changed(Self::owner_property(), &old, &value);
419 }
420
421 pub fn topmost(&self) -> bool {
423 self.get_value(Self::topmost_property())
424 }
425
426 pub fn set_topmost(&self, value: bool) {
427 self.set_value(Self::topmost_property(), value)
428 }
429
430 pub fn desktop_scaling(&self) -> f64 {
432 self.desktop_scaling_override
433 .get()
434 .or_else(|| self.platform_impl().map(|platform_impl| platform_impl.desktop_scaling()))
435 .unwrap_or(1.0)
436 }
437
438 pub fn desktop_scaling_override(&self) -> Option<f64> {
441 self.desktop_scaling_override.get()
442 }
443
444 pub fn set_desktop_scaling_override(&self, value: Option<f64>) {
445 self.desktop_scaling_override.set(value)
446 }
447
448 pub fn activate(&self) {
450 if let Some(platform_impl) = self.platform_impl() {
451 platform_impl.activate();
452 }
453 }
454
455 pub fn ensure_initialized(&self) {
458 if !self.is_initialized() {
459 self.begin_init();
460 self.end_init();
461 }
462 }
463
464 fn handle_position_changed(&self, pos: PixelPoint) {
465 let e = PixelPointEventArgs::new(pos);
466 for (_, handler) in self.position_changed.snapshot().iter() {
467 handler(&e);
468 }
469 }
470
471 fn handle_activated(&self) {
472 for (_, handler) in self.activated.snapshot().iter() {
473 handler();
474 }
475
476 if self.is_focus_scope() {
477 self.focus_manager().set_focus_scope(&self.to_ref().upcast());
478 }
479
480 self.set_is_active(true);
481 }
482
483 fn handle_deactivated(&self) {
484 self.set_is_active(false);
485
486 for (_, handler) in self.deactivated.snapshot().iter() {
487 handler();
488 }
489 }
490}