Skip to main content

repose_ui/
window_v2.rs

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::{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,
898            y: window.position.y,
899            w: window.size.width,
900            h: window.size.height,
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(
918                &state
919                    .take_pending_screen()
920                    .unwrap_or_else(WindowScreenProvider::default),
921            )
922            .id;
923        let placement = state
924            .take_pending_placement()
925            .unwrap_or(WindowPlacement::Floating);
926        let minimized = state.take_pending_minimized().unwrap_or(false);
927        state.initialize(screen_id, placement, minimized, rect);
928    } else {
929        state.apply_pending(&screen_scope, &geometry_scope);
930    }
931    if let Some(bounds) = state.try_bounds() {
932        let mut sz = Size {
933            width: bounds.w,
934            height: bounds.h,
935        };
936        sz.width = sz.width.clamp(
937            window.min_size.width,
938            window.max_size.map(|s| s.width).unwrap_or(f32::INFINITY),
939        );
940        sz.height = sz.height.clamp(
941            window.min_size.height,
942            window.max_size.map(|s| s.height).unwrap_or(f32::INFINITY),
943        );
944        sz.width = sz.width.min(host_bounds.w.max(sz.width));
945        sz.height = sz.height.min(host_bounds.h.max(sz.height));
946        let mut pos = Vec2 {
947            x: bounds.x,
948            y: bounds.y,
949        };
950        if host_bounds.w > 1.0 && host_bounds.h > 1.0 {
951            let keep = 24.0;
952            let min_x = host_bounds.x - sz.width + keep;
953            let max_x = host_bounds.x + host_bounds.w - keep;
954            let min_y = host_bounds.y - sz.height + keep;
955            let max_y = host_bounds.y + host_bounds.h - keep;
956            pos.x = pos.x.clamp(min_x, max_x);
957            pos.y = pos.y.clamp(min_y, max_y);
958        }
959        window.position = pos;
960        window.size = sz;
961    }
962    if let Some(p) = state.try_placement() {
963        match p {
964            WindowPlacement::Maximized | WindowPlacement::Fullscreen => {
965                window.position = Vec2 {
966                    x: host_bounds.x,
967                    y: host_bounds.y,
968                };
969                window.size = Size {
970                    width: host_bounds.w,
971                    height: host_bounds.h,
972                };
973            }
974            WindowPlacement::Floating => {}
975        }
976    }
977}
978
979pub fn apply_dialog_state_to_floating(
980    state: &mut DialogState,
981    dialog_window: &mut FloatingWindow,
982    host_bounds: Rect,
983    parent_window: Option<&FloatingWindow>,
984    measure_content: impl Fn(WindowConstraints) -> Size + 'static,
985) {
986    let screen = Screen::primary(host_bounds);
987    let screen_scope = WindowScreenProviderScope::new(vec![screen.clone()], screen.clone());
988    let parent_metrics = parent_window.map(|pw| {
989        WindowMetrics::new(
990            screen.clone(),
991            Rect {
992                x: pw.position.x,
993                y: pw.position.y,
994                w: pw.size.width,
995                h: pw.size.height,
996            },
997            ScreenInsets::default(),
998        )
999    });
1000    let window_metrics = WindowMetrics::new(
1001        screen.clone(),
1002        Rect {
1003            x: dialog_window.position.x,
1004            y: dialog_window.position.y,
1005            w: dialog_window.size.width,
1006            h: dialog_window.size.height,
1007        },
1008        ScreenInsets::default(),
1009    );
1010    let geometry_scope =
1011        WindowGeometryProviderScope::new(parent_metrics, window_metrics, measure_content);
1012    if !state.is_initialized {
1013        let pending = state.drain_pending_bounds();
1014        let rect = if pending.is_empty() {
1015            WindowBoundsProvider::default().get_bounds(&geometry_scope)
1016        } else {
1017            let mut last = None;
1018            for p in pending {
1019                last = Some(p.get_bounds(&geometry_scope));
1020            }
1021            last.unwrap()
1022        };
1023        let screen_id = screen_scope
1024            .eval(
1025                &state
1026                    .take_pending_screen()
1027                    .unwrap_or_else(WindowScreenProvider::default),
1028            )
1029            .id;
1030        state.initialize(screen_id, rect);
1031    } else {
1032        state.apply_pending(&screen_scope, &geometry_scope);
1033    }
1034    if let Some(bounds) = state.try_bounds() {
1035        let mut sz = Size {
1036            width: bounds.w,
1037            height: bounds.h,
1038        };
1039        sz.width = sz.width.clamp(
1040            dialog_window.min_size.width,
1041            dialog_window
1042                .max_size
1043                .map(|s| s.width)
1044                .unwrap_or(f32::INFINITY),
1045        );
1046        sz.height = sz.height.clamp(
1047            dialog_window.min_size.height,
1048            dialog_window
1049                .max_size
1050                .map(|s| s.height)
1051                .unwrap_or(f32::INFINITY),
1052        );
1053        dialog_window.position = Vec2 {
1054            x: bounds.x,
1055            y: bounds.y,
1056        };
1057        dialog_window.size = sz;
1058    }
1059}
1060
1061#[cfg(test)]
1062mod tests {
1063    use super::*;
1064    fn host() -> Rect {
1065        Rect {
1066            x: 0.0,
1067            y: 0.0,
1068            w: 1280.0,
1069            h: 800.0,
1070        }
1071    }
1072    fn dummy_measure(_c: WindowConstraints) -> Size {
1073        Size {
1074            width: 400.0,
1075            height: 200.0,
1076        }
1077    }
1078    #[test]
1079    fn centered_fixed() {
1080        let mut state = WindowState::new(
1081            WindowScreenProvider::default(),
1082            WindowPlacement::Floating,
1083            WindowBoundsProvider::new_provider(
1084                WindowSizeProvider::fixed(Size {
1085                    width: 400.0,
1086                    height: 200.0,
1087                }),
1088                WindowPositionProvider::centered_on_screen(),
1089            ),
1090            false,
1091        );
1092        let mut win = FloatingWindow::new(
1093            1,
1094            "test",
1095            Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1096        );
1097        apply_window_state_to_floating(&mut state, &mut win, host(), dummy_measure);
1098        assert!(state.is_initialized);
1099        assert!((win.position.x - 440.0).abs() < 1.0);
1100        assert!((win.position.y - 300.0).abs() < 1.0);
1101        assert_eq!(win.size.width, 400.0);
1102    }
1103    #[test]
1104    fn unconstrained_sizes_to_content() {
1105        let mut state = WindowState::new(
1106            WindowScreenProvider::default(),
1107            WindowPlacement::Floating,
1108            WindowBoundsProvider::new_provider(
1109                WindowSizeProvider::unconstrained(),
1110                WindowPositionProvider::centered_on_screen(),
1111            ),
1112            false,
1113        );
1114        let mut win = FloatingWindow::new(
1115            1,
1116            "test",
1117            Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1118        );
1119        apply_window_state_to_floating(&mut state, &mut win, host(), dummy_measure);
1120        assert!((win.size.width - 400.0).abs() < 1.0);
1121        assert!((win.size.height - 200.0).abs() < 1.0);
1122    }
1123    #[test]
1124    fn async_request_distinction() {
1125        let mut state = WindowState::default();
1126        let h = host();
1127        let mut win = FloatingWindow::new(
1128            1,
1129            "test",
1130            Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1131        );
1132        apply_window_state_to_floating(&mut state, &mut win, h, dummy_measure);
1133        let initial = win.position;
1134        state.request_position(Vec2 { x: 100.0, y: 100.0 });
1135        assert_eq!(win.position, initial);
1136        apply_window_state_to_floating(&mut state, &mut win, h, dummy_measure);
1137        assert!((win.position.x - 100.0).abs() < 1.0);
1138    }
1139    #[test]
1140    fn request_size_preserves_position() {
1141        let mut state = WindowState::with_bounds(
1142            Some(Vec2 { x: 50.0, y: 60.0 }),
1143            Some(Size {
1144                width: 300.0,
1145                height: 200.0,
1146            }),
1147            false,
1148        );
1149        let h = host();
1150        let mut win = FloatingWindow::new(
1151            1,
1152            "test",
1153            Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1154        );
1155        apply_window_state_to_floating(&mut state, &mut win, h, dummy_measure);
1156        assert!((win.position.x - 50.0).abs() < 1.5);
1157        state.request_size(Size {
1158            width: 500.0,
1159            height: 400.0,
1160        });
1161        apply_window_state_to_floating(&mut state, &mut win, h, dummy_measure);
1162        assert!((win.position.x - 50.0).abs() < 1.5);
1163        assert!((win.size.width - 500.0).abs() < 1.0);
1164    }
1165    #[test]
1166    fn dialog_centered_in_parent() {
1167        let parent = FloatingWindow::new(
1168            1,
1169            "parent",
1170            Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1171        )
1172        .position(100.0, 100.0)
1173        .size(400.0, 300.0);
1174        let mut dialog_state = DialogState::new(
1175            WindowScreenProvider::default(),
1176            WindowBoundsProvider::new_provider(
1177                WindowSizeProvider::fixed(Size {
1178                    width: 200.0,
1179                    height: 100.0,
1180                }),
1181                WindowPositionProvider::centered_in_parent(Vec2::ZERO),
1182            ),
1183        );
1184        let mut dialog_win = FloatingWindow::new(
1185            2,
1186            "dialog",
1187            Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1188        );
1189        apply_dialog_state_to_floating(
1190            &mut dialog_state,
1191            &mut dialog_win,
1192            host(),
1193            Some(&parent),
1194            dummy_measure,
1195        );
1196        assert!((dialog_win.position.x - 200.0).abs() < 1.0);
1197        assert!((dialog_win.position.y - 200.0).abs() < 1.0);
1198    }
1199    #[test]
1200    fn min_max_clamping() {
1201        let mut state = WindowState::new(
1202            WindowScreenProvider::default(),
1203            WindowPlacement::Floating,
1204            WindowBoundsProvider::new_provider(
1205                WindowSizeProvider::fixed(Size {
1206                    width: 100.0,
1207                    height: 100.0,
1208                }),
1209                WindowPositionProvider::absolute(Vec2 { x: 0.0, y: 0.0 }),
1210            ),
1211            false,
1212        );
1213        let mut win = FloatingWindow::new(
1214            1,
1215            "test",
1216            Rc::new(|| repose_core::View::new(0, repose_core::ViewKind::Box)),
1217        )
1218        .min_size(200.0, 200.0)
1219        .max_size(300.0, 300.0);
1220        apply_window_state_to_floating(&mut state, &mut win, host(), dummy_measure);
1221        assert_eq!(win.size.width, 200.0);
1222        state.request_size(Size {
1223            width: 500.0,
1224            height: 500.0,
1225        });
1226        apply_window_state_to_floating(&mut state, &mut win, host(), dummy_measure);
1227        assert_eq!(win.size.width, 300.0);
1228    }
1229    #[test]
1230    fn screen_selection() {
1231        let s1 = Screen::new(
1232            "screen1",
1233            Rect {
1234                x: 0.0,
1235                y: 0.0,
1236                w: 1280.0,
1237                h: 800.0,
1238            },
1239            ScreenInsets::default(),
1240        );
1241        let s2 = Screen::new(
1242            "screen2",
1243            Rect {
1244                x: 1280.0,
1245                y: 0.0,
1246                w: 1280.0,
1247                h: 800.0,
1248            },
1249            ScreenInsets::default(),
1250        );
1251        let scope = WindowScreenProviderScope::new(vec![s1.clone(), s2.clone()], s1.clone());
1252        let provider = WindowScreenProvider::with_id("screen2");
1253        assert_eq!(provider.get_screen(&scope).id, "screen2");
1254    }
1255}