use arrayvec::ArrayVec;
use as_any::Downcast;
use vector2d::Vector2D;
use crate::{canvas::*, common::*, window::*};
use std::rc::Rc;
use super::*;
pub type FocusChangedCallback = dyn Fn(&mut DockPanel);
pub type DockPanelKeyHandler = fn(&mut DockPanel, event: &mut WindowEvent);
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(usize)]
pub enum DockAlignment {
Top,
Bottom,
Left,
Right,
Center,
}
impl DockAlignment {
pub const VARIANTS: &'static [Self] = &[
DockAlignment::Top,
DockAlignment::Bottom,
DockAlignment::Left,
DockAlignment::Right,
DockAlignment::Center,
];
}
pub struct DockPanel {
base: WidgetBase,
inner_border: bool,
children: [Option<WindowRef>; 5],
inner_borders: ArrayVec<Line2D<TSize>, 4>,
focus: DockAlignment,
key_handler: DockPanelKeyHandler,
callback: Rc<FocusChangedCallback>,
}
impl Default for DockPanel {
fn default() -> Self {
Self::new(false)
}
}
impl DockPanel {
pub const MAX_CHILD_SIZE: Size = Size::Relative(Percent::from_int(25));
pub fn new(inner_border: bool) -> Self {
Self {
base: WidgetBase::default(),
inner_border,
children: [const { None }; 5],
inner_borders: ArrayVec::new(),
focus: DockAlignment::Center,
key_handler: Self::default_key_handler,
callback: Rc::new(Self::default_callback),
}
}
pub fn default_callback(_: &mut Self) {}
pub fn default_key_handler(&mut self, _: &mut WindowEvent) {}
pub fn set_selection_changed_callback<F: Fn(&mut Self) + 'static>(&mut self, callback: F) {
self.callback = Rc::new(callback);
}
pub fn set_key_handler(&mut self, f: DockPanelKeyHandler) {
self.key_handler = f;
}
pub fn set_inner_border(&mut self, inner_border: bool) {
self.inner_border = inner_border;
self.provoke_changed_property(WindowProperty::Children);
}
pub fn set_child(&mut self, dock: DockAlignment, child: Option<WindowRef>) {
if self.focus == dock && child.is_none() {
self.focus = DockAlignment::Center;
}
self.children[dock as usize] = child;
self.provoke_changed_property(WindowProperty::Children);
}
pub fn set_focus(&mut self, dock: DockAlignment) {
if self.focus != dock && self.children[dock as usize].is_some() {
self.focus = dock;
self.provoke_changed_property(WindowProperty::Focus);
let cb = self.callback.clone();
cb(self);
}
}
fn draw_border(window: &dyn Window, mut canvas: BorderCanvas) {
let slf: &Self = window.downcast_ref().unwrap();
if let BorderStyle::Preset { kind, bg, fg } = slf.base.border {
canvas.draw_preset_border(kind, fg, bg, "").unwrap();
let cts = kind.connector_style();
let size = canvas.size();
for l in slf.inner_borders.iter().filter(|x| x.is_horizontal()) {
canvas
.get_glyph(0, l.a.y + 1)
.unwrap()
.set_grapheme(cts[0])
.unwrap();
canvas
.get_glyph(size.x - 1, l.a.y + 1)
.unwrap()
.set_grapheme(cts[1])
.unwrap();
}
}
}
}
impl Window for DockPanel {
fn render(&self, canvas: &mut crate::canvas::Canvas) {
if self.inner_border {
let kind = self.base.border.get_borderkind().unwrap_or_default();
let cts = kind.connector_style();
let lns = kind.line_style();
for line in self.inner_borders.iter() {
if line.is_vertical() {
if let Some(mut column) = canvas.get_column(line.a.x, GlyphWidth::Half) {
for pos in line.iter_points() {
column.get(pos.y).unwrap().set_grapheme(lns[1]).unwrap();
}
}
}
else if let Some(mut row) = canvas.get_row(line.a.y, GlyphWidth::Half) {
for pos in line.iter_points() {
row.get(pos.x).unwrap().set_grapheme(lns[0]).unwrap();
}
}
}
for vert in self.inner_borders.iter().filter(|x| x.is_vertical()) {
for hori in self.inner_borders.iter().filter(|x| x.is_horizontal()) {
if hori.a.y + 1 == vert.a.y {
if let Some(mut gl) =
canvas.get_glyph(vert.a.x, vert.a.y - 1, GlyphWidth::Half)
{
gl.set_grapheme(cts[2]).unwrap();
}
}
else if hori.a.y == vert.b.y {
if let Some(mut gl) = canvas.get_glyph(vert.b.x, vert.b.y, GlyphWidth::Half)
{
gl.set_grapheme(cts[3]).unwrap();
}
}
}
}
}
}
fn handle_event(&mut self, event: &mut WindowEvent) {
(self.key_handler)(self, event)
}
fn children(&mut self, mut builder: SubWindowBuilder) -> SubWindows {
builder.disable_overlapping();
let mut base_rect = builder.base_rect();
let max_x = Self::MAX_CHILD_SIZE.get_size(base_rect.size.x);
let max_y = Self::MAX_CHILD_SIZE.get_size(base_rect.size.y);
let draw_border = self.inner_border && base_rect.size.x > 4 && base_rect.size.y > 4;
self.inner_borders.clear();
for dock in DockAlignment::VARIANTS.iter() {
if let Some(child) = self.children[*dock as usize].clone() {
let rect;
let size = child.borrow().desired_size(Vector2D::new(max_x, max_y));
let border = draw_border as TSize;
if size.x * size.y == 0 {
continue;
}
match dock {
DockAlignment::Top => {
rect = base_rect.subrect(TSize::MIN, TSize::MIN, base_rect.size.x, size.y);
if draw_border {
self.inner_borders.push(Line2D::new(
TSize::MIN,
size.y,
base_rect.end().x,
size.y,
));
}
base_rect = base_rect
.subrect(
TSize::MIN,
size.y + border,
base_rect.size.x,
base_rect.size.y - (size.y + border),
)
.unwrap();
}
DockAlignment::Bottom => {
rect = base_rect.subrect(
TSize::MIN,
base_rect.size.y - size.y,
base_rect.size.x,
size.y,
);
if draw_border {
self.inner_borders.push(Line2D::new(
TSize::MIN,
base_rect.end().y - (size.y + border),
base_rect.end().x,
base_rect.end().y - (size.y + border),
));
}
base_rect = base_rect
.subrect(
TSize::MIN,
TSize::MIN,
base_rect.size.x,
base_rect.size.y - (size.y + border),
)
.unwrap();
}
DockAlignment::Left => {
rect = base_rect.subrect(TSize::MIN, TSize::MIN, size.x, base_rect.size.y);
if draw_border {
self.inner_borders.push(Line2D::new(
size.x,
base_rect.start.y,
size.x,
base_rect.end().y,
));
}
base_rect = base_rect
.subrect(
size.x + border,
TSize::MIN,
base_rect.size.x - (size.x + border),
base_rect.size.y,
)
.unwrap();
}
DockAlignment::Right => {
rect = base_rect.subrect(
base_rect.size.x - size.x,
TSize::MIN,
size.x,
base_rect.size.y,
);
if draw_border {
self.inner_borders.push(Line2D::new(
base_rect.end().x - (size.x + border),
base_rect.start.y,
base_rect.end().x - (size.x + border),
base_rect.end().y,
));
}
base_rect = base_rect
.subrect(
TSize::MIN,
TSize::MIN,
base_rect.size.x - (size.x + border),
base_rect.size.y,
)
.unwrap();
}
DockAlignment::Center => {
rect = Ok(base_rect);
}
}
builder.add_rect(rect.unwrap()).unwrap();
builder.add_child(child);
}
}
builder.build().unwrap()
}
fn focus(&self) -> Option<WindowRef> {
self.children[self.focus as usize].clone()
}
}
impl HasWindowUID for DockPanel {
fn uid(&self) -> WindowUID {
self.base.uid
}
}
impl WindowLayout for DockPanel {
fn desired_size(&self, available_size: TPoint) -> TPoint {
self.base.desired_size(available_size)
}
fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
self.base.alignment
}
fn margin(&self) -> Thickness {
self.base.margin
}
fn border(&self) -> BorderStyle {
BorderStyle::custom(Self::draw_border, Thickness::from(1))
}
fn is_visible(&self) -> bool {
self.base.visibility
}
}
impl Widget for DockPanel {
fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
self.base.alignment = (horizontal, vertical);
self.provoke_changed_property(WindowProperty::Alignment);
}
fn set_visibility(&mut self, visibility: bool) {
self.base.visibility = visibility;
self.provoke_changed_property(WindowProperty::IsVisible);
}
fn set_width(&mut self, width: Size) {
self.base.size.x = width;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_height(&mut self, height: Size) {
self.base.size.y = height;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_margin(&mut self, margin: Thickness) {
self.base.margin = margin;
self.provoke_changed_property(WindowProperty::Margin);
}
fn set_border(&mut self, border: BorderStyle) {
self.base.border = border;
self.provoke_changed_property(WindowProperty::Border);
}
fn set_enabled_state(&mut self, is_enabled: bool) {
self.base.enabled = is_enabled;
}
fn set_width_constraint(&mut self, width: SizeConstraint) {
self.base.constraints.x = width;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_height_constraint(&mut self, height: SizeConstraint) {
self.base.constraints.y = height;
self.provoke_changed_property(WindowProperty::Size);
}
}
impl WidgetColors for DockPanel {
fn set_disabled_color(&mut self, color: Option<Color>) {
self.base.colors.disabled = color;
}
fn set_base_fg_color(&mut self, color: Option<Color>) {
self.base.colors.base_fg = color;
}
fn set_base_bg_color(&mut self, color: Option<Color>) {
self.base.colors.base_bg = color;
}
}