1use crate::cast::Cast;
9use crate::decorations::{Border, Decorations, TitleBar};
10use crate::dir::Directional;
11use crate::event::{ConfigCx, Event, EventCx, IsUsed, ResizeDirection, Scroll, Unused, Used};
12use crate::geom::{Coord, Offset, Rect, Size};
13use crate::layout::{self, AlignHints, AxisInfo, SizeRules};
14use crate::theme::{DrawCx, FrameStyle, SizeCx};
15use crate::{Action, Events, Icon, Id, Layout, LayoutExt, Widget};
16use kas_macros::impl_scope;
17use smallvec::SmallVec;
18use std::num::NonZeroU32;
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
24pub struct WindowId(NonZeroU32);
25
26impl WindowId {
27 #[allow(unused)]
31 pub(crate) fn new(n: NonZeroU32) -> WindowId {
32 WindowId(n)
33 }
34
35 pub(crate) fn get(self) -> u32 {
36 self.0.get()
37 }
38}
39
40#[derive(Clone, Debug)]
44pub enum WindowCommand {
45 SetTitle(String),
47 SetIcon(Option<Icon>),
49}
50
51impl_scope! {
52 #[widget]
60 pub struct Window<Data: 'static> {
61 core: widget_core!(),
62 icon: Option<Icon>, decorations: Decorations,
64 restrictions: (bool, bool),
65 drag_anywhere: bool,
66 transparent: bool,
67 #[widget]
68 inner: Box<dyn Widget<Data = Data>>,
69 #[widget(&())]
70 title_bar: TitleBar,
71 #[widget(&())] b_w: Border,
72 #[widget(&())] b_e: Border,
73 #[widget(&())] b_n: Border,
74 #[widget(&())] b_s: Border,
75 #[widget(&())] b_nw: Border,
76 #[widget(&())] b_ne: Border,
77 #[widget(&())] b_sw: Border,
78 #[widget(&())] b_se: Border,
79 bar_h: i32,
80 dec_offset: Offset,
81 dec_size: Size,
82 popups: SmallVec<[(WindowId, kas::PopupDescriptor, Offset); 16]>,
83 }
84
85 impl Layout for Self {
86 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
87 let mut inner = self.inner.size_rules(sizer.re(), axis);
88
89 self.bar_h = 0;
90 if matches!(self.decorations, Decorations::Toolkit) {
91 let bar = self.title_bar.size_rules(sizer.re(), axis);
92 if axis.is_horizontal() {
93 inner.max_with(bar);
94 } else {
95 inner.append(bar);
96 self.bar_h = bar.min_size();
97 }
98 }
99
100 let _ = self.b_w.size_rules(sizer.re(), axis);
102 let _ = self.b_e.size_rules(sizer.re(), axis);
103 let _ = self.b_n.size_rules(sizer.re(), axis);
104 let _ = self.b_s.size_rules(sizer.re(), axis);
105 let _ = self.b_nw.size_rules(sizer.re(), axis);
106 let _ = self.b_ne.size_rules(sizer.re(), axis);
107 let _ = self.b_se.size_rules(sizer.re(), axis);
108 let _ = self.b_sw.size_rules(sizer.re(), axis);
109
110 if matches!(self.decorations, Decorations::Border | Decorations::Toolkit) {
111 let frame = sizer.frame(FrameStyle::Window, axis);
112 let (rules, offset, size) = frame.surround(inner);
113 self.dec_offset.set_component(axis, offset);
114 self.dec_size.set_component(axis, size);
115 rules
116 } else {
117 inner
118 }
119 }
120
121 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
122 self.core.rect = rect;
123 let s_nw: Size = self.dec_offset.cast();
125 let s_se = self.dec_size - s_nw;
126 let mut s_in = rect.size - self.dec_size;
127 let p_nw = rect.pos;
128 let mut p_in = p_nw + self.dec_offset;
129 let p_se = p_in + s_in;
130
131 self.b_w.set_rect(cx, Rect::new(Coord(p_nw.0, p_in.1), Size(s_nw.0, s_in.1)), hints);
132 self.b_e.set_rect(cx, Rect::new(Coord(p_se.0, p_in.1), Size(s_se.0, s_in.1)), hints);
133 self.b_n.set_rect(cx, Rect::new(Coord(p_in.0, p_nw.1), Size(s_in.0, s_nw.1)), hints);
134 self.b_s.set_rect(cx, Rect::new(Coord(p_in.0, p_se.1), Size(s_in.0, s_se.1)), hints);
135 self.b_nw.set_rect(cx, Rect::new(p_nw, s_nw), hints);
136 self.b_ne.set_rect(cx, Rect::new(Coord(p_se.0, p_nw.1), Size(s_se.0, s_nw.1)), hints);
137 self.b_se.set_rect(cx, Rect::new(p_se, s_se), hints);
138 self.b_sw.set_rect(cx, Rect::new(Coord(p_nw.0, p_se.1), Size(s_nw.0, s_se.1)), hints);
139
140 if self.bar_h > 0 {
141 let bar_size = Size(s_in.0, self.bar_h);
142 self.title_bar.set_rect(cx, Rect::new(p_in, bar_size), hints);
143 p_in.1 += self.bar_h;
144 s_in -= Size(0, self.bar_h);
145 }
146 self.inner.set_rect(cx, Rect::new(p_in, s_in), hints);
147 }
148
149 fn find_id(&mut self, _: Coord) -> Option<Id> {
150 unimplemented!()
151 }
152
153 fn draw(&mut self, _: DrawCx) {
154 unimplemented!()
155 }
156 }
157
158 impl Self {
159 pub(crate) fn find_id(&mut self, data: &Data, coord: Coord) -> Option<Id> {
160 if !self.core.rect.contains(coord) {
161 return None;
162 }
163 for (_, popup, translation) in self.popups.iter_mut().rev() {
164 if let Some(Some(id)) = self.inner.as_node(data).find_node(&popup.id, |mut node| node.find_id(coord + *translation)) {
165 return Some(id);
166 }
167 }
168 if self.bar_h > 0 {
169 if let Some(id) = self.title_bar.find_id(coord) {
170 return Some(id);
171 }
172 }
173 self.inner.find_id(coord)
174 .or_else(|| self.b_w.find_id(coord))
175 .or_else(|| self.b_e.find_id(coord))
176 .or_else(|| self.b_n.find_id(coord))
177 .or_else(|| self.b_s.find_id(coord))
178 .or_else(|| self.b_nw.find_id(coord))
179 .or_else(|| self.b_ne.find_id(coord))
180 .or_else(|| self.b_sw.find_id(coord))
181 .or_else(|| self.b_se.find_id(coord))
182 .or_else(|| Some(self.id()))
183 }
184
185 #[cfg(winit)]
186 pub(crate) fn draw(&mut self, data: &Data, mut draw: DrawCx) {
187 if self.dec_size != Size::ZERO {
188 draw.frame(self.core.rect, FrameStyle::Window, Default::default());
189 if self.bar_h > 0 {
190 draw.recurse(&mut self.title_bar);
191 }
192 }
193 draw.recurse(&mut self.inner);
194 for (_, popup, translation) in &self.popups {
195 self.inner.as_node(data).find_node(&popup.id, |mut node| {
196 let clip_rect = node.rect() - *translation;
197 draw.with_overlay(clip_rect, *translation, |draw| {
198 node._draw(draw);
199 });
200 });
201 }
202 }
203 }
204
205 impl Events for Self {
206 type Data = Data;
207
208 fn configure(&mut self, cx: &mut ConfigCx) {
209 if cx.platform().is_wayland() && self.decorations == Decorations::Server {
210 self.decorations = Decorations::Toolkit;
214 }
215 }
216
217 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
218 match event {
219 Event::PressStart { .. } if self.drag_anywhere => {
220 cx.drag_window();
221 Used
222 }
223 _ => Unused,
224 }
225 }
226
227 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
228 if let Some(cmd) = cx.try_pop() {
229 match cmd {
230 WindowCommand::SetTitle(title) => {
231 cx.action(self.id(), self.title_bar.set_title(title));
232 #[cfg(winit)]
233 if self.decorations == Decorations::Server {
234 if let Some(w) = cx.winit_window() {
235 w.set_title(self.title());
236 }
237 }
238 }
239 WindowCommand::SetIcon(icon) => {
240 #[cfg(winit)]
241 if self.decorations == Decorations::Server {
242 if let Some(w) = cx.winit_window() {
243 w.set_window_icon(icon);
244 return; }
246 }
247 self.icon = icon;
248 }
249 }
250 }
251 }
252
253 fn handle_scroll(&mut self, cx: &mut EventCx, data: &Data, _: Scroll) {
254 self.resize_popups(&mut cx.config_cx(), data);
256 }
257 }
258
259 impl std::fmt::Debug for Self {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261 f.debug_struct("Window")
262 .field("core", &self.core)
263 .field("title", &self.title_bar.title())
264 .finish()
265 }
266 }
267}
268
269impl<Data: 'static> Window<Data> {
270 pub fn new(ui: impl Widget<Data = Data> + 'static, title: impl ToString) -> Self {
272 Self::new_boxed(Box::new(ui), title)
273 }
274
275 pub fn new_boxed(ui: Box<dyn Widget<Data = Data>>, title: impl ToString) -> Self {
277 Window {
278 core: Default::default(),
279 icon: None,
280 decorations: Decorations::Server,
281 restrictions: (true, false),
282 drag_anywhere: true,
283 transparent: false,
284 inner: ui,
285 title_bar: TitleBar::new(title),
286 b_w: Border::new(ResizeDirection::West),
287 b_e: Border::new(ResizeDirection::East),
288 b_n: Border::new(ResizeDirection::North),
289 b_s: Border::new(ResizeDirection::South),
290 b_nw: Border::new(ResizeDirection::NorthWest),
291 b_ne: Border::new(ResizeDirection::NorthEast),
292 b_sw: Border::new(ResizeDirection::SouthWest),
293 b_se: Border::new(ResizeDirection::SouthEast),
294 bar_h: 0,
295 dec_offset: Default::default(),
296 dec_size: Default::default(),
297 popups: Default::default(),
298 }
299 }
300
301 pub fn title(&self) -> &str {
303 self.title_bar.title()
304 }
305
306 pub(crate) fn icon(&mut self) -> Option<Icon> {
308 self.icon.clone()
309 }
310
311 pub fn with_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
315 self.icon = icon.into();
316 self
317 }
318
319 pub fn decorations(&self) -> Decorations {
321 self.decorations
322 }
323
324 pub fn with_decorations(mut self, decorations: Decorations) -> Self {
335 self.decorations = decorations;
336 self
337 }
338
339 pub fn restrictions(&self) -> (bool, bool) {
341 self.restrictions
342 }
343
344 pub fn with_restrictions(mut self, restrict_min: bool, restrict_max: bool) -> Self {
355 self.restrictions = (restrict_min, restrict_max);
356 let resizable = !restrict_min || !restrict_max;
357 self.b_w.set_resizable(resizable);
358 self.b_e.set_resizable(resizable);
359 self.b_n.set_resizable(resizable);
360 self.b_s.set_resizable(resizable);
361 self.b_nw.set_resizable(resizable);
362 self.b_ne.set_resizable(resizable);
363 self.b_se.set_resizable(resizable);
364 self.b_sw.set_resizable(resizable);
365 self
366 }
367
368 pub fn drag_anywhere(&self) -> bool {
370 self.drag_anywhere
371 }
372
373 pub fn with_drag_anywhere(mut self, drag_anywhere: bool) -> Self {
378 self.drag_anywhere = drag_anywhere;
379 self
380 }
381
382 pub fn transparent(&self) -> bool {
384 self.transparent
385 }
386
387 pub fn with_transparent(mut self, transparent: bool) -> Self {
399 self.transparent = transparent;
400 self
401 }
402
403 pub(crate) fn add_popup(
407 &mut self,
408 cx: &mut EventCx,
409 data: &Data,
410 id: WindowId,
411 popup: kas::PopupDescriptor,
412 ) {
413 let index = self.popups.len();
414 self.popups.push((id, popup, Offset::ZERO));
415 self.resize_popup(&mut cx.config_cx(), data, index);
416 cx.action(Id::ROOT, Action::REDRAW);
417 }
418
419 pub(crate) fn remove_popup(&mut self, cx: &mut EventCx, id: WindowId) {
423 for i in 0..self.popups.len() {
424 if id == self.popups[i].0 {
425 self.popups.remove(i);
426 cx.action(Id::ROOT, Action::REGION_MOVED);
427 return;
428 }
429 }
430 }
431
432 pub(crate) fn resize_popups(&mut self, cx: &mut ConfigCx, data: &Data) {
437 for i in 0..self.popups.len() {
438 self.resize_popup(cx, data, i);
439 }
440 }
441}
442
443fn find_rect(widget: &dyn Layout, id: Id, mut translation: Offset) -> Option<(Rect, Offset)> {
446 let mut widget = widget;
447 loop {
448 if widget.eq_id(&id) {
449 if widget.translation() != Offset::ZERO {
450 log::warn!(
452 "Parent of pop-up {} has non-zero translation",
453 widget.identify()
454 );
455 }
456
457 let rect = widget.rect();
458 return Some((rect, translation));
459 } else if let Some(child) = widget
460 .find_child_index(&id)
461 .and_then(|i| widget.get_child(i))
462 {
463 translation += widget.translation();
464 widget = child;
465 continue;
466 } else {
467 return None;
468 }
469 }
470}
471
472impl<Data: 'static> Window<Data> {
473 fn resize_popup(&mut self, cx: &mut ConfigCx, data: &Data, index: usize) {
474 let r = self.core.rect;
477 let (_, ref mut popup, ref mut translation) = self.popups[index];
478
479 let is_reversed = popup.direction.is_reversed();
480 let place_in = |rp, rs: i32, cp: i32, cs: i32, ideal, m: (u16, u16)| -> (i32, i32) {
481 let m: (i32, i32) = (m.0.into(), m.1.into());
482 let before: i32 = cp - (rp + m.1);
483 let before = before.max(0);
484 let after = (rs - (cs + before + m.0)).max(0);
485 if after >= ideal {
486 if is_reversed && before >= ideal {
487 (cp - ideal - m.1, ideal)
488 } else {
489 (cp + cs + m.0, ideal)
490 }
491 } else if before >= ideal {
492 (cp - ideal - m.1, ideal)
493 } else if before > after {
494 (rp, before)
495 } else {
496 (cp + cs + m.0, after)
497 }
498 };
499 let place_out = |rp, rs, cp: i32, cs, ideal: i32| -> (i32, i32) {
500 let pos = cp.min(rp + rs - ideal).max(rp);
501 let size = ideal.max(cs).min(rs);
502 (pos, size)
503 };
504
505 let (c, t) = find_rect(self.inner.as_layout(), popup.parent.clone(), Offset::ZERO).unwrap();
506 *translation = t;
507 let r = r + t; let result = self.inner.as_node(data).find_node(&popup.id, |mut node| {
509 let mut cache = layout::SolveCache::find_constraints(node.re(), cx.size_cx());
510 let ideal = cache.ideal(false);
511 let m = cache.margins();
512
513 let rect = if popup.direction.is_horizontal() {
514 let (x, w) = place_in(r.pos.0, r.size.0, c.pos.0, c.size.0, ideal.0, m.horiz);
515 let (y, h) = place_out(r.pos.1, r.size.1, c.pos.1, c.size.1, ideal.1);
516 Rect::new(Coord(x, y), Size::new(w, h))
517 } else {
518 let (x, w) = place_out(r.pos.0, r.size.0, c.pos.0, c.size.0, ideal.0);
519 let (y, h) = place_in(r.pos.1, r.size.1, c.pos.1, c.size.1, ideal.1, m.vert);
520 Rect::new(Coord(x, y), Size::new(w, h))
521 };
522
523 cache.apply_rect(node.re(), cx, rect, false);
524 cache.print_widget_heirarchy(node.as_layout());
525 });
526
527 assert!(result.is_some());
530 }
531}