Skip to main content

sequoia_view/
dock_float.rs

1use std::cell::{Cell, RefCell};
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use sequoia_core::{Color, Constraints, Length, Point, Rect, Size};
6use sequoia_dock::{DockModel, DockOp, PanelId};
7use sequoia_input::{CursorIcon, Event, PointerButton, PointerEvent};
8use sequoia_layout::{LayoutCtx, LayoutStyle};
9use sequoia_reactive::Signal;
10use sequoia_style::VisualState;
11
12use crate::any::AnyView;
13use crate::dock_view::{PanelRenderer, TitleProvider, default_title};
14use crate::runtime::{ElementId, Runtime};
15use crate::view::{EventCtx, PaintCtx, View};
16use crate::views::{Text, Unit};
17
18const HEADER_H: f32 = 26.0;
19const BTN_W: f32 = 26.0;
20
21#[derive(Clone)]
22pub struct DockFloatLayer {
23    model: Signal<DockModel>,
24    render: PanelRenderer,
25    title: TitleProvider,
26    card_bg: Color,
27    header_bg: Color,
28    layout_width: Length,
29    layout_height: Length,
30
31    drags: Rc<RefCell<HashMap<u64, Rc<Cell<Option<CardDrag>>>>>>,
32}
33
34impl DockFloatLayer {
35    pub fn new(model: Signal<DockModel>, render: impl Fn(PanelId) -> AnyView + 'static) -> Self {
36        Self {
37            model,
38            render: Rc::new(render),
39            title: Rc::new(default_title),
40            card_bg: Color::rgb8(0x1E, 0x1E, 0x22),
41            header_bg: Color::rgb8(0x2C, 0x2C, 0x30),
42            layout_width: Length::Fill(1.0),
43            layout_height: Length::Fill(1.0),
44            drags: Rc::new(RefCell::new(HashMap::new())),
45        }
46    }
47
48    fn drag_for(&self, panel: PanelId) -> Rc<Cell<Option<CardDrag>>> {
49        self.drags
50            .borrow_mut()
51            .entry(panel.get())
52            .or_insert_with(|| Rc::new(Cell::new(None)))
53            .clone()
54    }
55
56    pub fn title_provider(mut self, f: impl Fn(PanelId) -> String + 'static) -> Self {
57        self.title = Rc::new(f);
58        self
59    }
60
61    pub fn card_background(mut self, color: Color) -> Self {
62        self.card_bg = color;
63        self
64    }
65
66    pub fn header_background(mut self, color: Color) -> Self {
67        self.header_bg = color;
68        self
69    }
70
71    pub fn width(mut self, width: impl Into<Length>) -> Self {
72        self.layout_width = width.into();
73        self
74    }
75
76    pub fn height(mut self, height: impl Into<Length>) -> Self {
77        self.layout_height = height.into();
78        self
79    }
80
81    pub fn fill_size(self) -> Self {
82        self.width(Length::Fill(1.0)).height(Length::Fill(1.0))
83    }
84}
85
86impl View for DockFloatLayer {
87    fn body(&self) -> impl View + 'static {
88        Unit
89    }
90
91    fn children_override(&self) -> Option<Vec<AnyView>> {
92        let m = self.model.get();
93
94        {
95            let live: Vec<u64> = m
96                .floating
97                .iter()
98                .filter(|f| f.window.is_none())
99                .map(|f| f.panel.get())
100                .collect();
101            self.drags.borrow_mut().retain(|id, _| live.contains(id));
102        }
103        let cards: Vec<AnyView> = m
104            .floating
105            .iter()
106            .filter(|f| f.window.is_none())
107            .map(|f| {
108                AnyView::new(
109                    DockFloatCard {
110                        model: self.model.clone(),
111                        panel: f.panel,
112                        title: (self.title)(f.panel),
113                        content: (self.render)(f.panel),
114                        x: f.x,
115                        y: f.y,
116                        w: f.w,
117                        h: f.h,
118                        card_bg: self.card_bg,
119                        header_bg: self.header_bg,
120                        drag: self.drag_for(f.panel),
121                    }
122                    .absolute_at(f.x, f.y),
123                )
124            })
125            .collect();
126        Some(cards)
127    }
128
129    fn layout_style(&self) -> LayoutStyle {
130        LayoutStyle {
131            width: self.layout_width,
132            height: self.layout_height,
133            ..LayoutStyle::default()
134        }
135    }
136
137    fn hit_testable(&self) -> bool {
138        false
139    }
140}
141
142#[derive(Clone, Copy)]
143struct CardDrag {
144    origin: Point,
145    card_x: f32,
146    card_y: f32,
147}
148
149#[derive(Clone)]
150pub(crate) struct DockFloatCard {
151    model: Signal<DockModel>,
152    panel: PanelId,
153    title: String,
154    content: AnyView,
155    x: f32,
156    y: f32,
157    w: f32,
158    h: f32,
159    card_bg: Color,
160    header_bg: Color,
161    drag: Rc<Cell<Option<CardDrag>>>,
162}
163
164impl DockFloatCard {
165    fn absolute_at(mut self, x: f32, y: f32) -> Self {
166        self.x = x;
167        self.y = y;
168        self
169    }
170
171    fn header_rect(&self, card: Rect) -> Rect {
172        Rect::from_xywh(
173            card.left(),
174            card.top(),
175            card.size.width,
176            HEADER_H.min(card.size.height),
177        )
178    }
179
180    fn body_rect(&self, card: Rect) -> Rect {
181        let hh = HEADER_H.min(card.size.height);
182        Rect::from_xywh(
183            card.left(),
184            card.top() + hh,
185            card.size.width,
186            (card.size.height - hh).max(0.0),
187        )
188    }
189}
190
191impl View for DockFloatCard {
192    fn body(&self) -> impl View + 'static {
193        Unit
194    }
195
196    fn children_override(&self) -> Option<Vec<AnyView>> {
197        let _ = self.model.get();
198        let panel = self.panel;
199        let model = self.model.clone();
200        let model2 = self.model.clone();
201        Some(vec![
202            AnyView::new(Text::new(self.title.clone())),
203            AnyView::new(DockFloatButton {
204                model,
205                panel,
206                kind: FloatButtonKind::DockBack,
207            }),
208            AnyView::new(DockFloatButton {
209                model: model2,
210                panel,
211                kind: FloatButtonKind::Close,
212            }),
213            self.content.clone(),
214        ])
215    }
216
217    fn layout_style(&self) -> LayoutStyle {
218        LayoutStyle {
219            width: Length::Px(self.w.max(1.0)),
220            height: Length::Px(self.h.max(1.0)),
221            ..LayoutStyle::default()
222        }
223        .position(sequoia_layout::Position::Absolute(
224            sequoia_layout::Inset::xy(self.x, self.y),
225        ))
226    }
227
228    fn captures_pointer(&self) -> bool {
229        true
230    }
231
232    fn has_visual_state(&self) -> bool {
233        true
234    }
235
236    fn arrange_override(
237        &self,
238        rt: &Runtime,
239        el: ElementId,
240        rect: Rect,
241        ctx: &mut LayoutCtx,
242    ) -> bool {
243        let kids = rt.children(el);
244        if kids.len() < 4 {
245            return false;
246        }
247        let head = self.header_rect(rect);
248        let body = self.body_rect(rect);
249
250        let title_w = (head.size.width - 2.0 * BTN_W).max(0.0);
251        let title_r = Rect::from_xywh(head.left(), head.top(), title_w, head.size.height);
252        let dock_r = Rect::from_xywh(head.left() + title_w, head.top(), BTN_W, head.size.height);
253        let close_r = Rect::from_xywh(
254            head.left() + title_w + BTN_W,
255            head.top(),
256            BTN_W,
257            head.size.height,
258        );
259        let slots = [
260            (kids[0], title_r),
261            (kids[1], dock_r),
262            (kids[2], close_r),
263            (kids[3], body),
264        ];
265        for (child, slot) in slots {
266            let _ = rt.measure_node(
267                child,
268                Constraints {
269                    min: Size::ZERO,
270                    max: Size::new(slot.size.width.max(0.0), slot.size.height.max(0.0)),
271                },
272                ctx,
273            );
274            rt.arrange_node(child, slot, ctx);
275        }
276        true
277    }
278
279    fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
280        match event {
281            Event::Pointer(PointerEvent::Down {
282                button: PointerButton::Left,
283                pos,
284                ..
285            }) => {
286                let head = self.header_rect(ctx.rect);
287                let title_w = (head.size.width - 2.0 * BTN_W).max(0.0);
288                let grab = Rect::from_xywh(head.left(), head.top(), title_w, head.size.height);
289                if grab.contains(*pos) {
290                    let cur = self.model.get_untracked().floating_of(self.panel);
291                    let (cx, cy) = cur.map(|f| (f.x, f.y)).unwrap_or((self.x, self.y));
292                    self.drag.set(Some(CardDrag {
293                        origin: *pos,
294                        card_x: cx,
295                        card_y: cy,
296                    }));
297                    ctx.capture_pointer();
298                    ctx.handled();
299                }
300            }
301            Event::Pointer(PointerEvent::Move { pos, .. }) => {
302                if let Some(st) = self.drag.get() {
303                    let panel = self.panel;
304                    let nx = st.card_x + (pos.x - st.origin.x);
305                    let ny = st.card_y + (pos.y - st.origin.y);
306                    self.model.update(|m| {
307                        if let Some(f) = m.floating.iter_mut().find(|f| f.panel == panel) {
308                            if nx.is_finite() {
309                                f.x = nx;
310                            }
311                            if ny.is_finite() {
312                                f.y = ny;
313                            }
314                        }
315                    });
316                    ctx.handled();
317                }
318            }
319            Event::Pointer(PointerEvent::Up {
320                button: PointerButton::Left,
321                ..
322            }) => {
323                self.drag.set(None);
324            }
325            Event::Pointer(PointerEvent::Cancel) => {
326                self.drag.set(None);
327            }
328            _ => {}
329        }
330    }
331
332    fn paint_self(&self, ctx: &mut PaintCtx) {
333        ctx.paint_box(crate::BoxStyle::new().background(self.card_bg).radius(8.0));
334        let head = self.header_rect(ctx.rect);
335        ctx.paint_box_in(
336            crate::BoxStyle::new()
337                .background(self.header_bg)
338                .radius(8.0),
339            head,
340        );
341    }
342}
343
344#[derive(Clone, Copy, PartialEq, Eq)]
345enum FloatButtonKind {
346    DockBack,
347    Close,
348}
349
350#[derive(Clone)]
351struct DockFloatButton {
352    model: Signal<DockModel>,
353    panel: PanelId,
354    kind: FloatButtonKind,
355}
356
357impl View for DockFloatButton {
358    fn body(&self) -> impl View + 'static {
359        Unit
360    }
361
362    fn children_override(&self) -> Option<Vec<AnyView>> {
363        let label = match self.kind {
364            FloatButtonKind::DockBack => "⧉",
365            FloatButtonKind::Close => "×",
366        };
367        Some(vec![AnyView::new(Text::new(label))])
368    }
369
370    fn layout_style(&self) -> LayoutStyle {
371        LayoutStyle {
372            width: Length::Px(BTN_W),
373            height: Length::Px(HEADER_H),
374            ..LayoutStyle::default()
375        }
376    }
377
378    fn has_visual_state(&self) -> bool {
379        true
380    }
381
382    fn captures_pointer(&self) -> bool {
383        true
384    }
385
386    fn cursor_icon(&self, _state: VisualState) -> CursorIcon {
387        CursorIcon::Pointer
388    }
389
390    fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
391        match event {
392            Event::Pointer(PointerEvent::Up {
393                button: PointerButton::Left,
394                pos,
395                ..
396            }) if ctx.rect.contains(*pos) => {
397                let panel = self.panel;
398                match self.kind {
399                    FloatButtonKind::DockBack => {
400                        self.model.update(|m| {
401                            m.dock_first_tab(panel);
402                        });
403                    }
404                    FloatButtonKind::Close => {
405                        self.model.update(|m| {
406                            m.apply(DockOp::Close { panel });
407                        });
408                    }
409                }
410                ctx.handled();
411            }
412            _ => {}
413        }
414    }
415}