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::{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}