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}