Skip to main content

sequoia_view/
dockable.rs

1use std::rc::Rc;
2
3use sequoia_core::{Color, Constraints, Length, Rect, Size};
4use sequoia_dock::{PanelId, PanelPayload};
5use sequoia_input::{CursorIcon, Event, PointerButton, PointerEvent};
6use sequoia_layout::{LayoutCtx, LayoutStyle};
7use sequoia_style::VisualState;
8
9use crate::any::AnyView;
10use crate::dnd::DragOptions;
11use crate::runtime::{ElementId, Runtime};
12use crate::view::{EventCtx, PaintCtx, View};
13use crate::views::{Text, Unit};
14
15const HEADER_H: f32 = 28.0;
16
17#[derive(Clone)]
18pub struct DockHeader {
19    panel: PanelId,
20    title: String,
21    height: f32,
22    background: Color,
23    on_drag_start: Option<Rc<dyn Fn()>>,
24}
25
26impl DockHeader {
27    pub fn new(panel: PanelId, title: impl Into<String>) -> Self {
28        Self {
29            panel,
30            title: title.into(),
31            height: HEADER_H,
32            background: Color::rgb8(0x2C, 0x2C, 0x30),
33            on_drag_start: None,
34        }
35    }
36
37    pub fn height(mut self, h: f32) -> Self {
38        self.height = if h.is_finite() && h > 0.0 {
39            h
40        } else {
41            HEADER_H
42        };
43        self
44    }
45
46    pub fn background(mut self, color: Color) -> Self {
47        self.background = color;
48        self
49    }
50
51    pub fn on_drag_start(mut self, f: impl Fn() + 'static) -> Self {
52        self.on_drag_start = Some(Rc::new(f));
53        self
54    }
55
56    pub fn panel(&self) -> PanelId {
57        self.panel
58    }
59}
60
61impl View for DockHeader {
62    fn body(&self) -> impl View + 'static {
63        Unit
64    }
65
66    fn children_override(&self) -> Option<Vec<AnyView>> {
67        Some(vec![AnyView::new(Text::new(self.title.clone()))])
68    }
69
70    fn layout_style(&self) -> LayoutStyle {
71        LayoutStyle {
72            width: Length::Fill(1.0),
73            height: Length::Px(self.height),
74            ..LayoutStyle::default()
75        }
76    }
77
78    fn has_visual_state(&self) -> bool {
79        true
80    }
81
82    fn captures_pointer(&self) -> bool {
83        true
84    }
85
86    fn cursor_icon(&self, state: VisualState) -> CursorIcon {
87        if state.pressed {
88            CursorIcon::Grabbing
89        } else {
90            CursorIcon::Grab
91        }
92    }
93
94    fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
95        match event {
96            Event::Pointer(PointerEvent::Down {
97                button: PointerButton::Left,
98                pos,
99                ..
100            }) if ctx.rect.contains(*pos) => {
101                ctx.begin_drag(PanelPayload(self.panel), DragOptions::new());
102                if let Some(f) = &self.on_drag_start {
103                    f();
104                }
105            }
106            _ => {}
107        }
108    }
109
110    fn paint_self(&self, ctx: &mut PaintCtx) {
111        ctx.paint_box(
112            crate::BoxStyle::new()
113                .background(self.background)
114                .radius(6.0),
115        );
116    }
117}
118
119#[derive(Clone)]
120pub struct Dockable {
121    panel: PanelId,
122    header: DockHeader,
123    content: AnyView,
124}
125
126impl Dockable {
127    pub fn new(panel: PanelId, title: impl Into<String>, content: impl View + 'static) -> Self {
128        let title = title.into();
129        Self {
130            panel,
131            header: DockHeader::new(panel, title),
132            content: AnyView::new(content),
133        }
134    }
135
136    pub fn header(mut self, header: DockHeader) -> Self {
137        debug_assert_eq!(header.panel(), self.panel, "other panel header");
138        self.header = header;
139        self
140    }
141
142    pub fn header_height(mut self, h: f32) -> Self {
143        self.header = self.header.height(h);
144        self
145    }
146
147    pub fn panel(&self) -> PanelId {
148        self.panel
149    }
150}
151
152impl View for Dockable {
153    fn body(&self) -> impl View + 'static {
154        Unit
155    }
156
157    fn children_override(&self) -> Option<Vec<AnyView>> {
158        Some(vec![
159            AnyView::new(self.header.clone()),
160            self.content.clone(),
161        ])
162    }
163
164    fn layout_style(&self) -> LayoutStyle {
165        LayoutStyle {
166            width: Length::Fill(1.0),
167            height: Length::Fill(1.0),
168            ..LayoutStyle::default()
169        }
170    }
171
172    fn arrange_override(
173        &self,
174        rt: &Runtime,
175        el: ElementId,
176        rect: Rect,
177        ctx: &mut LayoutCtx,
178    ) -> bool {
179        let kids = rt.children(el);
180        if kids.len() < 2 {
181            return false;
182        }
183        let hh = self.header.height.min(rect.size.height.max(0.0));
184        let head = Rect::from_xywh(rect.left(), rect.top(), rect.size.width, hh);
185        let body = Rect::from_xywh(
186            rect.left(),
187            rect.top() + hh,
188            rect.size.width,
189            (rect.size.height - hh).max(0.0),
190        );
191        let _ = rt.measure_node(
192            kids[0],
193            Constraints {
194                min: Size::ZERO,
195                max: Size::new(head.size.width.max(0.0), hh),
196            },
197            ctx,
198        );
199        rt.arrange_node(kids[0], head, ctx);
200        let _ = rt.measure_node(
201            kids[1],
202            Constraints {
203                min: Size::ZERO,
204                max: Size::new(body.size.width.max(0.0), body.size.height.max(0.0)),
205            },
206            ctx,
207        );
208        rt.arrange_node(kids[1], body, ctx);
209        true
210    }
211}
212
213const PANEL_CLOSE_W: f32 = 26.0;
214
215#[derive(Clone)]
216pub struct DockPanel {
217    panel: PanelId,
218    header: DockHeader,
219    content: AnyView,
220    background: Color,
221    radius: f32,
222    show_close: bool,
223    on_close: Option<Rc<dyn Fn()>>,
224}
225
226impl DockPanel {
227    pub fn new(panel: PanelId, title: impl Into<String>, content: impl View + 'static) -> Self {
228        Self {
229            panel,
230            header: DockHeader::new(panel, title),
231            content: AnyView::new(content),
232            background: Color::rgb8(0x1E, 0x1E, 0x22),
233            radius: 6.0,
234            show_close: false,
235            on_close: None,
236        }
237    }
238
239    pub fn header(mut self, header: DockHeader) -> Self {
240        debug_assert_eq!(header.panel(), self.panel, "other panel header");
241        self.header = header;
242        self
243    }
244
245    pub fn header_height(mut self, h: f32) -> Self {
246        self.header = self.header.height(h);
247        self
248    }
249
250    pub fn header_background(mut self, color: Color) -> Self {
251        self.header = self.header.background(color);
252        self
253    }
254
255    pub fn background(mut self, color: Color) -> Self {
256        self.background = color;
257        self
258    }
259
260    pub fn radius(mut self, radius: f32) -> Self {
261        self.radius = radius;
262        self
263    }
264
265    pub fn closable(mut self, closable: bool) -> Self {
266        self.show_close = closable;
267        self
268    }
269
270    pub fn on_close(mut self, f: impl Fn() + 'static) -> Self {
271        self.on_close = Some(Rc::new(f));
272        self.show_close = true;
273        self
274    }
275
276    pub fn panel(&self) -> PanelId {
277        self.panel
278    }
279}
280
281impl View for DockPanel {
282    fn body(&self) -> impl View + 'static {
283        Unit
284    }
285
286    fn children_override(&self) -> Option<Vec<AnyView>> {
287        if self.show_close {
288            Some(vec![
289                AnyView::new(self.header.clone()),
290                AnyView::new(DockPanelClose {
291                    on_close: self.on_close.clone(),
292                }),
293                self.content.clone(),
294            ])
295        } else {
296            Some(vec![
297                AnyView::new(self.header.clone()),
298                self.content.clone(),
299            ])
300        }
301    }
302
303    fn layout_style(&self) -> LayoutStyle {
304        LayoutStyle {
305            width: Length::Fill(1.0),
306            height: Length::Fill(1.0),
307            ..LayoutStyle::default()
308        }
309    }
310
311    fn arrange_override(
312        &self,
313        rt: &Runtime,
314        el: ElementId,
315        rect: Rect,
316        ctx: &mut LayoutCtx,
317    ) -> bool {
318        let kids = rt.children(el);
319        let hh = self.header.height.min(rect.size.height.max(0.0));
320        let head = Rect::from_xywh(rect.left(), rect.top(), rect.size.width, hh);
321        let body = Rect::from_xywh(
322            rect.left(),
323            rect.top() + hh,
324            rect.size.width,
325            (rect.size.height - hh).max(0.0),
326        );
327        if self.show_close {
328            if kids.len() < 3 {
329                return false;
330            }
331            let close_w = PANEL_CLOSE_W.min(head.size.width.max(0.0));
332            let title_r = Rect::from_xywh(
333                head.left(),
334                head.top(),
335                (head.size.width - close_w).max(0.0),
336                head.size.height,
337            );
338            let close_r =
339                Rect::from_xywh(head.left() + title_r.size.width, head.top(), close_w, hh);
340            let slots = [(kids[0], title_r), (kids[1], close_r), (kids[2], body)];
341            for (child, slot) in slots {
342                let _ = rt.measure_node(
343                    child,
344                    Constraints {
345                        min: Size::ZERO,
346                        max: Size::new(slot.size.width.max(0.0), slot.size.height.max(0.0)),
347                    },
348                    ctx,
349                );
350                rt.arrange_node(child, slot, ctx);
351            }
352            true
353        } else {
354            if kids.len() < 2 {
355                return false;
356            }
357            let _ = rt.measure_node(
358                kids[0],
359                Constraints {
360                    min: Size::ZERO,
361                    max: Size::new(head.size.width.max(0.0), hh),
362                },
363                ctx,
364            );
365            rt.arrange_node(kids[0], head, ctx);
366            let _ = rt.measure_node(
367                kids[1],
368                Constraints {
369                    min: Size::ZERO,
370                    max: Size::new(body.size.width.max(0.0), body.size.height.max(0.0)),
371                },
372                ctx,
373            );
374            rt.arrange_node(kids[1], body, ctx);
375            true
376        }
377    }
378
379    fn paint_self(&self, ctx: &mut PaintCtx) {
380        ctx.paint_box(
381            crate::BoxStyle::new()
382                .background(self.background)
383                .radius(self.radius),
384        );
385    }
386}
387
388#[derive(Clone)]
389struct DockPanelClose {
390    on_close: Option<Rc<dyn Fn()>>,
391}
392
393impl View for DockPanelClose {
394    fn body(&self) -> impl View + 'static {
395        Unit
396    }
397
398    fn children_override(&self) -> Option<Vec<AnyView>> {
399        Some(vec![AnyView::new(Text::new("×"))])
400    }
401
402    fn layout_style(&self) -> LayoutStyle {
403        LayoutStyle {
404            width: Length::Px(PANEL_CLOSE_W),
405            height: Length::Fill(1.0),
406            ..LayoutStyle::default()
407        }
408    }
409
410    fn has_visual_state(&self) -> bool {
411        true
412    }
413
414    fn captures_pointer(&self) -> bool {
415        true
416    }
417
418    fn cursor_icon(&self, _state: VisualState) -> CursorIcon {
419        CursorIcon::Pointer
420    }
421
422    fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
423        match event {
424            Event::Pointer(PointerEvent::Up {
425                button: PointerButton::Left,
426                pos,
427                ..
428            }) if ctx.rect.contains(*pos) => {
429                if let Some(f) = &self.on_close {
430                    f();
431                }
432                ctx.handled();
433            }
434            _ => {}
435        }
436    }
437}