1use super::{Decorations, Icon, Popup, PopupDescriptor, ResizeDirection, WindowId};
9use crate::cast::Cast;
10use crate::dir::{Direction, Directional};
11use crate::event::{Command, ConfigCx, Event, EventCx, IsUsed, Scroll, Unused, Used};
12use crate::geom::{Coord, Offset, Rect, Size};
13use crate::layout::{self, Align, AlignHints, AxisInfo, SizeRules};
14use crate::theme::{DrawCx, FrameStyle, SizeCx};
15use crate::widgets::{Border, Label, TitleBar};
16use crate::{Action, Events, Id, Layout, Role, RoleCx, Tile, TileExt, Widget};
17use kas_macros::{impl_self, widget_set_rect};
18use smallvec::SmallVec;
19
20pub(crate) trait WindowErased {
21 fn as_tile(&self) -> &dyn Tile;
22 fn show_tooltip(&mut self, cx: &mut EventCx, id: Id, text: String);
23 fn close_tooltip(&mut self, cx: &mut EventCx);
24}
25
26#[impl_self]
27mod Window {
28 #[widget]
39 pub struct Window<Data: 'static> {
40 core: widget_core!(),
41 icon: Option<Icon>, decorations: Decorations,
43 restrictions: (bool, bool),
44 drag_anywhere: bool,
45 transparent: bool,
46 escapable: bool,
47 alt_bypass: bool,
48 disable_nav_focus: bool,
49 #[widget]
50 inner: Box<dyn Widget<Data = Data>>,
51 #[widget(&())]
52 tooltip: Popup<Label<String>>,
53 #[widget(&())]
54 title_bar: TitleBar,
55 #[widget(&())]
56 b_w: Border,
57 #[widget(&())]
58 b_e: Border,
59 #[widget(&())]
60 b_n: Border,
61 #[widget(&())]
62 b_s: Border,
63 #[widget(&())]
64 b_nw: Border,
65 #[widget(&())]
66 b_ne: Border,
67 #[widget(&())]
68 b_sw: Border,
69 #[widget(&())]
70 b_se: Border,
71 bar_h: i32,
72 dec_offset: Offset,
73 dec_size: Size,
74 popups: SmallVec<[(WindowId, PopupDescriptor, Offset); 16]>,
75 }
76
77 impl Layout for Self {
78 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
79 let mut inner = self.inner.size_rules(sizer.re(), axis);
80
81 self.bar_h = 0;
82 if matches!(self.decorations, Decorations::Toolkit) {
83 let bar = self.title_bar.size_rules(sizer.re(), axis);
84 if axis.is_horizontal() {
85 inner.max_with(bar);
86 } else {
87 inner.append(bar);
88 self.bar_h = bar.min_size();
89 }
90 }
91
92 let _ = self.b_w.size_rules(sizer.re(), axis);
94 let _ = self.b_e.size_rules(sizer.re(), axis);
95 let _ = self.b_n.size_rules(sizer.re(), axis);
96 let _ = self.b_s.size_rules(sizer.re(), axis);
97 let _ = self.b_nw.size_rules(sizer.re(), axis);
98 let _ = self.b_ne.size_rules(sizer.re(), axis);
99 let _ = self.b_se.size_rules(sizer.re(), axis);
100 let _ = self.b_sw.size_rules(sizer.re(), axis);
101
102 if matches!(self.decorations, Decorations::Border | Decorations::Toolkit) {
103 let frame = sizer.frame(FrameStyle::Window, axis);
104 let (rules, offset, size) = frame.surround(inner);
105 self.dec_offset.set_component(axis, offset);
106 self.dec_size.set_component(axis, size);
107 rules
108 } else {
109 inner
110 }
111 }
112
113 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
114 widget_set_rect!(rect);
115 let s_nw: Size = self.dec_offset.cast();
117 let s_se = self.dec_size - s_nw;
118 let mut s_in = rect.size - self.dec_size;
119 let p_nw = rect.pos;
120 let mut p_in = p_nw + self.dec_offset;
121 let p_se = p_in + s_in;
122
123 self.b_w.set_rect(
124 cx,
125 Rect::new(Coord(p_nw.0, p_in.1), Size(s_nw.0, s_in.1)),
126 hints,
127 );
128 self.b_e.set_rect(
129 cx,
130 Rect::new(Coord(p_se.0, p_in.1), Size(s_se.0, s_in.1)),
131 hints,
132 );
133 self.b_n.set_rect(
134 cx,
135 Rect::new(Coord(p_in.0, p_nw.1), Size(s_in.0, s_nw.1)),
136 hints,
137 );
138 self.b_s.set_rect(
139 cx,
140 Rect::new(Coord(p_in.0, p_se.1), Size(s_in.0, s_se.1)),
141 hints,
142 );
143 self.b_nw.set_rect(cx, Rect::new(p_nw, s_nw), hints);
144 self.b_ne.set_rect(
145 cx,
146 Rect::new(Coord(p_se.0, p_nw.1), Size(s_se.0, s_nw.1)),
147 hints,
148 );
149 self.b_se.set_rect(cx, Rect::new(p_se, s_se), hints);
150 self.b_sw.set_rect(
151 cx,
152 Rect::new(Coord(p_nw.0, p_se.1), Size(s_nw.0, s_se.1)),
153 hints,
154 );
155
156 if self.bar_h > 0 {
157 let bar_size = Size(s_in.0, self.bar_h);
158 self.title_bar
159 .set_rect(cx, Rect::new(p_in, bar_size), hints);
160 p_in.1 += self.bar_h;
161 s_in -= Size(0, self.bar_h);
162 }
163 self.inner.set_rect(cx, Rect::new(p_in, s_in), hints);
164 }
165
166 fn draw(&self, mut draw: DrawCx) {
167 for (_, popup, translation) in &self.popups {
169 if let Some(child) = self.find_tile(&popup.id) {
170 let clip_rect = child.rect() - *translation;
172 draw.with_overlay(clip_rect, *translation, |draw| {
173 child.draw(draw);
174 });
175 }
176 }
177
178 if self.dec_size != Size::ZERO {
179 draw.frame(self.rect(), FrameStyle::Window, Default::default());
180 if self.bar_h > 0 {
181 self.title_bar.draw(draw.re());
182 }
183 }
184 self.inner.draw(draw.re());
185 }
186 }
187
188 impl Tile for Self {
189 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
190 Role::Window
191 }
192
193 fn probe(&self, coord: Coord) -> Id {
194 for (_, popup, translation) in self.popups.iter().rev() {
195 if let Some(widget) = self.inner.find_tile(&popup.id)
196 && let Some(id) = widget.try_probe(coord + *translation)
197 {
198 return id;
199 }
200 }
201 if self.bar_h > 0
202 && let Some(id) = self.title_bar.try_probe(coord)
203 {
204 return id;
205 }
206 self.inner
207 .try_probe(coord)
208 .or_else(|| self.b_w.try_probe(coord))
209 .or_else(|| self.b_e.try_probe(coord))
210 .or_else(|| self.b_n.try_probe(coord))
211 .or_else(|| self.b_s.try_probe(coord))
212 .or_else(|| self.b_nw.try_probe(coord))
213 .or_else(|| self.b_ne.try_probe(coord))
214 .or_else(|| self.b_sw.try_probe(coord))
215 .or_else(|| self.b_se.try_probe(coord))
216 .unwrap_or_else(|| self.id())
217 }
218 }
219
220 impl Events for Self {
221 type Data = Data;
222
223 fn configure(&mut self, cx: &mut ConfigCx) {
224 if cx.platform().is_wayland() && self.decorations == Decorations::Server {
225 self.decorations = Decorations::Toolkit;
229 }
230
231 if self.alt_bypass {
232 cx.config.alt_bypass = true;
233 }
234
235 if self.disable_nav_focus {
236 cx.config.nav_focus = false;
237 }
238 }
239
240 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
241 match event {
242 Event::Command(Command::Escape, _) => {
243 if let Some(id) = self.popups.last().map(|desc| desc.0) {
244 cx.close_window(id);
245 } else if self.escapable {
246 cx.window_action(Action::CLOSE);
247 }
248 Used
249 }
250 Event::PressStart(_) if self.drag_anywhere => {
251 cx.drag_window();
252 Used
253 }
254 Event::Timer(handle) if handle == crate::event::Mouse::TIMER_HOVER => {
255 cx.hover_timer_expiry(self);
256 Used
257 }
258 _ => Unused,
259 }
260 }
261
262 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
263 if let Some(kas::messages::SetWindowTitle(title)) = cx.try_pop() {
264 self.title_bar.set_title(cx, title);
265 if self.decorations == Decorations::Server
266 && let Some(w) = cx.winit_window()
267 {
268 w.set_title(self.title());
269 }
270 } else if let Some(kas::messages::SetWindowIcon(icon)) = cx.try_pop() {
271 if self.decorations == Decorations::Server
272 && let Some(w) = cx.winit_window()
273 {
274 w.set_window_icon(icon);
275 return; }
277 self.icon = icon;
278 }
279 }
280
281 fn handle_scroll(&mut self, cx: &mut EventCx, data: &Data, _: Scroll) {
282 self.resize_popups(&mut cx.config_cx(), data);
284 }
285 }
286
287 impl WindowErased for Self {
288 fn as_tile(&self) -> &dyn Tile {
289 self
290 }
291
292 fn show_tooltip(&mut self, cx: &mut EventCx, id: Id, text: String) {
293 self.tooltip.inner.set_string(cx, text);
294 self.tooltip.open(cx, &(), id, false);
295 }
296
297 fn close_tooltip(&mut self, cx: &mut EventCx) {
298 self.tooltip.close(cx);
299 }
300 }
301
302 impl std::fmt::Debug for Self {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 f.debug_struct("Window")
305 .field("core", &self.core)
306 .field("title", &self.title_bar.title())
307 .finish()
308 }
309 }
310}
311
312impl<Data: 'static> Window<Data> {
313 pub fn new(ui: impl Widget<Data = Data> + 'static, title: impl ToString) -> Self {
315 Self::new_boxed(Box::new(ui), title)
316 }
317
318 pub fn new_boxed(ui: Box<dyn Widget<Data = Data>>, title: impl ToString) -> Self {
320 Window {
321 core: Default::default(),
322 icon: None,
323 decorations: Decorations::Server,
324 restrictions: (true, false),
325 drag_anywhere: true,
326 transparent: false,
327 escapable: false,
328 alt_bypass: false,
329 disable_nav_focus: false,
330 inner: ui,
331 tooltip: Popup::new(Label::default(), Direction::Down).align(Align::Center),
332 title_bar: TitleBar::new(title),
333 b_w: Border::new(ResizeDirection::West),
334 b_e: Border::new(ResizeDirection::East),
335 b_n: Border::new(ResizeDirection::North),
336 b_s: Border::new(ResizeDirection::South),
337 b_nw: Border::new(ResizeDirection::NorthWest),
338 b_ne: Border::new(ResizeDirection::NorthEast),
339 b_sw: Border::new(ResizeDirection::SouthWest),
340 b_se: Border::new(ResizeDirection::SouthEast),
341 bar_h: 0,
342 dec_offset: Default::default(),
343 dec_size: Default::default(),
344 popups: Default::default(),
345 }
346 }
347
348 pub fn title(&self) -> &str {
350 self.title_bar.title()
351 }
352
353 pub(crate) fn icon(&mut self) -> Option<Icon> {
355 self.icon.clone()
356 }
357
358 pub fn with_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
362 self.icon = icon.into();
363 self
364 }
365
366 pub fn decorations(&self) -> Decorations {
368 self.decorations
369 }
370
371 pub fn with_decorations(mut self, decorations: Decorations) -> Self {
382 self.decorations = decorations;
383 self
384 }
385
386 pub fn restrictions(&self) -> (bool, bool) {
388 self.restrictions
389 }
390
391 pub fn with_restrictions(mut self, restrict_min: bool, restrict_max: bool) -> Self {
402 self.restrictions = (restrict_min, restrict_max);
403 let resizable = !restrict_min || !restrict_max;
404 self.b_w.set_resizable(resizable);
405 self.b_e.set_resizable(resizable);
406 self.b_n.set_resizable(resizable);
407 self.b_s.set_resizable(resizable);
408 self.b_nw.set_resizable(resizable);
409 self.b_ne.set_resizable(resizable);
410 self.b_se.set_resizable(resizable);
411 self.b_sw.set_resizable(resizable);
412 self
413 }
414
415 pub fn drag_anywhere(&self) -> bool {
417 self.drag_anywhere
418 }
419
420 pub fn with_drag_anywhere(mut self, drag_anywhere: bool) -> Self {
425 self.drag_anywhere = drag_anywhere;
426 self
427 }
428
429 pub fn transparent(&self) -> bool {
431 self.transparent
432 }
433
434 pub fn with_transparent(mut self, transparent: bool) -> Self {
446 self.transparent = transparent;
447 self
448 }
449
450 pub fn escapable(mut self) -> Self {
452 self.escapable = true;
453 self
454 }
455
456 pub fn with_alt_bypass(mut self) -> Self {
461 self.alt_bypass = true;
462 self
463 }
464
465 pub fn without_nav_focus(mut self) -> Self {
470 self.disable_nav_focus = true;
471 self
472 }
473
474 pub(crate) fn add_popup(
478 &mut self,
479 cx: &mut ConfigCx,
480 data: &Data,
481 id: WindowId,
482 popup: PopupDescriptor,
483 ) {
484 let index = 'index: {
485 for i in 0..self.popups.len() {
486 if self.popups[i].0 == id {
487 debug_assert_eq!(self.popups[i].1.id, popup.id);
488 self.popups[i].1 = popup;
489 break 'index i;
490 }
491 }
492
493 let len = self.popups.len();
494 self.popups.push((id, popup, Offset::ZERO));
495 len
496 };
497
498 self.resize_popup(cx, data, index);
499 cx.confirm_popup_is_sized(id);
500 cx.action(self.id(), Action::REGION_MOVED);
501 }
502
503 pub(crate) fn remove_popup(&mut self, cx: &mut ConfigCx, id: WindowId) {
507 for i in 0..self.popups.len() {
508 if id == self.popups[i].0 {
509 self.popups.remove(i);
510 cx.action(self.id(), Action::REGION_MOVED);
511 return;
512 }
513 }
514 }
515
516 pub(crate) fn resize_popups(&mut self, cx: &mut ConfigCx, data: &Data) {
521 for i in 0..self.popups.len() {
522 self.resize_popup(cx, data, i);
523 }
524 }
525
526 #[cfg(feature = "accesskit")]
528 pub(crate) fn iter_popups(&self) -> impl Iterator<Item = &PopupDescriptor> {
529 self.popups.iter().map(|(_, popup, _)| popup)
530 }
531}
532
533impl<Data: 'static> Window<Data> {
534 fn resize_popup(&mut self, cx: &mut ConfigCx, data: &Data, index: usize) {
535 let r = self.rect();
538 let popup = self.popups[index].1.clone();
539
540 let is_reversed = popup.direction.is_reversed();
541 let place_in = |rp, rs: i32, cp: i32, cs: i32, ideal, m: (u16, u16)| -> (i32, i32) {
542 let m: (i32, i32) = (m.0.into(), m.1.into());
543 let before: i32 = cp - (rp + m.1);
544 let before = before.max(0);
545 let after = (rs - (cs + before + m.0)).max(0);
546 if after >= ideal {
547 if is_reversed && before >= ideal {
548 (cp - ideal - m.1, ideal)
549 } else {
550 (cp + cs + m.0, ideal)
551 }
552 } else if before >= ideal {
553 (cp - ideal - m.1, ideal)
554 } else if before > after {
555 (rp, before)
556 } else {
557 (cp + cs + m.0, after)
558 }
559 };
560 let place_out = |rp, rs, cp: i32, cs, ideal: i32, align| -> (i32, i32) {
561 let mut size = ideal.max(cs).min(rs);
562 let pos = match align {
563 Align::Default | Align::TL => cp,
564 Align::BR => cp + cs,
565 Align::Center => cp + (cs - size) / 2,
566 Align::Stretch => {
567 size = size.max(cs);
568 cp
569 }
570 };
571 let pos = pos.min(rp + rs - size).max(rp);
572 (pos, size)
573 };
574
575 let Some((c, t)) = self.as_tile().find_tile_rect(&popup.parent) else {
576 return;
577 };
578 self.popups[index].2 = t;
579 let r = r + t; let result = self.as_node(data).find_node(&popup.id, |mut node| {
581 let mut cache = layout::SolveCache::find_constraints(node.re(), cx.size_cx());
582 let ideal = cache.ideal(false);
583 let m = cache.margins();
584
585 let rect = if popup.direction.is_horizontal() {
586 let (x, w) = place_in(r.pos.0, r.size.0, c.pos.0, c.size.0, ideal.0, m.horiz);
587 let (y, h) = place_out(r.pos.1, r.size.1, c.pos.1, c.size.1, ideal.1, popup.align);
588 Rect::new(Coord(x, y), Size::new(w, h))
589 } else {
590 let (x, w) = place_out(r.pos.0, r.size.0, c.pos.0, c.size.0, ideal.0, popup.align);
591 let (y, h) = place_in(r.pos.1, r.size.1, c.pos.1, c.size.1, ideal.1, m.vert);
592 Rect::new(Coord(x, y), Size::new(w, h))
593 };
594
595 cache.apply_rect(node.re(), cx, rect, false);
596 cache.print_widget_heirarchy(node.as_tile());
597 });
598
599 assert!(result.is_some());
602 }
603}