use std::mem;
use crate::axis::*;
use crate::background::*;
use crate::splitter::*;
use crate::tabcontrol::*;
use std::collections::HashMap;
use zaplib::*;
pub struct Dock<TItem>
where
TItem: Clone,
{
splitters: Elements<usize, Splitter>,
tab_controls: Elements<usize, TabControl>,
pub drop_size: Vec2,
pub drop_quad: Background,
pub drop_quad_view: View,
pub _drag_move: Option<PointerMoveEvent>,
pub _drag_end: Option<DockDragEnd<TItem>>,
pub _close_tab: Option<DockTabIdent>,
pub _tab_select: Option<(usize, usize)>,
}
#[derive(Clone, Debug)]
pub struct DockTabIdent {
tab_control_id: usize,
tab_id: usize,
}
#[derive(Clone)]
pub enum DockDragEnd<TItem>
where
TItem: Clone,
{
OldTab { pe: PointerUpEvent, ident: DockTabIdent },
NewItems { pe: PointerUpEvent, items: Vec<DockTab<TItem>> },
}
#[derive(Clone)]
pub struct DockTab<TItem>
where
TItem: Clone,
{
pub closeable: bool,
pub title: String,
pub item: TItem,
}
#[derive(Clone)]
pub enum DockItem<TItem>
where
TItem: Clone,
{
TabControl { current: usize, previous: usize, tabs: Vec<DockTab<TItem>> },
Splitter { pos: f32, align: SplitterAlign, axis: Axis, first: Box<DockItem<TItem>>, last: Box<DockItem<TItem>> },
}
struct DockWalkStack<'a, TItem>
where
TItem: Clone,
{
counter: usize,
uid: usize,
item: &'a mut DockItem<TItem>,
}
pub enum DockEvent<TItem> {
None,
DockTabClosed(TItem),
DockTabCloned { tab_control_id: usize, tab_id: usize },
DockChanged,
}
pub struct DockWalker<'a, TItem>
where
TItem: Clone,
{
walk_uid: usize,
stack: Vec<DockWalkStack<'a, TItem>>,
splitters: &'a mut Elements<usize, Splitter>,
tab_controls: &'a mut Elements<usize, TabControl>,
_drag_move: &'a mut Option<PointerMoveEvent>,
_drag_end: &'a mut Option<DockDragEnd<TItem>>,
_close_tab: &'a mut Option<DockTabIdent>,
_tab_select: &'a mut Option<(usize, usize)>,
}
impl<'a, TItem> DockWalker<'a, TItem>
where
TItem: Clone,
{
pub fn walk_dock_item(&mut self) -> Option<(usize, &mut DockItem<TItem>)> {
let push_or_pop = if let Some(stack_top) = self.stack.last_mut() {
match stack_top.item {
DockItem::TabControl { .. } => {
if stack_top.counter == 0 {
let uid = self.walk_uid;
self.walk_uid += 1;
stack_top.counter += 1;
return Some((uid, unsafe { mem::transmute(&mut *stack_top.item) }));
} else {
None
}
}
DockItem::Splitter { first, last, .. } => {
if stack_top.counter == 0 {
let uid = self.walk_uid;
self.walk_uid += 1;
stack_top.counter += 1;
return Some((uid, unsafe { mem::transmute(&mut *stack_top.item) }));
} else if stack_top.counter == 1 {
stack_top.counter += 1;
Some(DockWalkStack { counter: 0, uid: 0, item: unsafe { mem::transmute(first.as_mut()) } })
} else if stack_top.counter == 2 {
stack_top.counter += 1;
Some(DockWalkStack { counter: 0, uid: 0, item: unsafe { mem::transmute(last.as_mut()) } })
} else {
None
}
}
}
} else {
return None;
};
if let Some(item) = push_or_pop {
self.stack.push(item);
return self.walk_dock_item();
} else if !self.stack.is_empty() {
self.stack.pop();
return self.walk_dock_item();
}
None
}
pub fn walk_handle_dock(&mut self, cx: &mut Cx, event: &mut Event) -> Option<(&mut TItem, DockTabIdent)> {
let push_or_pop = if let Some(stack_top) = self.stack.last_mut() {
match stack_top.item {
DockItem::TabControl { current, previous, tabs } => {
if stack_top.counter == 0 {
let tab_control_id = self.walk_uid;
stack_top.counter += 1;
stack_top.uid = self.walk_uid;
self.walk_uid += 1;
if *current < tabs.len() {
return Some((
unsafe { mem::transmute(&mut tabs[*current].item) },
DockTabIdent { tab_control_id, tab_id: *current },
));
}
None
} else {
let tab_control_option = self.tab_controls.get_mut(stack_top.uid);
let mut defocus = false;
if let Some(tab_control) = tab_control_option {
match tab_control.handle_tab_control(cx, event) {
TabControlEvent::TabSelect { tab_id } => {
if *current != tab_id {
*previous = *current;
*current = tab_id;
cx.request_draw();
*self._tab_select = Some((stack_top.uid, tab_id));
defocus = true;
}
}
TabControlEvent::TabDragMove { pe, .. } => {
*self._drag_move = Some(pe);
*self._drag_end = None;
cx.request_draw();
}
TabControlEvent::TabDragEnd { pe, tab_id } => {
*self._drag_move = None;
*self._drag_end = Some(DockDragEnd::OldTab {
pe,
ident: DockTabIdent { tab_control_id: stack_top.uid, tab_id },
});
cx.request_draw();
}
TabControlEvent::TabClose { tab_id } => {
*self._close_tab = Some(DockTabIdent { tab_control_id: stack_top.uid, tab_id });
if tab_id < *current && *current > 0 {
*current -= 1;
}
cx.request_draw();
}
_ => (),
}
}
if defocus {
for (id, tab_control) in self.tab_controls.enumerate() {
if *id != stack_top.uid {
tab_control.set_tab_control_focus(cx, false);
}
}
}
None
}
}
DockItem::Splitter { first, last, pos, align, .. } => {
if stack_top.counter == 0 {
stack_top.counter += 1;
stack_top.uid = self.walk_uid;
self.walk_uid += 1;
let split = self.splitters.get_mut(stack_top.uid);
if let Some(split) = split {
match split.handle(cx, event) {
SplitterEvent::Moving { new_pos } => {
*pos = new_pos;
cx.request_draw();
}
SplitterEvent::MovingEnd { new_align, new_pos } => {
*align = new_align;
*pos = new_pos;
cx.request_draw();
}
_ => (),
};
}
Some(DockWalkStack { counter: 0, uid: 0, item: unsafe { mem::transmute(first.as_mut()) } })
} else if stack_top.counter == 1 {
stack_top.counter += 1;
Some(DockWalkStack { counter: 0, uid: 0, item: unsafe { mem::transmute(last.as_mut()) } })
} else {
None
}
}
}
} else {
return None;
};
if let Some(item) = push_or_pop {
self.stack.push(item);
return self.walk_handle_dock(cx, event);
} else if !self.stack.is_empty() {
self.stack.pop();
return self.walk_handle_dock(cx, event);
}
None
}
pub fn walk_draw_dock<F>(&mut self, cx: &mut Cx, mut tab_handler: F) -> Option<&'a mut TItem>
where
F: FnMut(&mut Cx, &mut TabControl, &DockTab<TItem>, bool),
{
let push_or_pop = if let Some(stack_top) = self.stack.last_mut() {
match stack_top.item {
DockItem::TabControl { current, previous: _, tabs } => {
if stack_top.counter == 0 {
stack_top.counter += 1;
stack_top.uid = self.walk_uid;
self.walk_uid += 1;
let tab_control = self.tab_controls.get_draw(stack_top.uid, TabControl::new);
tab_control.begin_tabs(cx);
for (id, tab) in tabs.iter().enumerate() {
tab_handler(cx, tab_control, tab, *current == id)
}
tab_control.end_tabs(cx);
tab_control.begin_tab_page(cx);
if *current < tabs.len() {
return Some(unsafe { mem::transmute(&mut tabs[*current].item) });
}
tab_control.end_tab_page(cx);
None
} else {
let tab_control = self.tab_controls.get_draw(stack_top.uid, TabControl::new);
tab_control.end_tab_page(cx);
None
}
}
DockItem::Splitter { align, pos, axis, first, last } => {
if stack_top.counter == 0 {
stack_top.counter += 1;
stack_top.uid = self.walk_uid;
self.walk_uid += 1;
let split = self.splitters.get_draw(stack_top.uid, Splitter::default);
split.set_splitter_state(align.clone(), *pos, *axis);
split.begin_draw(cx);
Some(DockWalkStack { counter: 0, uid: 0, item: unsafe { mem::transmute(first.as_mut()) } })
} else if stack_top.counter == 1 {
stack_top.counter += 1;
let split = self.splitters.get_draw(stack_top.uid, Splitter::default);
split.mid_draw(cx);
Some(DockWalkStack { counter: 0, uid: 0, item: unsafe { mem::transmute(last.as_mut()) } })
} else {
let split = self.splitters.get_draw(stack_top.uid, Splitter::default);
split.end_draw(cx);
None
}
}
}
} else {
return None;
};
if let Some(item) = push_or_pop {
self.stack.push(item);
return self.walk_draw_dock(cx, tab_handler);
} else if !self.stack.is_empty() {
self.stack.pop();
return self.walk_draw_dock(cx, tab_handler);
}
None
}
}
enum DockDropKind {
Tab(usize),
TabsView,
Left,
Top,
Right,
Bottom,
Center,
}
impl<TItem> Default for Dock<TItem>
where
TItem: Clone,
{
fn default() -> Dock<TItem> {
Dock {
drop_size: Vec2 { x: 100., y: 70. },
drop_quad: Background::default().with_draw_depth(3.),
splitters: Elements::new(),
tab_controls: Elements::new(),
drop_quad_view: View::default().with_is_overlay(true),
_close_tab: None,
_drag_move: None,
_drag_end: None,
_tab_select: None,
}
}
}
impl<TItem> Dock<TItem>
where
TItem: Clone,
{
fn recur_remove_tab(
dock_walk: &mut DockItem<TItem>,
control_id: usize,
tab_id: usize,
counter: &mut usize,
clone: bool,
select_previous: bool,
) -> Option<DockTab<TItem>>
where
TItem: Clone,
{
match dock_walk {
DockItem::TabControl { tabs, current, previous } => {
let id = *counter;
*counter += 1;
if id == control_id {
if tab_id >= tabs.len() {
return None;
}
if clone && tabs[tab_id].closeable {
return Some(tabs[tab_id].clone());
} else {
if select_previous && *previous != *current && *previous < tabs.len() - 1 {
*current = *previous;
} else if *current >= 1 && *current == tabs.len() - 1 {
*current -= 1;
}
return Some(tabs.remove(tab_id));
}
}
}
DockItem::Splitter { first, last, .. } => {
*counter += 1;
let left = Self::recur_remove_tab(first, control_id, tab_id, counter, clone, select_previous);
if left.is_some() {
return left;
}
let right = Self::recur_remove_tab(last, control_id, tab_id, counter, clone, select_previous);
if right.is_some() {
return right;
}
}
}
None
}
fn recur_collapse_empty(dock_walk: &mut DockItem<TItem>) -> bool
where
TItem: Clone,
{
match dock_walk {
DockItem::TabControl { tabs, .. } => return tabs.is_empty(),
DockItem::Splitter { first, last, .. } => {
let rem_first = Self::recur_collapse_empty(first);
let rem_last = Self::recur_collapse_empty(last);
if rem_first && rem_last {
return true;
}
if rem_first {
*dock_walk = *last.clone();
} else if rem_last {
*dock_walk = *first.clone();
}
}
}
false
}
fn recur_split_dock(
dock_walk: &mut DockItem<TItem>,
items: &[DockTab<TItem>],
control_id: usize,
kind: &DockDropKind,
counter: &mut usize,
) -> Option<DockTabIdent>
where
TItem: Clone,
{
match dock_walk {
DockItem::TabControl { tabs, previous: _, current } => {
let id = *counter;
*counter += 1;
if id == control_id {
match kind {
DockDropKind::Tab(id) => {
let mut idc = *id;
for item in items {
tabs.insert(idc, item.clone());
idc += 1;
}
*current = idc - 1;
return Some(DockTabIdent { tab_control_id: control_id, tab_id: *current });
}
DockDropKind::Left => {
*dock_walk = DockItem::Splitter {
align: SplitterAlign::Weighted,
pos: 0.5,
axis: Axis::Vertical,
last: Box::new(dock_walk.clone()),
first: Box::new(DockItem::TabControl { current: 0, previous: 0, tabs: items.to_owned() }),
};
return Some(DockTabIdent { tab_control_id: control_id + 1, tab_id: 0 });
}
DockDropKind::Right => {
*dock_walk = DockItem::Splitter {
align: SplitterAlign::Weighted,
pos: 0.5,
axis: Axis::Vertical,
first: Box::new(dock_walk.clone()),
last: Box::new(DockItem::TabControl { current: 0, previous: 0, tabs: items.to_owned() }),
};
return Some(DockTabIdent { tab_control_id: control_id + 2, tab_id: 0 });
}
DockDropKind::Top => {
*dock_walk = DockItem::Splitter {
align: SplitterAlign::Weighted,
pos: 0.5,
axis: Axis::Horizontal,
last: Box::new(dock_walk.clone()),
first: Box::new(DockItem::TabControl { current: 0, previous: 0, tabs: items.to_owned() }),
};
return Some(DockTabIdent { tab_control_id: control_id + 1, tab_id: 0 });
}
DockDropKind::Bottom => {
*dock_walk = DockItem::Splitter {
align: SplitterAlign::Weighted,
pos: 0.5,
axis: Axis::Horizontal,
first: Box::new(dock_walk.clone()),
last: Box::new(DockItem::TabControl { current: 0, previous: 0, tabs: items.to_owned() }),
};
return Some(DockTabIdent { tab_control_id: control_id + 2, tab_id: 0 });
}
DockDropKind::TabsView | DockDropKind::Center => {
*current = tabs.len() + items.len() - 1;
for item in items {
tabs.push(item.clone());
}
return Some(DockTabIdent { tab_control_id: control_id, tab_id: tabs.len() - 1 });
}
}
}
}
DockItem::Splitter { first, last, .. } => {
*counter += 1;
if let Some(ret) = Self::recur_split_dock(first, items, control_id, kind, counter) {
return Some(ret);
}
if let Some(ret) = Self::recur_split_dock(last, items, control_id, kind, counter) {
return Some(ret);
}
}
}
None
}
fn get_drop_kind(pos: Vec2, drop_size: Vec2, tvr: Rect, cdr: Rect, tab_rects: Vec<Rect>) -> (DockDropKind, Rect) {
if tvr.contains(pos) {
for (id, tr) in tab_rects.iter().enumerate() {
if tr.contains(pos) {
return (DockDropKind::Tab(id), *tr);
}
}
return (DockDropKind::TabsView, tvr);
}
if pos.y < cdr.pos.y + drop_size.y {
return (DockDropKind::Top, Rect { pos: vec2(cdr.pos.x, cdr.pos.y), size: vec2(cdr.size.x, 0.5 * cdr.size.y) });
}
if pos.y > cdr.pos.y + cdr.size.y - drop_size.y {
return (
DockDropKind::Bottom,
Rect { pos: vec2(cdr.pos.x, cdr.pos.y + 0.5 * cdr.size.y), size: vec2(cdr.size.x, 0.5 * cdr.size.y) },
);
}
if pos.x < cdr.pos.x + drop_size.x {
return (DockDropKind::Left, Rect { pos: vec2(cdr.pos.x, cdr.pos.y), size: vec2(0.5 * cdr.size.x, cdr.size.y) });
}
if pos.x > cdr.pos.x + cdr.size.x - drop_size.x {
return (
DockDropKind::Right,
Rect { pos: vec2(cdr.pos.x + 0.5 * cdr.size.x, cdr.pos.y), size: vec2(0.5 * cdr.size.x, cdr.size.y) },
);
}
(DockDropKind::Center, cdr)
}
pub fn dock_drag_out(&mut self, cx: &mut Cx) {
self._drag_move = None;
cx.request_draw();
}
pub fn dock_drag_move(&mut self, cx: &mut Cx, pe: PointerMoveEvent) {
self._drag_move = Some(pe);
cx.request_draw();
}
pub fn dock_drag_cancel(&mut self, cx: &mut Cx) {
self._drag_move = None;
cx.request_draw();
}
pub fn dock_drag_end(&mut self, _cx: &mut Cx, pe: PointerUpEvent, new_items: Vec<DockTab<TItem>>) {
self._drag_move = None;
self._drag_end = Some(DockDragEnd::NewItems { pe, items: new_items });
}
pub fn handle(&mut self, cx: &mut Cx, _event: &mut Event, dock_items: &mut DockItem<TItem>) -> DockEvent<TItem> {
if let Some(close_tab) = &self._close_tab {
let removed_tab =
Self::recur_remove_tab(dock_items, close_tab.tab_control_id, close_tab.tab_id, &mut 0, false, false);
Self::recur_collapse_empty(dock_items);
cx.request_draw();
self._close_tab = None;
return DockEvent::DockTabClosed(removed_tab.unwrap().item);
}
if let Some(drag_end) = self._drag_end.clone() {
self._drag_end = None;
let mut tab_clone_ident = None;
let pe = match &drag_end {
DockDragEnd::OldTab { pe, .. } => pe,
DockDragEnd::NewItems { pe, .. } => pe,
};
for (target_id, tab_control) in self.tab_controls.enumerate() {
let cdr = tab_control.get_content_drop_rect(cx);
let tvr = tab_control.get_tabs_view_rect(cx);
if tvr.contains(pe.abs) || cdr.contains(pe.abs) {
let tab_rects = tab_control.get_tab_rects(cx);
let (kind, _rect) = Self::get_drop_kind(pe.abs, self.drop_size, tvr, cdr, tab_rects);
let mut do_tab_clone = false;
let items = match &drag_end {
DockDragEnd::OldTab { ident, .. } => {
if pe.modifiers.control || pe.modifiers.logo {
do_tab_clone = true;
}
let item = Self::recur_remove_tab(
dock_items,
ident.tab_control_id,
ident.tab_id,
&mut 0,
do_tab_clone,
true,
);
if let Some(item) = item {
if !item.closeable {
do_tab_clone = false;
}
vec![item]
} else {
vec![]
}
}
DockDragEnd::NewItems { items, .. } => items.clone(),
};
if !items.is_empty() {
let new_ident = Self::recur_split_dock(dock_items, &items, *target_id, &kind, &mut 0);
if do_tab_clone {
tab_clone_ident = new_ident;
}
};
}
}
Self::recur_collapse_empty(dock_items);
cx.request_draw();
if let Some(ident) = tab_clone_ident {
return DockEvent::DockTabCloned { tab_control_id: ident.tab_control_id, tab_id: ident.tab_id };
}
return DockEvent::DockChanged;
};
DockEvent::None
}
pub fn draw(&mut self, cx: &mut Cx) {
if let Some(pe) = &self._drag_move {
cx.begin_absolute_box();
self.drop_quad_view.begin_view(cx, LayoutSize::FILL);
for (_id, tab_control) in self.tab_controls.enumerate() {
let cdr = tab_control.get_content_drop_rect(cx);
let tvr = tab_control.get_tabs_view_rect(cx);
if tvr.contains(pe.abs) || cdr.contains(pe.abs) {
let tab_rects = tab_control.get_tab_rects(cx);
let (_kind, rect) = Self::get_drop_kind(pe.abs, self.drop_size, tvr, cdr, tab_rects);
self.drop_quad.draw(cx, rect.translate(cx.get_box_origin()), vec4(0.67, 0.67, 0.67, 0.8));
}
}
self.drop_quad_view.end_view(cx);
cx.end_absolute_box();
}
}
pub fn walker<'a>(&'a mut self, dock_items: &'a mut DockItem<TItem>) -> DockWalker<'a, TItem> {
let stack = vec![DockWalkStack { counter: 0, uid: 0, item: dock_items }];
self.splitters.begin_draw();
self.tab_controls.begin_draw();
DockWalker {
walk_uid: 0,
stack,
splitters: &mut self.splitters,
tab_controls: &mut self.tab_controls,
_drag_move: &mut self._drag_move,
_drag_end: &mut self._drag_end,
_close_tab: &mut self._close_tab,
_tab_select: &mut self._tab_select,
}
}
}
#[derive(Clone, Default)]
struct Elements<ID, T>
where
ID: std::cmp::Ord + std::hash::Hash,
{
element_list: Vec<ID>,
element_map: HashMap<ID, ElementsRedraw<T>>,
redraw_id: u64,
marked_begin_draw: bool,
}
#[derive(Clone, Default)]
struct ElementsRedraw<T> {
redraw_id: u64,
item: T,
}
struct ElementsIterator<'a, ID, T>
where
ID: std::cmp::Ord + std::hash::Hash,
{
elements: &'a mut Elements<ID, T>,
counter: usize,
}
impl<'a, ID, T> Iterator for ElementsIterator<'a, ID, T>
where
ID: std::cmp::Ord + std::hash::Hash + Clone,
{
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.counter >= self.elements.element_list.len() {
return None;
}
let element_id = &self.elements.element_list[self.counter];
let element = self.elements.element_map.get_mut(element_id).unwrap();
self.counter += 1;
if element.redraw_id == self.elements.redraw_id {
return Some(unsafe { std::mem::transmute(&mut element.item) });
}
}
}
}
struct ElementsIteratorNamed<'a, ID, T>
where
ID: std::cmp::Ord + std::hash::Hash,
{
elements: &'a mut Elements<ID, T>,
counter: usize,
}
impl<'a, ID, T> ElementsIteratorNamed<'a, ID, T>
where
ID: std::cmp::Ord + std::hash::Hash,
{
fn new(elements: &'a mut Elements<ID, T>) -> Self {
ElementsIteratorNamed { elements, counter: 0 }
}
}
impl<'a, ID, T> Iterator for ElementsIteratorNamed<'a, ID, T>
where
ID: std::cmp::Ord + std::hash::Hash + Clone,
{
type Item = (&'a ID, &'a mut T);
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.counter >= self.elements.element_list.len() {
return None;
}
let element_id = &mut self.elements.element_list[self.counter];
let element = self.elements.element_map.get_mut(element_id).unwrap();
self.counter += 1;
if element.redraw_id == self.elements.redraw_id {
return Some((unsafe { std::mem::transmute(element_id) }, unsafe { std::mem::transmute(&mut element.item) }));
}
}
}
}
impl<ID, T> Elements<ID, T>
where
ID: std::cmp::Ord + std::hash::Hash + Clone,
{
fn new() -> Elements<ID, T> {
Elements::<ID, T> { redraw_id: 0, element_list: Vec::new(), element_map: HashMap::new(), marked_begin_draw: false }
}
fn enumerate(&mut self) -> ElementsIteratorNamed<ID, T> {
ElementsIteratorNamed::new(self)
}
fn get_mut(&mut self, index: ID) -> Option<&mut T> {
let elem = self.element_map.get_mut(&index);
if let Some(elem) = elem {
Some(&mut elem.item)
} else {
None
}
}
fn begin_draw(&mut self) {
self.marked_begin_draw = true;
}
fn get_draw<F>(&mut self, index: ID, mut insert_callback: F) -> &mut T
where
F: FnMut() -> T,
{
if self.marked_begin_draw {
self.marked_begin_draw = false;
self.redraw_id += 1;
}
let element_list = &mut self.element_list;
let redraw_id = self.redraw_id;
let redraw = self.element_map.entry(index.clone()).or_insert_with(|| {
element_list.push(index);
let elem = insert_callback();
ElementsRedraw { redraw_id, item: elem }
});
redraw.redraw_id = redraw_id;
&mut redraw.item
}
}