1#![allow(non_snake_case)]
2
3use std::cell::RefCell;
4use std::collections::VecDeque;
5use std::rc::Rc;
6use std::sync::atomic::{AtomicUsize, Ordering};
7
8use repose_core::{Dp, DpOffset, DpSize, Rect, Size, Vec2};
9
10#[derive(Clone, Copy, Debug, Default, PartialEq)]
11pub struct ScreenInsets {
12 pub left: f32,
13 pub top: f32,
14 pub right: f32,
15 pub bottom: f32,
16}
17
18#[derive(Clone, Debug, PartialEq)]
19pub struct Screen {
20 pub id: String,
21 pub bounds: Rect,
22 pub insets: ScreenInsets,
23}
24
25impl Screen {
26 pub fn new(id: impl Into<String>, bounds: Rect, insets: ScreenInsets) -> Self {
27 Self {
28 id: id.into(),
29 bounds,
30 insets,
31 }
32 }
33 pub fn available_bounds(&self) -> Rect {
34 Rect {
35 x: self.bounds.x + self.insets.left,
36 y: self.bounds.y + self.insets.top,
37 w: (self.bounds.w - self.insets.left - self.insets.right).max(0.0),
38 h: (self.bounds.h - self.insets.top - self.insets.bottom).max(0.0),
39 }
40 }
41 pub fn primary(host_bounds: Rect) -> Self {
42 Self {
43 id: "primary".into(),
44 bounds: host_bounds,
45 insets: ScreenInsets::default(),
46 }
47 }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
51pub enum WindowPlacement {
52 #[default]
53 Floating,
54 Maximized,
55 Fullscreen,
56}
57
58#[derive(Clone, Debug)]
59pub struct WindowMetrics {
60 pub screen: Screen,
61 pub bounds: Rect,
62 pub insets: ScreenInsets,
63}
64impl WindowMetrics {
65 pub fn new(screen: Screen, bounds: Rect, insets: ScreenInsets) -> Self {
66 Self {
67 screen,
68 bounds,
69 insets,
70 }
71 }
72}
73
74pub struct WindowScreenProviderScope {
75 pub screens: Vec<Screen>,
76 pub default_screen: Screen,
77}
78impl WindowScreenProviderScope {
79 pub fn new(screens: Vec<Screen>, default_screen: Screen) -> Self {
80 Self {
81 screens,
82 default_screen,
83 }
84 }
85 pub fn eval(&self, p: &WindowScreenProvider) -> Screen {
86 p.get_screen(self)
87 }
88}
89
90#[derive(Clone)]
91pub struct WindowScreenProvider {
92 inner: Rc<dyn Fn(&WindowScreenProviderScope) -> Screen>,
93}
94impl WindowScreenProvider {
95 pub fn new<F: Fn(&WindowScreenProviderScope) -> Screen + 'static>(f: F) -> Self {
96 Self { inner: Rc::new(f) }
97 }
98 pub fn get_screen(&self, scope: &WindowScreenProviderScope) -> Screen {
99 (self.inner)(scope)
100 }
101 pub fn default_screen() -> Self {
102 Self::new(|s| s.default_screen.clone())
103 }
104 pub fn with_id(id: impl Into<String>) -> Self {
105 let wanted = id.into();
106 Self::new(move |s| {
107 s.screens
108 .iter()
109 .find(|x| x.id == wanted)
110 .cloned()
111 .unwrap_or_else(|| s.default_screen.clone())
112 })
113 }
114}
115impl Default for WindowScreenProvider {
116 fn default() -> Self {
117 Self::default_screen()
118 }
119}
120
121#[derive(Clone, Copy, Debug)]
122pub struct WindowConstraints {
123 pub min_width: f32,
124 pub max_width: f32,
125 pub min_height: f32,
126 pub max_height: f32,
127}
128impl WindowConstraints {
129 pub const INFINITY: f32 = f32::INFINITY;
130}
131
132pub struct WindowGeometryProviderScope<'a> {
133 pub parent_metrics: Option<WindowMetrics>,
134 pub window_metrics: WindowMetrics,
135 pub measure_content: Rc<dyn Fn(WindowConstraints) -> Size + 'a>,
136}
137impl<'a> WindowGeometryProviderScope<'a> {
138 pub fn new(
139 parent_metrics: Option<WindowMetrics>,
140 window_metrics: WindowMetrics,
141 measure_content: impl Fn(WindowConstraints) -> Size + 'a,
142 ) -> Self {
143 Self {
144 parent_metrics,
145 window_metrics,
146 measure_content: Rc::new(measure_content),
147 }
148 }
149 pub fn content_to_window_size(&self, c: Size) -> Size {
150 let ins = self.window_metrics.insets;
151 let raw = Size {
152 width: c.width + ins.left + ins.right,
153 height: c.height + ins.top + ins.bottom,
154 };
155 let avail = self.window_metrics.screen.available_bounds();
156 Size {
157 width: raw.width.min(avail.w),
158 height: raw.height.min(avail.h),
159 }
160 }
161 pub fn measure_window_content(&self, min_w: f32, max_w: f32, min_h: f32, max_h: f32) -> Size {
162 (self.measure_content)(WindowConstraints {
163 min_width: min_w.max(0.0),
164 max_width: max_w,
165 min_height: min_h.max(0.0),
166 max_height: max_h,
167 })
168 }
169 pub(crate) fn preferred_width_for_height(&self, h: f32) -> f32 {
170 self.measure_window_content(0.0, WindowConstraints::INFINITY, h, h)
171 .width
172 }
173 pub(crate) fn preferred_height_for_width(&self, w: f32) -> f32 {
174 self.measure_window_content(w, w, 0.0, WindowConstraints::INFINITY)
175 .height
176 }
177 pub fn eval_size(&self, p: &WindowSizeProvider) -> Size {
178 p.get_size(self)
179 }
180 pub fn eval_position(&self, p: &WindowPositionProvider, sz: Size) -> Vec2 {
181 p.get_position(self, sz)
182 }
183 pub fn eval_bounds(&self, p: &WindowBoundsProvider) -> Rect {
184 p.get_bounds(self)
185 }
186}
187
188#[derive(Clone)]
189pub struct WindowBoundsProvider {
190 inner: Rc<dyn Fn(&WindowGeometryProviderScope) -> Rect>,
191}
192impl WindowBoundsProvider {
193 pub fn new<F: Fn(&WindowGeometryProviderScope) -> Rect + 'static>(f: F) -> Self {
194 Self { inner: Rc::new(f) }
195 }
196 pub fn get_bounds(&self, s: &WindowGeometryProviderScope) -> Rect {
197 let r = (self.inner)(s);
198 debug_assert!(r.w.is_finite() && r.h.is_finite() && r.x.is_finite() && r.y.is_finite());
199 r
200 }
201 pub fn default() -> Self {
202 Self::new_provider(
203 WindowSizeProvider::default(),
204 WindowPositionProvider::default(),
205 )
206 }
207 pub fn absolute(rect: Rect) -> Self {
208 Self::new(move |_| rect)
209 }
210 pub fn new_provider(
211 size_provider: WindowSizeProvider,
212 position_provider: WindowPositionProvider,
213 ) -> Self {
214 Self::new(move |scope| {
215 let sz = size_provider.get_size(scope);
216 let pos = position_provider.get_position(scope, sz);
217 Rect {
218 x: pos.x,
219 y: pos.y,
220 w: sz.width,
221 h: sz.height,
222 }
223 })
224 }
225}
226impl Default for WindowBoundsProvider {
227 fn default() -> Self {
228 Self::default()
229 }
230}
231
232static CASCADE_COUNTER: AtomicUsize = AtomicUsize::new(0);
233
234#[derive(Clone)]
235pub struct WindowPositionProvider {
236 inner: Rc<dyn Fn(&WindowGeometryProviderScope, Size) -> Vec2>,
237}
238impl WindowPositionProvider {
239 pub fn new<F: Fn(&WindowGeometryProviderScope, Size) -> Vec2 + 'static>(f: F) -> Self {
240 Self { inner: Rc::new(f) }
241 }
242 pub fn get_position(&self, s: &WindowGeometryProviderScope, sz: Size) -> Vec2 {
243 let v = (self.inner)(s, sz);
244 debug_assert!(v.x.is_finite() && v.y.is_finite());
245 v
246 }
247 pub fn default() -> Self {
248 Self::new(|_, _| {
249 let n = CASCADE_COUNTER.fetch_add(1, Ordering::Relaxed) as f32;
250 Vec2 {
251 x: 40.0 + (n * 24.0) % 200.0,
252 y: 40.0 + (n * 24.0) % 200.0,
253 }
254 })
255 }
256 pub fn current() -> Self {
257 Self::new(|s, _| Vec2 {
258 x: s.window_metrics.bounds.x,
259 y: s.window_metrics.bounds.y,
260 })
261 }
262 pub fn centered_on_screen() -> Self {
263 Self::centered_in_screen_bounds(Vec2::ZERO)
264 }
265 pub fn centered_in_screen_bounds(offset: Vec2) -> Self {
266 Self::new(move |s, sz| {
267 let avail = s.window_metrics.screen.available_bounds();
268 Vec2 {
269 x: avail.x + (avail.w - sz.width) / 2.0 + offset.x,
270 y: avail.y + (avail.h - sz.height) / 2.0 + offset.y,
271 }
272 })
273 }
274 pub fn centered_in_screen() -> Self {
275 Self::new(|s, sz| {
276 let b = s.window_metrics.screen.bounds;
277 Vec2 {
278 x: b.x + (b.w - sz.width) / 2.0,
279 y: b.y + (b.h - sz.height) / 2.0,
280 }
281 })
282 }
283 pub fn aligned_to_screen_available(ax: f32, ay: f32, offset: Vec2) -> Self {
284 Self::new(move |s, sz| {
285 let avail = s.window_metrics.screen.available_bounds();
286 Vec2 {
287 x: avail.x + (avail.w - sz.width) * ax.clamp(0.0, 1.0) + offset.x,
288 y: avail.y + (avail.h - sz.height) * ay.clamp(0.0, 1.0) + offset.y,
289 }
290 })
291 }
292 pub fn absolute(pos: Vec2) -> Self {
293 Self::new(move |_, _| pos)
294 }
295 pub fn absolute_xy(x: f32, y: f32) -> Self {
296 Self::absolute(Vec2 { x, y })
297 }
298 pub fn aligned_to_parent(
299 anchor_x: f32,
300 anchor_y: f32,
301 align_x: f32,
302 align_y: f32,
303 offset: Vec2,
304 exclude_parent_insets: bool,
305 ) -> Self {
306 Self::new(move |s, sz| {
307 let pm = s
308 .parent_metrics
309 .as_ref()
310 .expect("AlignedToParentWindow requires parent_metrics");
311 let parent_bounds = if exclude_parent_insets {
312 let ins = pm.insets;
313 Rect {
314 x: pm.bounds.x + ins.left,
315 y: pm.bounds.y + ins.top,
316 w: (pm.bounds.w - ins.left - ins.right).max(0.0),
317 h: (pm.bounds.h - ins.top - ins.bottom).max(0.0),
318 }
319 } else {
320 pm.bounds
321 };
322 let anchor = Vec2 {
323 x: parent_bounds.x + parent_bounds.w * anchor_x.clamp(0.0, 1.0),
324 y: parent_bounds.y + parent_bounds.h * anchor_y.clamp(0.0, 1.0),
325 };
326 let target = Rect {
327 x: anchor.x - sz.width,
328 y: anchor.y - sz.height,
329 w: sz.width * 2.0,
330 h: sz.height * 2.0,
331 };
332 Vec2 {
333 x: target.x + (target.w - sz.width) * align_x.clamp(0.0, 1.0) + offset.x,
334 y: target.y + (target.h - sz.height) * align_y.clamp(0.0, 1.0) + offset.y,
335 }
336 })
337 }
338 pub fn centered_in_parent(offset: Vec2) -> Self {
339 Self::aligned_to_parent(0.5, 0.5, 0.5, 0.5, offset, false)
340 }
341}
342impl Default for WindowPositionProvider {
343 fn default() -> Self {
344 Self::default()
345 }
346}
347
348#[derive(Clone)]
349pub struct WindowSizeProvider {
350 inner: Rc<dyn Fn(&WindowGeometryProviderScope) -> Size>,
351}
352impl WindowSizeProvider {
353 pub fn new<F: Fn(&WindowGeometryProviderScope) -> Size + 'static>(f: F) -> Self {
354 Self { inner: Rc::new(f) }
355 }
356 pub fn get_size(&self, s: &WindowGeometryProviderScope) -> Size {
357 let sz = (self.inner)(s);
358 debug_assert!(
359 sz.width.is_finite() && sz.height.is_finite() && sz.width >= 0.0 && sz.height >= 0.0
360 );
361 sz
362 }
363 pub fn default() -> Self {
364 Self::fixed(Size {
365 width: 800.0,
366 height: 600.0,
367 })
368 }
369 pub fn current() -> Self {
370 Self::new(|s| Size {
371 width: s.window_metrics.bounds.w,
372 height: s.window_metrics.bounds.h,
373 })
374 }
375 pub fn fixed(sz: Size) -> Self {
376 Self::new(move |_| sz)
377 }
378 pub fn fixed_xy(w: f32, h: f32) -> Self {
379 Self::fixed(Size {
380 width: w,
381 height: h,
382 })
383 }
384 pub fn unconstrained() -> Self {
385 Self::new(|s| {
386 let avail = s.window_metrics.screen.available_bounds();
387 let unconstrained = s.content_to_window_size(s.measure_window_content(
388 0.0,
389 WindowConstraints::INFINITY,
390 0.0,
391 WindowConstraints::INFINITY,
392 ));
393 let w_fits = unconstrained.width <= avail.w;
394 let h_fits = unconstrained.height <= avail.h;
395 if w_fits && h_fits {
396 unconstrained
397 } else if !w_fits && !h_fits {
398 Size {
399 width: avail.w,
400 height: avail.h,
401 }
402 } else if !w_fits {
403 let h = s.preferred_height_for_width(avail.w);
404 s.content_to_window_size(Size {
405 width: avail.w,
406 height: h,
407 })
408 } else {
409 let w = s.preferred_width_for_height(avail.h);
410 s.content_to_window_size(Size {
411 width: w,
412 height: avail.h,
413 })
414 }
415 })
416 }
417 pub fn preferred_width(h: f32) -> Self {
418 Self::new(move |s| {
419 let w = s.preferred_width_for_height(h);
420 s.content_to_window_size(Size {
421 width: w,
422 height: h,
423 })
424 })
425 }
426 pub fn preferred_height(w: f32) -> Self {
427 Self::new(move |s| {
428 let h = s.preferred_height_for_width(w);
429 s.content_to_window_size(Size {
430 width: w,
431 height: h,
432 })
433 })
434 }
435}
436impl Default for WindowSizeProvider {
437 fn default() -> Self {
438 Self::default()
439 }
440}
441
442pub struct WindowState {
443 pub is_initialized: bool,
444 screen_id: Option<String>,
445 placement: Option<WindowPlacement>,
446 is_minimized: Option<bool>,
447 bounds: Option<Rect>,
448 pending_screen: Option<WindowScreenProvider>,
449 pending_placement: Option<WindowPlacement>,
450 pending_minimized: Option<bool>,
451 pending_bounds: VecDeque<WindowBoundsProvider>,
452}
453impl WindowState {
454 pub fn create_uninitialized() -> Self {
455 Self {
456 is_initialized: false,
457 screen_id: None,
458 placement: None,
459 is_minimized: None,
460 bounds: None,
461 pending_screen: None,
462 pending_placement: None,
463 pending_minimized: None,
464 pending_bounds: VecDeque::new(),
465 }
466 }
467 pub fn new(
468 initial_screen_provider: WindowScreenProvider,
469 initial_placement: WindowPlacement,
470 initial_bounds_provider: WindowBoundsProvider,
471 initially_minimized: bool,
472 ) -> Self {
473 let mut s = Self::create_uninitialized();
474 s.request_screen(initial_screen_provider);
475 s.request_placement(initial_placement);
476 s.request_bounds_provider(initial_bounds_provider);
477 s.request_minimized(initially_minimized);
478 s
479 }
480 pub fn with_bounds(
481 initial_position: Option<Vec2>,
482 initial_size: Option<Size>,
483 initially_minimized: bool,
484 ) -> Self {
485 let sp = initial_size
486 .map(WindowSizeProvider::fixed)
487 .unwrap_or_else(WindowSizeProvider::default);
488 let pp = initial_position
489 .map(WindowPositionProvider::absolute)
490 .unwrap_or_else(WindowPositionProvider::default);
491 Self::new(
492 WindowScreenProvider::default(),
493 WindowPlacement::Floating,
494 WindowBoundsProvider::new_provider(sp, pp),
495 initially_minimized,
496 )
497 }
498 pub fn initialize(
499 &mut self,
500 screen_id: String,
501 placement: WindowPlacement,
502 is_minimized: bool,
503 bounds: Rect,
504 ) {
505 self.is_initialized = true;
506 self.screen_id = Some(screen_id);
507 self.placement = Some(placement);
508 self.is_minimized = Some(is_minimized);
509 self.bounds = Some(bounds);
510 }
511 pub fn screen_id(&self) -> &str {
512 self.screen_id
513 .as_deref()
514 .expect("window not initialized: screenId")
515 }
516 pub fn placement_value(&self) -> WindowPlacement {
517 self.placement.expect("window not initialized: placement")
518 }
519 pub fn is_minimized_value(&self) -> bool {
520 self.is_minimized
521 .expect("window not initialized: isMinimized")
522 }
523 pub fn bounds_value(&self) -> Rect {
524 self.bounds.expect("window not initialized: bounds")
525 }
526 pub fn position(&self) -> Vec2 {
527 let b = self.bounds_value();
528 Vec2 { x: b.x, y: b.y }
529 }
530 pub fn size(&self) -> Size {
531 let b = self.bounds_value();
532 Size {
533 width: b.w,
534 height: b.h,
535 }
536 }
537 pub fn try_screen_id(&self) -> Option<&str> {
538 self.screen_id.as_deref()
539 }
540 pub fn try_placement(&self) -> Option<WindowPlacement> {
541 self.placement
542 }
543 pub fn try_is_minimized(&self) -> Option<bool> {
544 self.is_minimized
545 }
546 pub fn try_bounds(&self) -> Option<Rect> {
547 self.bounds
548 }
549 pub fn request_screen(&mut self, p: WindowScreenProvider) {
550 self.pending_screen = Some(p);
551 }
552 pub fn request_placement(&mut self, p: WindowPlacement) {
553 self.pending_placement = Some(p);
554 }
555 pub fn request_minimized(&mut self, v: bool) {
556 self.pending_minimized = Some(v);
557 }
558 pub fn request_bounds_provider(&mut self, p: WindowBoundsProvider) {
559 self.pending_bounds.push_back(p);
560 }
561 pub fn request_bounds_fn<F: Fn(&WindowGeometryProviderScope) -> Rect + 'static>(
562 &mut self,
563 f: F,
564 ) {
565 self.request_bounds_provider(WindowBoundsProvider::new(f));
566 }
567 pub fn request_bounds(&mut self, r: Rect) {
568 self.request_bounds_provider(WindowBoundsProvider::absolute(r));
569 }
570 pub fn request_position_provider(&mut self, p: WindowPositionProvider) {
571 self.request_bounds_provider(WindowBoundsProvider::new_provider(
572 WindowSizeProvider::current(),
573 p,
574 ));
575 }
576 pub fn request_position(&mut self, pos: Vec2) {
577 self.request_position_provider(WindowPositionProvider::absolute(pos));
578 }
579 pub fn request_position_xy(&mut self, x: f32, y: f32) {
580 self.request_position(Vec2 { x, y });
581 }
582 pub fn request_size_provider(&mut self, p: WindowSizeProvider) {
583 self.request_bounds_provider(WindowBoundsProvider::new_provider(
584 p,
585 WindowPositionProvider::current(),
586 ));
587 }
588 pub fn request_size(&mut self, sz: Size) {
589 self.request_size_provider(WindowSizeProvider::fixed(sz));
590 }
591 pub fn request_size_xy(&mut self, w: f32, h: f32) {
592 self.request_size(Size {
593 width: w,
594 height: h,
595 });
596 }
597 pub fn take_pending_screen(&mut self) -> Option<WindowScreenProvider> {
598 self.pending_screen.take()
599 }
600 pub fn take_pending_placement(&mut self) -> Option<WindowPlacement> {
601 self.pending_placement.take()
602 }
603 pub fn take_pending_minimized(&mut self) -> Option<bool> {
604 self.pending_minimized.take()
605 }
606 pub fn drain_pending_bounds(&mut self) -> Vec<WindowBoundsProvider> {
607 self.pending_bounds.drain(..).collect()
608 }
609 pub fn has_pending(&self) -> bool {
610 self.pending_screen.is_some()
611 || self.pending_placement.is_some()
612 || self.pending_minimized.is_some()
613 || !self.pending_bounds.is_empty()
614 }
615 pub fn apply_pending(
616 &mut self,
617 screen_scope: &WindowScreenProviderScope,
618 geometry_scope: &WindowGeometryProviderScope,
619 ) -> Option<Rect> {
620 if let Some(p) = self.take_pending_screen() {
621 self.screen_id = Some(p.get_screen(screen_scope).id);
622 }
623 if let Some(p) = self.take_pending_placement() {
624 self.placement = Some(p);
625 }
626 if let Some(m) = self.take_pending_minimized() {
627 self.is_minimized = Some(m);
628 }
629 let pending = self.drain_pending_bounds();
630 if pending.is_empty() {
631 return None;
632 }
633 let mut last = None;
634 for p in pending {
635 let r = p.get_bounds(geometry_scope);
636 self.bounds = Some(r);
637 last = Some(r);
638 if self.placement != Some(WindowPlacement::Floating) {
639 self.placement = Some(WindowPlacement::Floating);
640 }
641 }
642 last
643 }
644 pub fn on_host_bounds_changed(&mut self, bounds: Rect, screen_id: String) {
645 self.bounds = Some(bounds);
646 self.screen_id = Some(screen_id);
647 if !self.is_initialized {
648 self.is_initialized = true;
649 if self.placement.is_none() {
650 self.placement = Some(WindowPlacement::Floating);
651 }
652 if self.is_minimized.is_none() {
653 self.is_minimized = Some(false);
654 }
655 }
656 }
657 pub fn on_host_placement_changed(&mut self, p: WindowPlacement) {
658 self.placement = Some(p);
659 }
660 pub fn on_host_minimized_changed(&mut self, v: bool) {
661 self.is_minimized = Some(v);
662 }
663}
664impl Default for WindowState {
665 fn default() -> Self {
666 Self::new(
667 WindowScreenProvider::default(),
668 WindowPlacement::Floating,
669 WindowBoundsProvider::default(),
670 false,
671 )
672 }
673}
674
675pub struct DialogState {
676 pub is_initialized: bool,
677 screen_id: Option<String>,
678 bounds: Option<Rect>,
679 pending_screen: Option<WindowScreenProvider>,
680 pending_bounds: VecDeque<WindowBoundsProvider>,
681}
682impl DialogState {
683 pub fn create_uninitialized() -> Self {
684 Self {
685 is_initialized: false,
686 screen_id: None,
687 bounds: None,
688 pending_screen: None,
689 pending_bounds: VecDeque::new(),
690 }
691 }
692 pub fn new(
693 initial_screen_provider: WindowScreenProvider,
694 initial_bounds_provider: WindowBoundsProvider,
695 ) -> Self {
696 let mut s = Self::create_uninitialized();
697 s.request_screen(initial_screen_provider);
698 s.request_bounds_provider(initial_bounds_provider);
699 s
700 }
701 pub fn with_bounds(initial_position: Option<Vec2>, initial_size: Option<Size>) -> Self {
702 let sp = initial_size
703 .map(WindowSizeProvider::fixed)
704 .unwrap_or_else(WindowSizeProvider::default);
705 let pp = initial_position
706 .map(WindowPositionProvider::absolute)
707 .unwrap_or_else(WindowPositionProvider::default);
708 Self::new(
709 WindowScreenProvider::default(),
710 WindowBoundsProvider::new_provider(sp, pp),
711 )
712 }
713 pub fn screen_id(&self) -> &str {
714 self.screen_id
715 .as_deref()
716 .expect("dialog not initialized: screenId")
717 }
718 pub fn bounds_value(&self) -> Rect {
719 self.bounds.expect("dialog not initialized: bounds")
720 }
721 pub fn position(&self) -> Vec2 {
722 let b = self.bounds_value();
723 Vec2 { x: b.x, y: b.y }
724 }
725 pub fn size(&self) -> Size {
726 let b = self.bounds_value();
727 Size {
728 width: b.w,
729 height: b.h,
730 }
731 }
732 pub fn try_screen_id(&self) -> Option<&str> {
733 self.screen_id.as_deref()
734 }
735 pub fn try_bounds(&self) -> Option<Rect> {
736 self.bounds
737 }
738 pub fn request_screen(&mut self, p: WindowScreenProvider) {
739 self.pending_screen = Some(p);
740 }
741 pub fn request_bounds_provider(&mut self, p: WindowBoundsProvider) {
742 self.pending_bounds.push_back(p);
743 }
744 pub fn request_bounds_fn<F: Fn(&WindowGeometryProviderScope) -> Rect + 'static>(
745 &mut self,
746 f: F,
747 ) {
748 self.request_bounds_provider(WindowBoundsProvider::new(f));
749 }
750 pub fn request_bounds(&mut self, r: Rect) {
751 self.request_bounds_provider(WindowBoundsProvider::absolute(r));
752 }
753 pub fn request_position_provider(&mut self, p: WindowPositionProvider) {
754 self.request_bounds_provider(WindowBoundsProvider::new_provider(
755 WindowSizeProvider::current(),
756 p,
757 ));
758 }
759 pub fn request_position(&mut self, pos: Vec2) {
760 self.request_position_provider(WindowPositionProvider::absolute(pos));
761 }
762 pub fn request_position_xy(&mut self, x: f32, y: f32) {
763 self.request_position(Vec2 { x, y });
764 }
765 pub fn request_size_provider(&mut self, p: WindowSizeProvider) {
766 self.request_bounds_provider(WindowBoundsProvider::new_provider(
767 p,
768 WindowPositionProvider::current(),
769 ));
770 }
771 pub fn request_size(&mut self, sz: Size) {
772 self.request_size_provider(WindowSizeProvider::fixed(sz));
773 }
774 pub fn request_size_xy(&mut self, w: f32, h: f32) {
775 self.request_size(Size {
776 width: w,
777 height: h,
778 });
779 }
780 pub fn take_pending_screen(&mut self) -> Option<WindowScreenProvider> {
781 self.pending_screen.take()
782 }
783 pub fn drain_pending_bounds(&mut self) -> Vec<WindowBoundsProvider> {
784 self.pending_bounds.drain(..).collect()
785 }
786 pub fn apply_pending(
787 &mut self,
788 screen_scope: &WindowScreenProviderScope,
789 geometry_scope: &WindowGeometryProviderScope,
790 ) -> Option<Rect> {
791 if let Some(p) = self.take_pending_screen() {
792 self.screen_id = Some(p.get_screen(screen_scope).id);
793 }
794 let pending = self.drain_pending_bounds();
795 if pending.is_empty() {
796 return None;
797 }
798 let mut last = None;
799 for p in pending {
800 let r = p.get_bounds(geometry_scope);
801 self.bounds = Some(r);
802 last = Some(r);
803 }
804 last
805 }
806 pub fn on_host_bounds_changed(&mut self, bounds: Rect, screen_id: String) {
807 self.bounds = Some(bounds);
808 self.screen_id = Some(screen_id);
809 if !self.is_initialized {
810 self.is_initialized = true;
811 }
812 }
813 pub fn initialize(&mut self, screen_id: String, bounds: Rect) {
814 self.is_initialized = true;
815 self.screen_id = Some(screen_id);
816 self.bounds = Some(bounds);
817 }
818}
819impl Default for DialogState {
820 fn default() -> Self {
821 Self::new(
822 WindowScreenProvider::default(),
823 WindowBoundsProvider::default(),
824 )
825 }
826}
827
828pub fn remember_window_state(
829 key: impl Into<String>,
830 initial_screen_provider: WindowScreenProvider,
831 initial_placement: WindowPlacement,
832 initial_bounds_provider: WindowBoundsProvider,
833 initially_minimized: bool,
834) -> Rc<RefCell<WindowState>> {
835 let key = key.into();
836 repose_core::remember_with_key(key, move || {
837 RefCell::new(WindowState::new(
838 initial_screen_provider.clone(),
839 initial_placement,
840 initial_bounds_provider.clone(),
841 initially_minimized,
842 ))
843 })
844}
845pub fn remember_window_state_with_bounds(
846 key: impl Into<String>,
847 initial_position: Option<Vec2>,
848 initial_size: Option<Size>,
849 initially_minimized: bool,
850) -> Rc<RefCell<WindowState>> {
851 let key = key.into();
852 repose_core::remember_with_key(key, move || {
853 RefCell::new(WindowState::with_bounds(
854 initial_position,
855 initial_size,
856 initially_minimized,
857 ))
858 })
859}
860pub fn remember_dialog_state(
861 key: impl Into<String>,
862 initial_screen_provider: WindowScreenProvider,
863 initial_bounds_provider: WindowBoundsProvider,
864) -> Rc<RefCell<DialogState>> {
865 let key = key.into();
866 repose_core::remember_with_key(key, move || {
867 RefCell::new(DialogState::new(
868 initial_screen_provider.clone(),
869 initial_bounds_provider.clone(),
870 ))
871 })
872}
873pub fn remember_dialog_state_with_bounds(
874 key: impl Into<String>,
875 initial_position: Option<Vec2>,
876 initial_size: Option<Size>,
877) -> Rc<RefCell<DialogState>> {
878 let key = key.into();
879 repose_core::remember_with_key(key, move || {
880 RefCell::new(DialogState::with_bounds(initial_position, initial_size))
881 })
882}
883
884use crate::windowing::FloatingWindow;
885
886pub fn apply_window_state_to_floating(
887 state: &mut WindowState,
888 window: &mut FloatingWindow,
889 host_bounds: Rect,
890 measure_content: impl Fn(WindowConstraints) -> Size + 'static,
891) {
892 let screen = Screen::primary(host_bounds);
893 let screen_scope = WindowScreenProviderScope::new(vec![screen.clone()], screen.clone());
894 let window_metrics = WindowMetrics::new(
895 screen.clone(),
896 Rect {
897 x: window.position.x.0,
898 y: window.position.y.0,
899 w: window.size.width.0,
900 h: window.size.height.0,
901 },
902 ScreenInsets::default(),
903 );
904 let geometry_scope = WindowGeometryProviderScope::new(None, window_metrics, measure_content);
905 if !state.is_initialized {
906 let pending = state.drain_pending_bounds();
907 let rect = if pending.is_empty() {
908 WindowBoundsProvider::default().get_bounds(&geometry_scope)
909 } else {
910 let mut last = None;
911 for p in pending {
912 last = Some(p.get_bounds(&geometry_scope));
913 }
914 last.unwrap()
915 };
916 let screen_id = screen_scope
917 .eval(&state.take_pending_screen().unwrap_or_default())
918 .id;
919 let placement = state
920 .take_pending_placement()
921 .unwrap_or(WindowPlacement::Floating);
922 let minimized = state.take_pending_minimized().unwrap_or(false);
923 state.initialize(screen_id, placement, minimized, rect);
924 } else {
925 state.apply_pending(&screen_scope, &geometry_scope);
926 }
927 if let Some(bounds) = state.try_bounds() {
928 let mut sz = Size {
929 width: bounds.w,
930 height: bounds.h,
931 };
932 sz.width = sz.width.clamp(
933 window.min_size.width.0,
934 window.max_size.map(|s| s.width.0).unwrap_or(f32::INFINITY),
935 );
936 sz.height = sz.height.clamp(
937 window.min_size.height.0,
938 window.max_size.map(|s| s.height.0).unwrap_or(f32::INFINITY),
939 );
940 sz.width = sz.width.min(host_bounds.w.max(sz.width));
941 sz.height = sz.height.min(host_bounds.h.max(sz.height));
942 let mut pos = Vec2 {
943 x: bounds.x,
944 y: bounds.y,
945 };
946 if host_bounds.w > 1.0 && host_bounds.h > 1.0 {
947 let keep = 24.0;
948 let min_x = host_bounds.x - sz.width + keep;
949 let max_x = host_bounds.x + host_bounds.w - keep;
950 let min_y = host_bounds.y - sz.height + keep;
951 let max_y = host_bounds.y + host_bounds.h - keep;
952 pos.x = pos.x.clamp(min_x, max_x);
953 pos.y = pos.y.clamp(min_y, max_y);
954 }
955 window.position = DpOffset::new(Dp(pos.x), Dp(pos.y));
956 window.size = DpSize::new(Dp(sz.width), Dp(sz.height));
957 }
958 if let Some(p) = state.try_placement() {
959 match p {
960 WindowPlacement::Maximized | WindowPlacement::Fullscreen => {
961 window.position = DpOffset::new(Dp(host_bounds.x), Dp(host_bounds.y));
962 window.size = DpSize::new(Dp(host_bounds.w), Dp(host_bounds.h));
963 }
964 WindowPlacement::Floating => {}
965 }
966 }
967}
968
969pub fn apply_dialog_state_to_floating(
970 state: &mut DialogState,
971 dialog_window: &mut FloatingWindow,
972 host_bounds: Rect,
973 parent_window: Option<&FloatingWindow>,
974 measure_content: impl Fn(WindowConstraints) -> Size + 'static,
975) {
976 let screen = Screen::primary(host_bounds);
977 let screen_scope = WindowScreenProviderScope::new(vec![screen.clone()], screen.clone());
978 let parent_metrics = parent_window.map(|pw| {
979 WindowMetrics::new(
980 screen.clone(),
981 Rect {
982 x: pw.position.x.0,
983 y: pw.position.y.0,
984 w: pw.size.width.0,
985 h: pw.size.height.0,
986 },
987 ScreenInsets::default(),
988 )
989 });
990 let window_metrics = WindowMetrics::new(
991 screen.clone(),
992 Rect {
993 x: dialog_window.position.x.0,
994 y: dialog_window.position.y.0,
995 w: dialog_window.size.width.0,
996 h: dialog_window.size.height.0,
997 },
998 ScreenInsets::default(),
999 );
1000 let geometry_scope =
1001 WindowGeometryProviderScope::new(parent_metrics, window_metrics, measure_content);
1002 if !state.is_initialized {
1003 let pending = state.drain_pending_bounds();
1004 let rect = if pending.is_empty() {
1005 WindowBoundsProvider::default().get_bounds(&geometry_scope)
1006 } else {
1007 let mut last = None;
1008 for p in pending {
1009 last = Some(p.get_bounds(&geometry_scope));
1010 }
1011 last.unwrap()
1012 };
1013 let screen_id = screen_scope
1014 .eval(&state.take_pending_screen().unwrap_or_default())
1015 .id;
1016 state.initialize(screen_id, rect);
1017 } else {
1018 state.apply_pending(&screen_scope, &geometry_scope);
1019 }
1020 if let Some(bounds) = state.try_bounds() {
1021 let mut sz = Size {
1022 width: bounds.w,
1023 height: bounds.h,
1024 };
1025 sz.width = sz.width.clamp(
1026 dialog_window.min_size.width.0,
1027 dialog_window
1028 .max_size
1029 .map(|s| s.width.0)
1030 .unwrap_or(f32::INFINITY),
1031 );
1032 sz.height = sz.height.clamp(
1033 dialog_window.min_size.height.0,
1034 dialog_window
1035 .max_size
1036 .map(|s| s.height.0)
1037 .unwrap_or(f32::INFINITY),
1038 );
1039 dialog_window.position = DpOffset::new(Dp(bounds.x), Dp(bounds.y));
1040 dialog_window.size = DpSize::new(Dp(sz.width), Dp(sz.height));
1041 }
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046 use super::*;
1047 fn host() -> Rect {
1048 Rect {
1049 x: 0.0,
1050 y: 0.0,
1051 w: 1280.0,
1052 h: 800.0,
1053 }
1054 }
1055 fn dummy_measure(_c: WindowConstraints) -> Size {
1056 Size {
1057 width: 400.0,
1058 height: 200.0,
1059 }
1060 }
1061 #[test]
1062 fn centered_fixed() {
1063 let mut state = WindowState::new(
1064 WindowScreenProvider::default(),
1065 WindowPlacement::Floating,
1066 WindowBoundsProvider::new_provider(
1067 WindowSizeProvider::fixed(Size {
1068 width: 400.0,
1069 height: 200.0,
1070 }),
1071 WindowPositionProvider::centered_on_screen(),
1072 ),
1073 false,
1074 );
1075 let mut win = FloatingWindow::new(
1076 1,
1077 "test",
1078 Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1079 );
1080 apply_window_state_to_floating(&mut state, &mut win, host(), dummy_measure);
1081 assert!(state.is_initialized);
1082 assert!((win.position.x - Dp(440.0)).abs().0 < 1.0);
1083 assert!((win.position.y - Dp(300.0)).abs().0 < 1.0);
1084 assert_eq!(win.size.width, Dp(400.0));
1085 }
1086 #[test]
1087 fn unconstrained_sizes_to_content() {
1088 let mut state = WindowState::new(
1089 WindowScreenProvider::default(),
1090 WindowPlacement::Floating,
1091 WindowBoundsProvider::new_provider(
1092 WindowSizeProvider::unconstrained(),
1093 WindowPositionProvider::centered_on_screen(),
1094 ),
1095 false,
1096 );
1097 let mut win = FloatingWindow::new(
1098 1,
1099 "test",
1100 Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1101 );
1102 apply_window_state_to_floating(&mut state, &mut win, host(), dummy_measure);
1103 assert!((win.size.width - Dp(400.0)).abs().0 < 1.0);
1104 assert!((win.size.height - Dp(200.0)).abs().0 < 1.0);
1105 }
1106 #[test]
1107 fn async_request_distinction() {
1108 let mut state = WindowState::default();
1109 let h = host();
1110 let mut win = FloatingWindow::new(
1111 1,
1112 "test",
1113 Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1114 );
1115 apply_window_state_to_floating(&mut state, &mut win, h, dummy_measure);
1116 let initial = win.position;
1117 state.request_position(Vec2 { x: 100.0, y: 100.0 });
1118 assert_eq!(win.position, initial);
1119 apply_window_state_to_floating(&mut state, &mut win, h, dummy_measure);
1120 assert!((win.position.x - Dp(100.0)).abs().0 < 1.0);
1121 }
1122 #[test]
1123 fn request_size_preserves_position() {
1124 let mut state = WindowState::with_bounds(
1125 Some(Vec2 { x: 50.0, y: 60.0 }),
1126 Some(Size {
1127 width: 300.0,
1128 height: 200.0,
1129 }),
1130 false,
1131 );
1132 let h = host();
1133 let mut win = FloatingWindow::new(
1134 1,
1135 "test",
1136 Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1137 );
1138 apply_window_state_to_floating(&mut state, &mut win, h, dummy_measure);
1139 assert!((win.position.x - Dp(50.0)).abs().0 < 1.5);
1140 state.request_size(Size {
1141 width: 500.0,
1142 height: 400.0,
1143 });
1144 apply_window_state_to_floating(&mut state, &mut win, h, dummy_measure);
1145 assert!((win.position.x - Dp(50.0)).abs().0 < 1.5);
1146 assert!((win.size.width - Dp(500.0)).abs().0 < 1.0);
1147 }
1148 #[test]
1149 fn dialog_centered_in_parent() {
1150 let parent = FloatingWindow::new(
1151 1,
1152 "parent",
1153 Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1154 )
1155 .position(Dp(100.0), Dp(100.0))
1156 .size(Dp(400.0), Dp(300.0));
1157 let mut dialog_state = DialogState::new(
1158 WindowScreenProvider::default(),
1159 WindowBoundsProvider::new_provider(
1160 WindowSizeProvider::fixed(Size {
1161 width: 200.0,
1162 height: 100.0,
1163 }),
1164 WindowPositionProvider::centered_in_parent(Vec2::ZERO),
1165 ),
1166 );
1167 let mut dialog_win = FloatingWindow::new(
1168 2,
1169 "dialog",
1170 Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1171 );
1172 apply_dialog_state_to_floating(
1173 &mut dialog_state,
1174 &mut dialog_win,
1175 host(),
1176 Some(&parent),
1177 dummy_measure,
1178 );
1179 assert!((dialog_win.position.x - Dp(200.0)).abs().0 < 1.0);
1180 assert!((dialog_win.position.y - Dp(200.0)).abs().0 < 1.0);
1181 }
1182 #[test]
1183 fn min_max_clamping() {
1184 let mut state = WindowState::new(
1185 WindowScreenProvider::default(),
1186 WindowPlacement::Floating,
1187 WindowBoundsProvider::new_provider(
1188 WindowSizeProvider::fixed(Size {
1189 width: 100.0,
1190 height: 100.0,
1191 }),
1192 WindowPositionProvider::absolute(Vec2 { x: 0.0, y: 0.0 }),
1193 ),
1194 false,
1195 );
1196 let mut win = FloatingWindow::new(
1197 1,
1198 "test",
1199 Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1200 )
1201 .min_size(Dp(200.0), Dp(200.0))
1202 .max_size(Dp(300.0), Dp(300.0));
1203 apply_window_state_to_floating(&mut state, &mut win, host(), dummy_measure);
1204 assert_eq!(win.size.width, Dp(200.0));
1205 state.request_size(Size {
1206 width: 500.0,
1207 height: 500.0,
1208 });
1209 apply_window_state_to_floating(&mut state, &mut win, host(), dummy_measure);
1210 assert_eq!(win.size.width, Dp(300.0));
1211 }
1212 #[test]
1213 fn screen_selection() {
1214 let s1 = Screen::new(
1215 "screen1",
1216 Rect {
1217 x: 0.0,
1218 y: 0.0,
1219 w: 1280.0,
1220 h: 800.0,
1221 },
1222 ScreenInsets::default(),
1223 );
1224 let s2 = Screen::new(
1225 "screen2",
1226 Rect {
1227 x: 1280.0,
1228 y: 0.0,
1229 w: 1280.0,
1230 h: 800.0,
1231 },
1232 ScreenInsets::default(),
1233 );
1234 let scope = WindowScreenProviderScope::new(vec![s1.clone(), s2.clone()], s1.clone());
1235 let provider = WindowScreenProvider::with_id("screen2");
1236 assert_eq!(provider.get_screen(&scope).id, "screen2");
1237 }
1238}