1use std::cell::{Cell, RefCell};
32use std::rc::Rc;
33use std::sync::Arc;
34
35use web_time::Instant;
36
37use crate::color::Color;
38use crate::cursor::{CursorIcon, set_cursor_icon};
39use crate::event::{Event, EventResult, MouseButton};
40use crate::geometry::{Point, Rect, Size};
41use crate::draw_ctx::DrawCtx;
42use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
43use crate::text::Font;
44use crate::widget::{Widget, paint_subtree};
45use crate::widgets::window_title_bar::{TitleBarView, WindowTitleBar};
46
47fn snap(r: Rect) -> Rect {
50 Rect::new(r.x.round(), r.y.round(), r.width.round(), r.height.round())
51}
52
53const TITLE_H: f64 = 28.0;
54const CORNER_R: f64 = 8.0;
55const SHADOW_BLUR: f64 = 14.0;
58const SHADOW_DX: f64 = 2.0;
60const SHADOW_DY: f64 = 6.0;
61const SHADOW_STEPS: usize = 10;
63const CLOSE_R: f64 = 6.0;
64const CLOSE_PAD: f64 = 10.0;
65const MAX_PAD: f64 = CLOSE_PAD + CLOSE_R * 2.0 + 4.0; const RESIZE_EDGE: f64 = 6.0; const MIN_W: f64 = 120.0;
70const MIN_H: f64 = 80.0;
71const DBL_CLICK_MS: u128 = 500; #[derive(Clone, Copy, Debug, PartialEq)]
77enum ResizeDir {
78 N, NE, E, SE, S, SW, W, NW,
79}
80
81#[derive(Clone, Copy, Debug, PartialEq)]
85enum DragMode {
86 None,
87 Move,
88 Resize(ResizeDir),
89}
90
91pub struct Window {
93 bounds: Rect,
94 children: Vec<Box<dyn Widget>>, base: WidgetBase,
96
97 font_size: f64,
98
99 visible: bool,
100 visible_cell: Option<Rc<Cell<bool>>>,
101 reset_to: Option<Rc<Cell<Option<Rect>>>>,
102 position_cell: Option<Rc<Cell<Rect>>>,
103
104 last_visible: Cell<bool>,
108 raise_request: Cell<bool>,
111
112 collapsed: bool,
113 pre_collapse_h: f64,
115
116 drag_mode: DragMode,
117 drag_start_world: Point,
119 drag_start_bounds: Rect,
121
122 close_hovered: bool,
123 on_close: Option<Box<dyn FnMut()>>,
124
125 maximized: bool,
127 pre_maximize_bounds: Rect,
129 maximize_hovered: bool,
130
131 hover_dir: Option<ResizeDir>,
134
135 last_title_click: Option<Instant>,
137
138 title_bar: WindowTitleBar,
144 title_state: Rc<RefCell<TitleBarView>>,
145
146 canvas_size: Size,
148 constrain: bool,
150
151 auto_size: bool,
156
157 title: String,
161 on_raised: Option<Box<dyn FnMut(&str)>>,
167}
168
169impl Window {
170 pub fn new(title: impl Into<String>, font: Arc<Font>, content: Box<dyn Widget>) -> Self {
175 let font_size = 13.0;
176 let title_str: String = title.into();
177 let title_state = Rc::new(RefCell::new(TitleBarView::default_visuals()));
178 let title_bar = WindowTitleBar::new(&title_str, Arc::clone(&font), Rc::clone(&title_state));
179 Self {
180 bounds: Rect::new(60.0, 60.0, 360.0, 280.0),
181 children: vec![content],
182 base: WidgetBase::new(),
183 font_size,
184 visible: true,
185 visible_cell: None,
186 reset_to: None,
187 position_cell: None,
188 last_visible: Cell::new(true),
192 raise_request: Cell::new(false),
193 collapsed: false,
194 pre_collapse_h: 280.0,
195 drag_mode: DragMode::None,
196 drag_start_world: Point::ORIGIN,
197 drag_start_bounds: Rect::default(),
198 close_hovered: false,
199 on_close: None,
200 maximized: false,
201 pre_maximize_bounds: Rect::new(60.0, 60.0, 360.0, 280.0),
202 maximize_hovered: false,
203 hover_dir: None,
204 last_title_click: None,
205 title_bar,
206 title_state,
207 canvas_size: Size::new(0.0, 0.0),
216 constrain: true,
217 auto_size: false,
218 title: title_str,
219 on_raised: None,
220 }
221 }
222
223 pub fn title(&self) -> &str { &self.title }
225
226 pub fn on_raised(mut self, cb: impl FnMut(&str) + 'static) -> Self {
231 self.on_raised = Some(Box::new(cb));
232 self
233 }
234
235 pub fn with_bounds(mut self, b: Rect) -> Self {
236 self.pre_collapse_h = b.height;
237 self.bounds = b;
238 self
239 }
240 pub fn with_font_size(mut self, size: f64) -> Self { self.font_size = size; self }
241
242 pub fn with_visible_cell(mut self, cell: Rc<Cell<bool>>) -> Self {
243 self.visible_cell = Some(cell);
244 self
245 }
246
247 pub fn with_reset_cell(mut self, cell: Rc<Cell<Option<Rect>>>) -> Self {
248 self.reset_to = Some(cell);
249 self
250 }
251
252 pub fn with_position_cell(mut self, cell: Rc<Cell<Rect>>) -> Self {
253 self.position_cell = Some(cell);
254 self
255 }
256
257 pub fn with_margin(mut self, m: Insets) -> Self { self.base.margin = m; self }
258 pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
259 pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
260 pub fn with_min_size(mut self, s: Size) -> Self { self.base.min_size = s; self }
261 pub fn with_max_size(mut self, s: Size) -> Self { self.base.max_size = s; self }
262
263 pub fn with_constrain(mut self, constrain: bool) -> Self { self.constrain = constrain; self }
264
265 pub fn with_auto_size(mut self, auto: bool) -> Self { self.auto_size = auto; self }
268
269 pub fn on_close(mut self, cb: impl FnMut() + 'static) -> Self {
270 self.on_close = Some(Box::new(cb));
271 self
272 }
273
274 fn clamp_to_canvas(&mut self) {
275 if !self.constrain { return; }
276 let cw = self.canvas_size.width;
277 let ch = self.canvas_size.height;
278 const MIN_H_VISIBLE: f64 = 40.0;
286
287 let min_x = MIN_H_VISIBLE - self.bounds.width;
288 let max_x = (cw - MIN_H_VISIBLE).max(min_x);
289 self.bounds.x = self.bounds.x.clamp(min_x, max_x).round();
290
291 let min_y = TITLE_H - self.bounds.height;
296 let max_y = (ch - self.bounds.height).max(min_y);
297 self.bounds.y = self.bounds.y.clamp(min_y, max_y).round();
298 }
299
300 pub fn show(&mut self) { self.visible = true; }
301 pub fn hide(&mut self) { self.visible = false; }
302 pub fn toggle(&mut self) { self.visible = !self.visible; }
303 pub fn is_visible(&self) -> bool {
309 if let Some(ref cell) = self.visible_cell { cell.get() } else { self.visible }
310 }
311
312 fn title_bar_bottom(&self) -> f64 { self.bounds.height - TITLE_H }
313
314 fn in_title_bar(&self, local: Point) -> bool {
315 local.y >= self.title_bar_bottom() && local.y <= self.bounds.height
316 && local.x >= 0.0 && local.x <= self.bounds.width
317 }
318
319 fn close_center(&self) -> Point {
320 Point::new(
321 self.bounds.width - CLOSE_PAD,
322 self.bounds.height - TITLE_H * 0.5,
323 )
324 }
325
326 fn in_close_button(&self, local: Point) -> bool {
327 let c = self.close_center();
328 let dx = local.x - c.x;
329 let dy = local.y - c.y;
330 dx * dx + dy * dy <= (CLOSE_R + 3.0) * (CLOSE_R + 3.0)
331 }
332
333 fn maximize_center(&self) -> Point {
334 Point::new(
335 self.bounds.width - MAX_PAD,
336 self.bounds.height - TITLE_H * 0.5,
337 )
338 }
339
340 fn in_maximize_button(&self, local: Point) -> bool {
341 let c = self.maximize_center();
342 let dx = local.x - c.x;
343 let dy = local.y - c.y;
344 dx * dx + dy * dy <= (CLOSE_R + 3.0) * (CLOSE_R + 3.0)
345 }
346
347 fn in_chevron_button(&self, local: Point) -> bool {
353 let cx = 12.0;
354 let cy = self.bounds.height - TITLE_H * 0.5;
355 let half = 8.0;
356 local.x >= cx - half && local.x <= cx + half
357 && local.y >= cy - half && local.y <= cy + half
358 }
359
360 fn toggle_collapse(&mut self) {
364 let top = self.bounds.y + self.bounds.height;
365 if self.collapsed {
366 self.bounds.height = self.pre_collapse_h;
367 self.bounds.y = (top - self.pre_collapse_h).round();
368 self.collapsed = false;
369 } else {
370 self.pre_collapse_h = self.bounds.height;
371 self.bounds.height = TITLE_H;
372 self.bounds.y = (top - TITLE_H).round();
373 self.collapsed = true;
374 }
375 self.clamp_to_canvas();
376 }
377
378 fn toggle_maximize(&mut self) {
379 if self.maximized {
380 self.bounds = self.pre_maximize_bounds;
381 self.maximized = false;
382 } else {
383 self.pre_maximize_bounds = self.bounds;
384 self.bounds = snap(Rect::new(
385 0.0, 0.0,
386 self.canvas_size.width,
387 self.canvas_size.height,
388 ));
389 self.maximized = true;
390 }
391 }
392
393 fn resize_dir(&self, local: Point) -> Option<ResizeDir> {
398 if self.collapsed || self.auto_size { return None; }
399 let w = self.bounds.width;
400 let h = self.bounds.height;
401 let x = local.x;
402 let y = local.y;
403
404 if x < 0.0 || x > w || y < 0.0 || y > h { return None; }
406
407 let on_n = y > h - RESIZE_EDGE;
408 let on_s = y < RESIZE_EDGE;
409 let on_w = x < RESIZE_EDGE;
410 let on_e = x > w - RESIZE_EDGE;
411
412 match (on_n, on_e, on_s, on_w) {
413 (true, true, _, _ ) => Some(ResizeDir::NE),
414 (true, _, _, true ) => Some(ResizeDir::NW),
415 (_, _, true, true ) => Some(ResizeDir::SW),
416 (_, true, true, _ ) => Some(ResizeDir::SE),
417 (true, _, _, _ ) => Some(ResizeDir::N),
418 (_, true, _, _ ) => Some(ResizeDir::E),
419 (_, _, true, _ ) => Some(ResizeDir::S),
420 (_, _, _, true ) => Some(ResizeDir::W),
421 _ => None,
422 }
423 }
424
425 fn apply_resize(&mut self, world_pos: Point) {
427 let dx = world_pos.x - self.drag_start_world.x;
428 let dy = world_pos.y - self.drag_start_world.y;
429 let sb = self.drag_start_bounds;
430
431 let (mut x, mut y, mut w, mut h) = (sb.x, sb.y, sb.width, sb.height);
432
433 if let DragMode::Resize(dir) = self.drag_mode {
434 match dir {
435 ResizeDir::N => { h = (sb.height + dy).max(MIN_H); }
436 ResizeDir::S => { y = sb.y + dy; h = (sb.height - dy).max(MIN_H); if h == MIN_H { y = sb.y + sb.height - MIN_H; } }
437 ResizeDir::E => { w = (sb.width + dx).max(MIN_W); }
438 ResizeDir::W => { x = sb.x + dx; w = (sb.width - dx).max(MIN_W); if w == MIN_W { x = sb.x + sb.width - MIN_W; } }
439 ResizeDir::NE => { w = (sb.width + dx).max(MIN_W); h = (sb.height + dy).max(MIN_H); }
440 ResizeDir::NW => { x = sb.x + dx; w = (sb.width - dx).max(MIN_W); if w == MIN_W { x = sb.x + sb.width - MIN_W; } h = (sb.height + dy).max(MIN_H); }
441 ResizeDir::SE => { w = (sb.width + dx).max(MIN_W); y = sb.y + dy; h = (sb.height - dy).max(MIN_H); if h == MIN_H { y = sb.y + sb.height - MIN_H; } }
442 ResizeDir::SW => { x = sb.x + dx; w = (sb.width - dx).max(MIN_W); if w == MIN_W { x = sb.x + sb.width - MIN_W; } y = sb.y + dy; h = (sb.height - dy).max(MIN_H); if h == MIN_H { y = sb.y + sb.height - MIN_H; } }
443 }
444 }
445
446 self.bounds = snap(Rect::new(x, y, w, h));
447 self.clamp_to_canvas();
448 }
449}
450
451fn resize_cursor(dir: ResizeDir) -> CursorIcon {
453 match dir {
454 ResizeDir::N => CursorIcon::ResizeNorth,
455 ResizeDir::S => CursorIcon::ResizeSouth,
456 ResizeDir::E => CursorIcon::ResizeEast,
457 ResizeDir::W => CursorIcon::ResizeWest,
458 ResizeDir::NE => CursorIcon::ResizeNorthEast,
459 ResizeDir::NW => CursorIcon::ResizeNorthWest,
460 ResizeDir::SE => CursorIcon::ResizeSouthEast,
461 ResizeDir::SW => CursorIcon::ResizeSouthWest,
462 }
463}
464
465impl Widget for Window {
466 fn type_name(&self) -> &'static str { "Window" }
467 fn id(&self) -> Option<&str> { Some(&self.title) }
469
470 fn is_visible(&self) -> bool {
471 if let Some(ref cell) = self.visible_cell { cell.get() } else { self.visible }
472 }
473
474 fn needs_paint(&self) -> bool {
481 if !self.is_visible() || self.collapsed { return false; }
482 self.children().iter().any(|c| c.needs_paint())
483 }
484
485 fn next_paint_deadline(&self) -> Option<web_time::Instant> {
486 if !self.is_visible() || self.collapsed { return None; }
487 let mut best: Option<web_time::Instant> = None;
488 for c in self.children() {
489 if let Some(t) = c.next_paint_deadline() {
490 best = Some(match best { Some(b) if b <= t => b, _ => t });
491 }
492 }
493 best
494 }
495
496 fn bounds(&self) -> Rect { self.bounds }
497
498 fn margin(&self) -> Insets { self.base.margin }
499 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
500 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
501 fn min_size(&self) -> Size { self.base.min_size }
502 fn max_size(&self) -> Size { self.base.max_size }
503
504 fn take_raise_request(&mut self) -> bool {
507 let pending = self.raise_request.get();
508 self.raise_request.set(false);
509 pending
510 }
511
512 fn set_bounds(&mut self, b: Rect) {
513 if let Some(ref cell) = self.reset_to {
514 if let Some(new_b) = cell.get() {
515 self.bounds = new_b;
516 self.pre_collapse_h = new_b.height;
517 self.collapsed = false;
518 cell.set(None);
519 return;
520 }
521 }
522 if self.bounds.width == 0.0 || self.bounds.height == 0.0 {
523 self.bounds = b;
524 self.pre_collapse_h = b.height;
525 }
526 }
527
528 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
529 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
530
531 fn clip_children_rect(&self) -> Option<(f64, f64, f64, f64)> {
535 if !self.is_visible() { return None; }
536 let w = self.bounds.width;
537 let content_h = (self.bounds.height - TITLE_H).max(0.0);
538 Some((0.0, 0.0, w, content_h))
540 }
541
542 fn hit_test(&self, local_pos: Point) -> bool {
543 if !self.is_visible() { return false; }
544 if self.drag_mode != DragMode::None { return true; }
545 let b = self.bounds();
546 local_pos.x >= 0.0 && local_pos.x <= b.width
547 && local_pos.y >= 0.0 && local_pos.y <= b.height
548 }
549
550 fn layout(&mut self, available: Size) -> Size {
551 let now_visible = self.is_visible();
556 if now_visible && !self.last_visible.get() {
557 self.raise_request.set(true);
558 if let Some(cb) = self.on_raised.as_mut() { cb(&self.title); }
559 if self.maximized {
565 self.bounds = self.pre_maximize_bounds;
566 self.maximized = false;
567 }
568 }
569 self.last_visible.set(now_visible);
570
571 if !now_visible {
572 return Size::new(self.bounds.width, self.bounds.height);
573 }
574
575 if self.auto_size && !self.collapsed && !self.maximized {
587 if let Some(child) = self.children.first_mut() {
588 let max_sz = child.max_size();
589 let cap_w = if max_sz.width.is_finite() { max_sz.width }
590 else { available.width.max(MIN_W) };
591 let cap_h = if max_sz.height.is_finite() { max_sz.height }
592 else { available.height.max(MIN_H) };
593 let pref = child.layout(Size::new(cap_w, cap_h));
594 let new_w = pref.width.min(cap_w).max(MIN_W);
595 let new_h = (pref.height + TITLE_H).min(cap_h + TITLE_H).max(MIN_H);
596 let top = self.bounds.y + self.bounds.height;
597 self.bounds.width = new_w;
598 self.bounds.height = new_h;
599 self.bounds.y = top - new_h;
600 self.pre_collapse_h = new_h;
601 }
602 }
603
604 let content_h = (self.bounds.height - TITLE_H).max(0.0);
606
607 if let Some(child) = self.children.first_mut() {
608 if !self.collapsed {
609 child.layout(Size::new(self.bounds.width, content_h));
610 child.set_bounds(Rect::new(0.0, 0.0, self.bounds.width, content_h));
611 }
612 }
615
616 let tb_y = self.bounds.height - TITLE_H;
619 self.title_bar.set_bounds(Rect::new(0.0, tb_y, self.bounds.width, TITLE_H));
620 self.title_bar.layout(Size::new(self.bounds.width, TITLE_H));
621
622 self.canvas_size = available;
636 if let Some(ref cell) = self.position_cell {
637 let save_bounds = if self.maximized {
643 self.pre_maximize_bounds
644 } else {
645 self.bounds
646 };
647 cell.set(save_bounds);
648 }
649
650 Size::new(self.bounds.width, self.bounds.height)
651 }
652
653 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
654 if !self.is_visible() { return; }
655
656 let v = ctx.visuals();
657 let w = self.bounds.width;
658 let h = self.bounds.height;
660
661 let base = v.window_shadow;
665 for i in (0..SHADOW_STEPS).rev() {
666 let t = i as f64 / SHADOW_STEPS as f64;
667 let infl = t * SHADOW_BLUR;
668 let falloff = (1.0 - t).powi(2) as f32;
669 let alpha = base.a * falloff / SHADOW_STEPS as f32 * 6.0;
670 ctx.set_fill_color(Color::rgba(base.r, base.g, base.b, alpha));
671 ctx.begin_path();
672 ctx.rounded_rect(
673 SHADOW_DX - infl,
674 -SHADOW_DY - infl,
675 w + 2.0 * infl,
676 h + 2.0 * infl,
677 CORNER_R + infl,
678 );
679 ctx.fill();
680 }
681
682 ctx.set_fill_color(v.window_fill);
684 ctx.begin_path();
685 ctx.rounded_rect(0.0, 0.0, w, h, CORNER_R);
686 ctx.fill();
687
688 {
694 let mut st = self.title_state.borrow_mut();
695 st.bar_color = if self.drag_mode == DragMode::Move {
696 v.window_title_fill_drag
697 } else {
698 v.window_title_fill
699 };
700 st.title_color = v.window_title_text;
701 st.collapsed = self.collapsed;
702 st.maximized = self.maximized;
703 st.close_hovered = self.close_hovered;
704 st.maximize_hovered = self.maximize_hovered;
705 }
706 let tb_bounds = self.title_bar.bounds();
707 ctx.save();
708 ctx.translate(tb_bounds.x, tb_bounds.y);
709 paint_subtree(&mut self.title_bar, ctx);
710 ctx.restore();
711
712 ctx.set_fill_color(v.window_fill); ctx.set_stroke_color(v.window_stroke);
716 ctx.set_line_width(1.0);
717 ctx.begin_path();
718 ctx.rounded_rect(0.0, 0.0, w, h, CORNER_R);
719 ctx.stroke();
720
721 }
722
723 fn paint_overlay(&mut self, ctx: &mut dyn DrawCtx) {
725 if !self.is_visible() || self.collapsed { return; }
726 let v = ctx.visuals();
727 let w = self.bounds.width;
728 let h = self.bounds.height;
729
730 let is_se_active = matches!(self.drag_mode, DragMode::Resize(ResizeDir::SE));
733 let is_se_hover = self.hover_dir == Some(ResizeDir::SE);
734 let grip_color = if is_se_active {
735 v.window_resize_active
736 } else if is_se_hover {
737 v.window_resize_hover
738 } else {
739 v.window_stroke
740 };
741 ctx.set_stroke_color(grip_color);
742 ctx.set_line_width(1.5);
743 let m = 3.0_f64; for i in 1..=3_i32 {
745 let off = i as f64 * 4.0 + m;
746 ctx.begin_path();
747 ctx.move_to(w - off, m);
748 ctx.line_to(w - m, off);
749 ctx.stroke();
750 }
751
752 let (highlight, is_active) = match self.drag_mode {
755 DragMode::Resize(d) => (Some(d), true),
756 DragMode::Move => (None, false), DragMode::None => (self.hover_dir, false),
758 };
759 let dir = match highlight { Some(d) => d, None => return };
760
761 let color = if is_active { v.window_resize_active } else { v.window_resize_hover };
762 ctx.set_stroke_color(color);
763 ctx.set_line_width(2.0);
764
765 let (top, bottom, left, right) = match dir {
767 ResizeDir::N => (true, false, false, false),
768 ResizeDir::S => (false, true, false, false),
769 ResizeDir::E => (false, false, false, true),
770 ResizeDir::W => (false, false, true, false),
771 ResizeDir::NE => (true, false, false, true),
772 ResizeDir::NW => (true, false, true, false),
773 ResizeDir::SE => (false, true, false, true),
774 ResizeDir::SW => (false, true, true, false),
775 };
776
777 let cr = CORNER_R;
779 if top {
780 ctx.begin_path();
781 ctx.move_to(cr, h);
782 ctx.line_to(w - cr, h);
783 ctx.stroke();
784 }
785 if bottom {
786 ctx.begin_path();
787 ctx.move_to(cr, 0.0);
788 ctx.line_to(w - cr, 0.0);
789 ctx.stroke();
790 }
791 if left {
792 ctx.begin_path();
793 ctx.move_to(0.0, cr);
794 ctx.line_to(0.0, h - cr);
795 ctx.stroke();
796 }
797 if right {
798 ctx.begin_path();
799 ctx.move_to(w, cr);
800 ctx.line_to(w, h - cr);
801 ctx.stroke();
802 }
803 }
804
805 fn on_event(&mut self, event: &Event) -> EventResult {
806 if !self.is_visible() { return EventResult::Ignored; }
807
808 match event {
809 Event::MouseMove { pos } => {
810 let was_close = self.close_hovered;
811 let was_max = self.maximize_hovered;
812 let was_dir = self.hover_dir;
813 self.close_hovered = self.in_close_button(*pos);
814 self.maximize_hovered = self.in_maximize_button(*pos);
815
816 match self.drag_mode {
817 DragMode::Move => {
818 let world = Point::new(pos.x + self.bounds.x, pos.y + self.bounds.y);
819 let dx = world.x - self.drag_start_world.x;
820 let dy = world.y - self.drag_start_world.y;
821 self.bounds.x = (self.drag_start_bounds.x + dx).round();
822 self.bounds.y = (self.drag_start_bounds.y + dy).round();
823 self.clamp_to_canvas();
824 self.hover_dir = None;
825 set_cursor_icon(CursorIcon::Grabbing);
826 crate::animation::request_tick();
827 return EventResult::Consumed;
828 }
829 DragMode::Resize(dir) => {
830 let world = Point::new(pos.x + self.bounds.x, pos.y + self.bounds.y);
831 self.apply_resize(world);
832 set_cursor_icon(resize_cursor(dir));
833 crate::animation::request_tick();
834 return EventResult::Consumed;
835 }
836 DragMode::None => {
837 self.hover_dir = self.resize_dir(*pos);
840 if let Some(dir) = self.hover_dir {
841 set_cursor_icon(resize_cursor(dir));
842 }
843 }
844 }
845 if was_close != self.close_hovered
846 || was_max != self.maximize_hovered
847 || was_dir != self.hover_dir
848 {
849 crate::animation::request_tick();
850 }
851 EventResult::Ignored
852 }
853
854 Event::MouseDown { button: MouseButton::Left, pos, .. } => {
855 self.raise_request.set(true);
864 crate::animation::request_tick();
866 if let Some(cb) = self.on_raised.as_mut() { cb(&self.title); }
867
868 if self.in_close_button(*pos) {
870 self.visible = false;
871 if let Some(ref cell) = self.visible_cell { cell.set(false); }
872 if let Some(cb) = self.on_close.as_mut() { cb(); }
873 crate::animation::request_tick();
874 return EventResult::Consumed;
875 }
876
877 if self.in_maximize_button(*pos) {
879 self.toggle_maximize();
880 crate::animation::request_tick();
881 return EventResult::Consumed;
882 }
883
884 if self.in_chevron_button(*pos) {
886 self.toggle_collapse();
887 self.last_title_click = None;
891 crate::animation::request_tick();
892 return EventResult::Consumed;
893 }
894
895 if let Some(dir) = self.resize_dir(*pos) {
897 let world = Point::new(pos.x + self.bounds.x, pos.y + self.bounds.y);
900 self.drag_mode = DragMode::Resize(dir);
901 self.drag_start_world = world;
902 self.drag_start_bounds = self.bounds;
903 return EventResult::Consumed;
904 }
905
906 if self.in_title_bar(*pos) {
908 let now = Instant::now();
910 let is_double = self.last_title_click
911 .map(|t| now.duration_since(t).as_millis() < DBL_CLICK_MS)
912 .unwrap_or(false);
913
914 if is_double {
915 self.toggle_maximize();
919 self.last_title_click = None;
920 crate::animation::request_tick();
921 } else {
922 self.last_title_click = Some(now);
923 let world = Point::new(pos.x + self.bounds.x, pos.y + self.bounds.y);
924 self.drag_mode = DragMode::Move;
925 self.drag_start_world = world;
926 self.drag_start_bounds = self.bounds;
927 }
928 return EventResult::Consumed;
929 }
930
931 if !self.collapsed {
933 EventResult::Consumed
934 } else {
935 EventResult::Ignored
936 }
937 }
938
939 Event::MouseUp { button: MouseButton::Left, .. } => {
940 let was_dragging = self.drag_mode != DragMode::None;
941 self.drag_mode = DragMode::None;
942 if was_dragging {
943 crate::animation::request_tick();
944 EventResult::Consumed
945 } else {
946 EventResult::Ignored
947 }
948 }
949
950 _ => EventResult::Ignored,
951 }
952 }
953}