Skip to main content

sequoia_view/
dock_window.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use sequoia_core::Length;
6use sequoia_dock::{DockModel, PanelId};
7use sequoia_layout::LayoutStyle;
8use sequoia_reactive::Signal;
9
10use crate::any::AnyView;
11use crate::dock_view::{PanelRenderer, TitleProvider, default_title};
12use crate::view::{PaintCtx, View};
13use crate::views::{Text, Unit};
14
15#[derive(Clone, Debug)]
16pub struct DetachedWindow {
17    pub panel: PanelId,
18
19    pub title: String,
20
21    pub w: f64,
22    pub h: f64,
23}
24
25#[derive(Clone)]
26pub struct DockWindowHost {
27    model: Signal<DockModel>,
28    title: TitleProvider,
29    on_open: Rc<dyn Fn(DetachedWindow)>,
30    on_close: Rc<dyn Fn(u64)>,
31    known: Rc<RefCell<HashMap<u64, u64>>>,
32}
33
34impl DockWindowHost {
35    pub fn new(model: Signal<DockModel>, _render: impl Fn(PanelId) -> AnyView + 'static) -> Self {
36        Self {
37            model,
38            title: Rc::new(default_title),
39            on_open: Rc::new(|_| {}),
40            on_close: Rc::new(|_| {}),
41            known: Rc::new(RefCell::new(HashMap::new())),
42        }
43    }
44
45    pub fn title_provider(mut self, f: impl Fn(PanelId) -> String + 'static) -> Self {
46        self.title = Rc::new(f);
47        self
48    }
49
50    pub fn on_open(mut self, f: impl Fn(DetachedWindow) + 'static) -> Self {
51        self.on_open = Rc::new(f);
52        self
53    }
54
55    pub fn on_close(mut self, f: impl Fn(u64) + 'static) -> Self {
56        self.on_close = Rc::new(f);
57        self
58    }
59
60    pub fn tracked(&self) -> Vec<(PanelId, u64)> {
61        self.known
62            .borrow()
63            .iter()
64            .map(|(p, w)| (PanelId::new(*p), *w))
65            .collect()
66    }
67
68    pub fn detach(
69        model: &Signal<DockModel>,
70        panel: PanelId,
71        w: f32,
72        h: f32,
73        alloc: impl FnOnce(DetachedWindow) -> u64,
74    ) -> bool {
75        let floated = {
76            let mut ok = false;
77            model.update(|m| {
78                ok = m.apply(sequoia_dock::DockOp::Float {
79                    panel,
80                    x: 80.0,
81                    y: 80.0,
82                    w,
83                    h,
84                });
85            });
86            ok
87        };
88        if !floated && !model.get_untracked().is_floating(panel) {
89            return false;
90        }
91        let current = model.get_untracked().floating_of(panel);
92        let (w, h) = current.map(|f| (f.w, f.h)).unwrap_or((w, h));
93        let win = alloc(DetachedWindow {
94            panel,
95            title: format!("{panel}"),
96            w: w.max(1.0) as f64,
97            h: h.max(1.0) as f64,
98        });
99        let mut attached = false;
100        model.update(|m| {
101            attached = m.attach_window(panel, Some(win));
102        });
103        if !attached {
104            return false;
105        }
106        true
107    }
108
109    fn sync(&self) {
110        let m = self.model.get_untracked();
111        let mut current: HashMap<u64, (u64, String, f32, f32)> = HashMap::new();
112        for f in &m.floating {
113            if let Some(win) = f.window {
114                current.insert(f.panel.get(), (win, (self.title)(f.panel), f.w, f.h));
115            }
116        }
117        let mut known = self.known.borrow_mut();
118
119        let mut opens: Vec<DetachedWindow> = Vec::new();
120        for (panel, (win, title, w, h)) in &current {
121            match known.get(panel) {
122                Some(known_win) if *known_win == *win => {}
123                Some(old) => {
124                    let old = *old;
125                    (self.on_close)(old);
126                    opens.push(DetachedWindow {
127                        panel: PanelId::new(*panel),
128                        title: title.clone(),
129                        w: (*w).max(1.0) as f64,
130                        h: (*h).max(1.0) as f64,
131                    });
132                    known.insert(*panel, *win);
133                }
134                None => {
135                    opens.push(DetachedWindow {
136                        panel: PanelId::new(*panel),
137                        title: title.clone(),
138                        w: (*w).max(1.0) as f64,
139                        h: (*h).max(1.0) as f64,
140                    });
141                    known.insert(*panel, *win);
142                }
143            }
144        }
145
146        let stale: Vec<u64> = known
147            .keys()
148            .filter(|p| !current.contains_key(*p))
149            .copied()
150            .collect();
151        let mut closes: Vec<u64> = Vec::new();
152        for panel in stale {
153            if let Some(win) = known.remove(&panel) {
154                closes.push(win);
155            }
156        }
157        drop(known);
158        for d in opens {
159            (self.on_open)(d);
160        }
161        for w in closes {
162            (self.on_close)(w);
163        }
164    }
165}
166
167impl View for DockWindowHost {
168    fn body(&self) -> impl View + 'static {
169        Unit
170    }
171
172    fn keyed_children(&self) -> Option<Vec<(u64, AnyView)>> {
173        let _ = self.model.get();
174        Some(Vec::new())
175    }
176
177    fn layout_style(&self) -> LayoutStyle {
178        LayoutStyle {
179            width: Length::Fit,
180            height: Length::Fit,
181            ..LayoutStyle::default()
182        }
183    }
184
185    fn is_leaf(&self) -> bool {
186        true
187    }
188
189    fn hit_testable(&self) -> bool {
190        false
191    }
192
193    fn on_unmount(&self) {
194        let wins: Vec<u64> = self.known.borrow().values().copied().collect();
195        self.known.borrow_mut().clear();
196        for w in wins {
197            (self.on_close)(w);
198        }
199    }
200
201    fn paint_self(&self, _ctx: &mut PaintCtx) {
202        self.sync();
203    }
204}
205
206#[derive(Clone)]
207pub struct DockWindowRoot {
208    model: Signal<DockModel>,
209    panel: PanelId,
210    render: PanelRenderer,
211}
212
213impl DockWindowRoot {
214    pub fn new(
215        model: Signal<DockModel>,
216        panel: PanelId,
217        render: impl Fn(PanelId) -> AnyView + 'static,
218    ) -> Self {
219        Self {
220            model,
221            panel,
222            render: Rc::new(render),
223        }
224    }
225
226    pub fn panel(&self) -> PanelId {
227        self.panel
228    }
229}
230
231impl View for DockWindowRoot {
232    fn body(&self) -> impl View + 'static {
233        Unit
234    }
235
236    fn children_override(&self) -> Option<Vec<AnyView>> {
237        let m = self.model.get();
238        if m.contains(self.panel) {
239            Some(vec![(self.render)(self.panel)])
240        } else {
241            Some(vec![AnyView::new(Text::new(
242                "Panel zadokowany gdzie indziej",
243            ))])
244        }
245    }
246
247    fn layout_style(&self) -> LayoutStyle {
248        LayoutStyle {
249            width: Length::Fill(1.0),
250            height: Length::Fill(1.0),
251            ..LayoutStyle::default()
252        }
253    }
254}