Skip to main content

i_slint_core/window/
popup.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore unclipped
5
6//! Popup window handling helpers
7
8use crate::lengths::{LogicalPoint, LogicalRect, LogicalSize};
9
10/// A collection of data that might influence the placement of a `Popup`.
11pub enum Placement {
12    /// Request a fixed position
13    Fixed(LogicalRect),
14}
15
16/// Find a placement for the `Popup`, using the provided `Placement`.
17/// When a `clip_region` is provided, then the `Popup` will stay within those bounds.
18/// The `clip_region` typically is the window or the screen the window is on.
19pub fn place_popup(placement: Placement, clip_region: &Option<LogicalRect>) -> LogicalRect {
20    match placement {
21        Placement::Fixed(rect) => {
22            let clip = clip_region.unwrap_or(rect);
23            if clip.contains_rect(&rect) {
24                rect
25            } else {
26                let size = LogicalSize::new(
27                    crate::Coord::min(rect.size.width, clip.size.width),
28                    crate::Coord::min(rect.size.height, clip.size.height),
29                );
30                let origin = LogicalPoint::new(
31                    rect.origin
32                        .x
33                        .clamp(clip.origin.x, clip.origin.x + clip.size.width - size.width),
34                    rect.origin
35                        .y
36                        .clamp(clip.origin.y, clip.origin.y + clip.size.height - size.height),
37                );
38                LogicalRect::new(origin, size)
39            }
40        }
41    }
42}
43
44#[cfg(test)]
45fn r(x: i32, y: i32, w: i32, h: i32) -> LogicalRect {
46    LogicalRect::new(LogicalPoint::new(x as f32, y as f32), LogicalSize::new(w as f32, h as f32))
47}
48
49#[cfg(test)]
50#[track_caller]
51fn fixed_placement(input: LogicalRect, expected: LogicalRect, clip: Option<LogicalRect>) {
52    std::eprintln!("fixed: {input:?}, clip({clip:?}) => {expected:?}");
53    let result = place_popup(Placement::Fixed(input), &clip);
54    if let Some(clip) = clip {
55        clip.contains_rect(&result);
56    }
57    assert_eq!(result, expected);
58}
59
60#[test]
61fn test_place_popup_fixed_unclipped() {
62    let data = r(5, 5, 100, 100);
63    fixed_placement(data, data, None);
64
65    let data = r(5, -20, 100, 100);
66    fixed_placement(data, data, None);
67    let data = r(2000, -20, 100, 100);
68    fixed_placement(data, data, None);
69    let data = r(2000, 5, 100, 100);
70    fixed_placement(data, data, None);
71    let data = r(2000, 2000, 100, 100);
72    fixed_placement(data, data, None);
73    let data = r(5, 2000, 100, 100);
74    fixed_placement(data, data, None);
75    let data = r(-20, 2000, 100, 100);
76    fixed_placement(data, data, None);
77    let data = r(-20, 5, 100, 100);
78    fixed_placement(data, data, None);
79    let data = r(-20, -20, 100, 100);
80    fixed_placement(data, data, None);
81
82    let data = r(-20, -20, 2000, 2000);
83    fixed_placement(data, data, None);
84}
85
86#[test]
87fn test_place_popup_fixed_clipped() {
88    for (clip_offset_x, clip_offset_y) in [
89        (-200, -200),
90        (-200, 0),
91        (-200, 200),
92        (0, -200),
93        (0, 0),
94        (0, 200),
95        (200, -200),
96        (200, 0),
97        (200, 200),
98    ] {
99        for (clip_width, clip_height) in [(110, 110), (800, 600)] {
100            let clip = r(clip_offset_x, clip_offset_y, clip_width, clip_height);
101
102            let x_w = clip_offset_x - 10;
103            let x_c = clip_offset_x + 5;
104            let x_e = clip_offset_x + clip_width - 80;
105
106            let y_n = clip_offset_y - 10;
107            let y_c = clip_offset_y + 5;
108            let y_s = clip_offset_y + clip_height - 80;
109
110            let x_min = clip_offset_x;
111            let x_max = clip_offset_x + clip_width;
112            let y_min = clip_offset_y;
113            let y_max = clip_offset_y + clip_height;
114
115            assert!(clip_width > 105 && clip_width < 1000);
116            assert!(clip_height > 105 && clip_height < 1000);
117
118            // smaller, inside
119            fixed_placement(r(x_c, y_c, 100, 100), r(x_c, y_c, 100, 100), Some(clip));
120
121            // smaller, partial outside
122            fixed_placement(r(x_c, y_n, 100, 100), r(x_c, y_min, 100, 100), Some(clip));
123            fixed_placement(r(x_e, y_n, 100, 100), r(x_max - 100, y_min, 100, 100), Some(clip));
124            fixed_placement(r(x_e, y_c, 100, 100), r(x_max - 100, y_c, 100, 100), Some(clip));
125            fixed_placement(
126                r(x_e, y_s, 100, 100),
127                r(x_max - 100, y_max - 100, 100, 100),
128                Some(clip),
129            );
130            fixed_placement(r(x_c, y_s, 100, 100), r(x_c, y_max - 100, 100, 100), Some(clip));
131            fixed_placement(r(x_c, y_s, 100, 100), r(x_c, y_max - 100, 100, 100), Some(clip));
132            fixed_placement(r(x_w, y_s, 100, 100), r(x_min, y_max - 100, 100, 100), Some(clip));
133            fixed_placement(r(x_w, y_c, 100, 100), r(x_min, y_c, 100, 100), Some(clip));
134            fixed_placement(r(x_w, y_n, 100, 100), r(x_min, y_min, 100, 100), Some(clip));
135
136            // smaller, totally outside
137            fixed_placement(r(x_c, -2000, 100, 100), r(x_c, y_min, 100, 100), Some(clip));
138            fixed_placement(r(2000, -2000, 100, 100), r(x_max - 100, y_min, 100, 100), Some(clip));
139            fixed_placement(r(2000, y_c, 100, 100), r(x_max - 100, y_c, 100, 100), Some(clip));
140            fixed_placement(
141                r(2000, 2000, 100, 100),
142                r(x_max - 100, y_max - 100, 100, 100),
143                Some(clip),
144            );
145            fixed_placement(r(x_c, 2000, 100, 100), r(x_c, y_max - 100, 100, 100), Some(clip));
146            fixed_placement(r(-2000, 2000, 100, 100), r(x_min, y_max - 100, 100, 100), Some(clip));
147            fixed_placement(r(-2000, y_c, 100, 100), r(x_min, y_c, 100, 100), Some(clip));
148            fixed_placement(r(-2000, -2000, 100, 100), r(x_min, y_min, 100, 100), Some(clip));
149
150            // matching size, covering
151            fixed_placement(
152                r(x_min, y_min, clip_width, clip_height),
153                r(x_min, y_min, clip_width, clip_height),
154                Some(clip),
155            );
156
157            // matching size, overlapping
158            fixed_placement(
159                r(x_c, y_c, clip_width, clip_height),
160                r(x_min, y_min, clip_width, clip_height),
161                Some(clip),
162            );
163            fixed_placement(
164                r(x_c, y_n, clip_width, clip_height),
165                r(x_min, y_min, clip_width, clip_height),
166                Some(clip),
167            );
168
169            fixed_placement(
170                r(x_e, y_n, clip_width, clip_height),
171                r(x_min, y_min, clip_width, clip_height),
172                Some(clip),
173            );
174            fixed_placement(
175                r(x_e, y_c, clip_width, clip_height),
176                r(x_min, y_min, clip_width, clip_height),
177                Some(clip),
178            );
179            fixed_placement(
180                r(x_e, y_s, clip_width, clip_height),
181                r(x_min, y_min, clip_width, clip_height),
182                Some(clip),
183            );
184            fixed_placement(
185                r(x_c, y_s, clip_width, clip_height),
186                r(x_min, y_min, clip_width, clip_height),
187                Some(clip),
188            );
189            fixed_placement(
190                r(x_w, y_s, clip_width, clip_height),
191                r(x_min, y_min, clip_width, clip_height),
192                Some(clip),
193            );
194            fixed_placement(
195                r(x_w, y_c, clip_width, clip_height),
196                r(x_min, y_min, clip_width, clip_height),
197                Some(clip),
198            );
199            fixed_placement(
200                r(x_w, y_n, clip_width, clip_height),
201                r(x_min, y_min, clip_width, clip_height),
202                Some(clip),
203            );
204
205            // too big, overlapping
206            fixed_placement(
207                r(x_c, y_c, clip_width + 5, clip_height + 5),
208                r(x_min, y_min, clip_width, clip_height),
209                Some(clip),
210            );
211            fixed_placement(
212                r(x_c, y_n, clip_width + 5, clip_height + 5),
213                r(x_min, y_min, clip_width, clip_height),
214                Some(clip),
215            );
216            fixed_placement(
217                r(x_e, y_n, clip_width + 5, clip_height + 5),
218                r(x_min, y_min, clip_width, clip_height),
219                Some(clip),
220            );
221            fixed_placement(
222                r(x_e, y_c, clip_width + 5, clip_height + 5),
223                r(x_min, y_min, clip_width, clip_height),
224                Some(clip),
225            );
226            fixed_placement(
227                r(x_e, y_s, clip_width + 5, clip_height + 5),
228                r(x_min, y_min, clip_width, clip_height),
229                Some(clip),
230            );
231            fixed_placement(
232                r(x_c, y_s, clip_width + 5, clip_height + 5),
233                r(x_min, y_min, clip_width, clip_height),
234                Some(clip),
235            );
236            fixed_placement(
237                r(x_w, y_s, clip_width + 5, clip_height + 5),
238                r(x_min, y_min, clip_width, clip_height),
239                Some(clip),
240            );
241            fixed_placement(
242                r(x_w, y_c, clip_width + 5, clip_height + 5),
243                r(x_min, y_min, clip_width, clip_height),
244                Some(clip),
245            );
246            fixed_placement(
247                r(x_w, y_n, clip_width + 5, clip_height + 5),
248                r(x_min, y_min, clip_width, clip_height),
249                Some(clip),
250            );
251
252            // too big, outside
253            fixed_placement(
254                r(x_c, -3000, clip_width + 5, clip_height + 5),
255                r(x_min, y_min, clip_width, clip_height),
256                Some(clip),
257            );
258            fixed_placement(
259                r(3000, -3000, clip_width + 5, clip_height + 5),
260                r(x_min, y_min, clip_width, clip_height),
261                Some(clip),
262            );
263            fixed_placement(
264                r(3000, y_c, clip_width + 5, clip_height + 5),
265                r(x_min, y_min, clip_width, clip_height),
266                Some(clip),
267            );
268            fixed_placement(
269                r(3000, 3000, clip_width + 5, clip_height + 5),
270                r(x_min, y_min, clip_width, clip_height),
271                Some(clip),
272            );
273            fixed_placement(
274                r(x_c, 3000, clip_width + 5, clip_height + 5),
275                r(x_min, y_min, clip_width, clip_height),
276                Some(clip),
277            );
278            fixed_placement(
279                r(-3000, 3000, clip_width + 5, clip_height + 5),
280                r(x_min, y_min, clip_width, clip_height),
281                Some(clip),
282            );
283            fixed_placement(
284                r(-3000, y_c, clip_width + 5, clip_height + 5),
285                r(x_min, y_min, clip_width, clip_height),
286                Some(clip),
287            );
288            fixed_placement(
289                r(-3000, -3000, clip_width + 5, clip_height + 5),
290                r(x_min, y_min, clip_width, clip_height),
291                Some(clip),
292            );
293        }
294    }
295}