sequoia_view/
dock_window.rs1use 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 ¤t {
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}