1use arrayvec::ArrayVec;
2use as_any::Downcast;
3use vector2d::Vector2D;
4
5use crate::{canvas::*, common::*, window::*};
6use std::rc::Rc;
7
8use super::*;
9
10pub type FocusChangedCallback = dyn Fn(&mut DockPanel);
11pub type DockPanelKeyHandler = fn(&mut DockPanel, event: &mut WindowEvent);
12
13#[derive(Debug, Clone, Copy, PartialEq)]
14#[repr(usize)]
15pub enum DockAlignment {
16 Top,
17 Bottom,
18 Left,
19 Right,
20 Center,
21}
22
23impl DockAlignment {
24 pub const VARIANTS: &'static [Self] = &[
25 DockAlignment::Top,
26 DockAlignment::Bottom,
27 DockAlignment::Left,
28 DockAlignment::Right,
29 DockAlignment::Center,
30 ];
31}
32
33pub struct DockPanel {
48 base: WidgetBase,
49 inner_border: bool,
50 children: [Option<WindowRef>; 5],
51 inner_borders: ArrayVec<Line2D<TSize>, 4>,
52 focus: DockAlignment,
53 key_handler: DockPanelKeyHandler,
54 callback: Rc<FocusChangedCallback>,
55}
56
57impl Default for DockPanel {
58 fn default() -> Self {
59 Self::new(false)
60 }
61}
62
63impl DockPanel {
64 pub const MAX_CHILD_SIZE: Size = Size::Relative(Percent::from_int(25));
66
67 pub fn new(inner_border: bool) -> Self {
68 Self {
69 base: WidgetBase::default(),
70 inner_border,
71 children: [const { None }; 5],
72 inner_borders: ArrayVec::new(),
73 focus: DockAlignment::Center,
74 key_handler: Self::default_key_handler,
75 callback: Rc::new(Self::default_callback),
76 }
77 }
78
79 pub fn default_callback(_: &mut Self) {}
80
81 pub fn default_key_handler(&mut self, _: &mut WindowEvent) {}
82
83 pub fn set_selection_changed_callback<F: Fn(&mut Self) + 'static>(&mut self, callback: F) {
84 self.callback = Rc::new(callback);
85 }
86
87 pub fn set_key_handler(&mut self, f: DockPanelKeyHandler) {
88 self.key_handler = f;
89 }
90
91 pub fn set_inner_border(&mut self, inner_border: bool) {
92 self.inner_border = inner_border;
93 self.provoke_changed_property(WindowProperty::Children);
94 }
95
96 pub fn set_child(&mut self, dock: DockAlignment, child: Option<WindowRef>) {
97 if self.focus == dock && child.is_none() {
98 self.focus = DockAlignment::Center;
99 }
100 self.children[dock as usize] = child;
101 self.provoke_changed_property(WindowProperty::Children);
102 }
103
104 pub fn set_focus(&mut self, dock: DockAlignment) {
105 if self.focus != dock && self.children[dock as usize].is_some() {
106 self.focus = dock;
107 self.provoke_changed_property(WindowProperty::Focus);
108 let cb = self.callback.clone();
109 cb(self);
110 }
111 }
112
113 fn draw_border(window: &dyn Window, mut canvas: BorderCanvas) {
114 let slf: &Self = window.downcast_ref().unwrap();
115 if let BorderStyle::Preset { kind, bg, fg } = slf.base.border {
116 canvas.draw_preset_border(kind, fg, bg, "").unwrap();
117 let cts = kind.connector_style();
118 let size = canvas.size();
119 for l in slf.inner_borders.iter().filter(|x| x.is_horizontal()) {
120 canvas
121 .get_glyph(0, l.a.y + 1)
122 .unwrap()
123 .set_grapheme(cts[0])
124 .unwrap();
125 canvas
126 .get_glyph(size.x - 1, l.a.y + 1)
127 .unwrap()
128 .set_grapheme(cts[1])
129 .unwrap();
130 }
131 }
132 }
133}
134
135impl Window for DockPanel {
136 fn render(&self, canvas: &mut crate::canvas::Canvas) {
137 if self.inner_border {
139 let kind = self.base.border.get_borderkind().unwrap_or_default();
140 let cts = kind.connector_style();
141 let lns = kind.line_style();
142 for line in self.inner_borders.iter() {
143 if line.is_vertical() {
144 if let Some(mut column) = canvas.get_column(line.a.x, GlyphWidth::Half) {
145 for pos in line.iter_points() {
146 column.get(pos.y).unwrap().set_grapheme(lns[1]).unwrap();
147 }
148 }
149 }
150 else if let Some(mut row) = canvas.get_row(line.a.y, GlyphWidth::Half) {
151 for pos in line.iter_points() {
152 row.get(pos.x).unwrap().set_grapheme(lns[0]).unwrap();
153 }
154 }
155 }
156
157 for vert in self.inner_borders.iter().filter(|x| x.is_vertical()) {
158 for hori in self.inner_borders.iter().filter(|x| x.is_horizontal()) {
159 if hori.a.y + 1 == vert.a.y {
160 if let Some(mut gl) =
161 canvas.get_glyph(vert.a.x, vert.a.y - 1, GlyphWidth::Half)
162 {
163 gl.set_grapheme(cts[2]).unwrap();
164 }
165 }
166 else if hori.a.y == vert.b.y {
167 if let Some(mut gl) = canvas.get_glyph(vert.b.x, vert.b.y, GlyphWidth::Half)
168 {
169 gl.set_grapheme(cts[3]).unwrap();
170 }
171 }
172 }
173 }
174 }
175 }
176
177 fn handle_event(&mut self, event: &mut WindowEvent) {
178 (self.key_handler)(self, event)
179 }
180
181 fn children(&mut self, mut builder: SubWindowBuilder) -> SubWindows {
182 builder.disable_overlapping();
183 let mut base_rect = builder.base_rect();
184
185 let max_x = Self::MAX_CHILD_SIZE.get_size(base_rect.size.x);
186 let max_y = Self::MAX_CHILD_SIZE.get_size(base_rect.size.y);
187
188 let draw_border = self.inner_border && base_rect.size.x > 4 && base_rect.size.y > 4;
189
190 self.inner_borders.clear();
191 for dock in DockAlignment::VARIANTS.iter() {
192 if let Some(child) = self.children[*dock as usize].clone() {
193 let rect;
194 let size = child.borrow().desired_size(Vector2D::new(max_x, max_y));
195 let border = draw_border as TSize;
197 if size.x * size.y == 0 {
198 continue;
199 }
200 match dock {
201 DockAlignment::Top => {
202 rect = base_rect.subrect(TSize::MIN, TSize::MIN, base_rect.size.x, size.y);
203 if draw_border {
204 self.inner_borders.push(Line2D::new(
205 TSize::MIN,
206 size.y,
207 base_rect.end().x,
208 size.y,
209 ));
210 }
211 base_rect = base_rect
212 .subrect(
213 TSize::MIN,
214 size.y + border,
215 base_rect.size.x,
216 base_rect.size.y - (size.y + border),
217 )
218 .unwrap();
219 }
220 DockAlignment::Bottom => {
221 rect = base_rect.subrect(
222 TSize::MIN,
223 base_rect.size.y - size.y,
224 base_rect.size.x,
225 size.y,
226 );
227 if draw_border {
228 self.inner_borders.push(Line2D::new(
229 TSize::MIN,
230 base_rect.end().y - (size.y + border),
231 base_rect.end().x,
232 base_rect.end().y - (size.y + border),
233 ));
234 }
235
236 base_rect = base_rect
237 .subrect(
238 TSize::MIN,
239 TSize::MIN,
240 base_rect.size.x,
241 base_rect.size.y - (size.y + border),
242 )
243 .unwrap();
244 }
245 DockAlignment::Left => {
246 rect = base_rect.subrect(TSize::MIN, TSize::MIN, size.x, base_rect.size.y);
247 if draw_border {
248 self.inner_borders.push(Line2D::new(
249 size.x,
250 base_rect.start.y,
251 size.x,
252 base_rect.end().y,
253 ));
254 }
255 base_rect = base_rect
256 .subrect(
257 size.x + border,
258 TSize::MIN,
259 base_rect.size.x - (size.x + border),
260 base_rect.size.y,
261 )
262 .unwrap();
263 }
264 DockAlignment::Right => {
265 rect = base_rect.subrect(
266 base_rect.size.x - size.x,
267 TSize::MIN,
268 size.x,
269 base_rect.size.y,
270 );
271 if draw_border {
272 self.inner_borders.push(Line2D::new(
273 base_rect.end().x - (size.x + border),
274 base_rect.start.y,
275 base_rect.end().x - (size.x + border),
276 base_rect.end().y,
277 ));
278 }
279 base_rect = base_rect
280 .subrect(
281 TSize::MIN,
282 TSize::MIN,
283 base_rect.size.x - (size.x + border),
284 base_rect.size.y,
285 )
286 .unwrap();
287 }
288 DockAlignment::Center => {
289 rect = Ok(base_rect);
290 }
291 }
292 builder.add_rect(rect.unwrap()).unwrap();
293 builder.add_child(child);
294 }
295 }
296
297 builder.build().unwrap()
298 }
299
300 fn focus(&self) -> Option<WindowRef> {
301 self.children[self.focus as usize].clone()
302 }
303}
304
305impl HasWindowUID for DockPanel {
306 fn uid(&self) -> WindowUID {
307 self.base.uid
308 }
309}
310
311impl WindowLayout for DockPanel {
312 fn desired_size(&self, available_size: TPoint) -> TPoint {
313 self.base.desired_size(available_size)
314 }
315
316 fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
317 self.base.alignment
318 }
319
320 fn margin(&self) -> Thickness {
321 self.base.margin
322 }
323
324 fn border(&self) -> BorderStyle {
325 BorderStyle::custom(Self::draw_border, Thickness::from(1))
326 }
327
328 fn is_visible(&self) -> bool {
329 self.base.visibility
330 }
331}
332
333impl Widget for DockPanel {
334 fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
335 self.base.alignment = (horizontal, vertical);
336 self.provoke_changed_property(WindowProperty::Alignment);
337 }
338
339 fn set_visibility(&mut self, visibility: bool) {
340 self.base.visibility = visibility;
341 self.provoke_changed_property(WindowProperty::IsVisible);
342 }
343
344 fn set_width(&mut self, width: Size) {
345 self.base.size.x = width;
346 self.provoke_changed_property(WindowProperty::Size);
347 }
348
349 fn set_height(&mut self, height: Size) {
350 self.base.size.y = height;
351 self.provoke_changed_property(WindowProperty::Size);
352 }
353
354 fn set_margin(&mut self, margin: Thickness) {
355 self.base.margin = margin;
356 self.provoke_changed_property(WindowProperty::Margin);
357 }
358
359 fn set_border(&mut self, border: BorderStyle) {
360 self.base.border = border;
361 self.provoke_changed_property(WindowProperty::Border);
362 }
363
364 fn set_enabled_state(&mut self, is_enabled: bool) {
365 self.base.enabled = is_enabled;
366 }
367
368 fn set_width_constraint(&mut self, width: SizeConstraint) {
369 self.base.constraints.x = width;
370 self.provoke_changed_property(WindowProperty::Size);
371 }
372
373 fn set_height_constraint(&mut self, height: SizeConstraint) {
374 self.base.constraints.y = height;
375 self.provoke_changed_property(WindowProperty::Size);
376 }
377}
378
379impl WidgetColors for DockPanel {
380 fn set_disabled_color(&mut self, color: Option<Color>) {
381 self.base.colors.disabled = color;
382 }
383
384 fn set_base_fg_color(&mut self, color: Option<Color>) {
385 self.base.colors.base_fg = color;
386 }
387
388 fn set_base_bg_color(&mut self, color: Option<Color>) {
389 self.base.colors.base_bg = color;
390 }
391}