use densa_core::Point;
use densa_ui::{ActionKind, Cursor, EventCx, Handled, Key, KeyEvent, NamedKey, ScrollDelta};
use super::geometry::divider_rect;
use super::{Axis, DockArea, Drag, Found};
const DRAG_START: f32 = 6.0;
const WHEEL_LINE: f32 = 40.0;
const MIN_PANE: f32 = 48.0;
pub(super) fn resize_cursor(axis: Axis) -> Cursor {
match axis {
Axis::Row => Cursor::ResizeColumn,
Axis::Column => Cursor::ResizeRow,
}
}
impl DockArea {
pub(super) fn press(&mut self, cx: &mut EventCx<'_>, at: Point) -> Handled {
let (rect, scale) = (cx.rect(), cx.scale());
match self.find(at, rect, scale) {
Found::Tab(index, _) => {
if !self.is_active(index) {
self.activate(index);
self.restyle(cx);
cx.emit(ActionKind::Selected(index));
}
self.reveal(self.tabs[index].group, rect, scale);
self.focus_group = Some(self.tabs[index].group);
cx.request_focus();
self.drag = Some(Drag::Pressed {
tab: index,
from: at,
});
Handled::Yes
}
Found::Divider(i) => {
let divider = self.dividers[i];
let strip = divider_rect(÷r, rect, scale);
let grab = match divider.axis {
Axis::Row => at.x - (strip.left() + strip.right()) / 2.0,
Axis::Column => at.y - (strip.top() + strip.bottom()) / 2.0,
};
self.drag = Some(Drag::Divider {
split: divider.split,
grab,
moved: false,
});
cx.set_cursor(Some(resize_cursor(divider.axis)));
cx.request_paint();
Handled::Yes
}
Found::ScrollTabs(group, forward) => {
let strip = self.strip_of(group, rect, scale);
let by = strip.map_or(0.0, |strip| strip.tabs.width() / 2.0);
if self.scroll_tabs(group, if forward { by } else { -by }, rect, scale) {
cx.request_paint();
}
Handled::Yes
}
Found::Group(..) | Found::Nothing => Handled::No,
}
}
pub(super) fn drag_to(&mut self, cx: &mut EventCx<'_>, at: Point) -> Handled {
let (rect, scale) = (cx.rect(), cx.scale());
match self.drag {
Some(Drag::Pressed { tab, from }) => {
let far = cx.px(DRAG_START);
if (at.x - from.x).abs() > far || (at.y - from.y).abs() > far {
let target = self.target_at(at, rect, scale);
self.drag = Some(Drag::Tab { tab, target });
cx.set_cursor(Some(Cursor::Grabbing));
cx.request_paint();
}
Handled::Yes
}
Some(Drag::Tab { tab, target }) => {
let now = self.target_at(at, rect, scale);
if now != target {
self.drag = Some(Drag::Tab { tab, target: now });
cx.request_paint();
}
Handled::Yes
}
Some(Drag::Divider { split, grab, moved }) => {
let changed = self.resize(cx, split, at, grab);
self.drag = Some(Drag::Divider {
split,
grab,
moved: moved || changed,
});
Handled::Yes
}
None => {
let found = self.find(at, rect, scale);
let cursor = match found {
Found::Divider(i) => Some(resize_cursor(self.dividers[i].axis)),
_ => None,
};
cx.set_cursor(cursor);
let tab = match found {
Found::Tab(index, _) => Some(index),
_ => None,
};
self.hover(cx, tab);
Handled::No
}
}
}
fn resize(&mut self, cx: &mut EventCx<'_>, split: usize, at: Point, grab: f32) -> bool {
let Some(divider) = self.dividers.iter().find(|d| d.split == split).copied() else {
return false;
};
let (rect, area) = (cx.rect(), divider.area);
let (start, extent, to) = match divider.axis {
Axis::Row => (
rect.left() + area.x0 * rect.width(),
(area.x1 - area.x0) * rect.width(),
at.x - grab,
),
Axis::Column => (
rect.top() + area.y0 * rect.height(),
(area.y1 - area.y0) * rect.height(),
at.y - grab,
),
};
let extent = extent.max(1.0);
let least = (cx.px(MIN_PANE) / extent).min(0.5);
let share = ((to - start) / extent).clamp(least, 1.0 - least);
if !share.is_finite() || !self.set_ratio(split, share) {
return false;
}
self.arrange();
self.restyle(cx);
true
}
pub(super) fn wheel(&mut self, cx: &mut EventCx<'_>, at: Point, delta: ScrollDelta) -> Handled {
let (rect, scale) = (cx.rect(), cx.scale());
let group = match self.find(at, rect, scale) {
Found::Tab(index, _) => self.tabs[index].group,
Found::ScrollTabs(group, _) => group,
Found::Group(..) | Found::Divider(_) | Found::Nothing => return Handled::No,
};
let by = match delta {
ScrollDelta::Lines(dx, dy) => (dx + dy) * cx.px(WHEEL_LINE),
ScrollDelta::Pixels(dx, dy) => dx + dy,
_ => 0.0,
};
if !self.scroll_tabs(group, by, rect, scale) {
return Handled::No;
}
cx.request_paint();
Handled::Yes
}
pub(super) fn key(&mut self, cx: &mut EventCx<'_>, key: &KeyEvent<'_>) -> Handled {
if !key.pressed {
return Handled::No;
}
let focused = cx.is_focused();
let only_control = key.modifiers.control() && !key.modifiers.alt() && !key.modifiers.logo();
let cycle = key.key == Key::Named(NamedKey::Tab) && only_control;
let group = if focused {
self.focused_group()
} else if cycle {
self.group_with_focus(cx)
} else {
None
};
let Some(group) = group else {
return Handled::No;
};
let (tabs, active) = self.group_tabs(group);
let count = tabs.len();
if count == 0 {
return Handled::No;
}
let back = (active + count - 1) % count;
let next = (active + 1) % count;
let to = match key.key {
Key::Named(NamedKey::Tab) if cycle => {
if key.modifiers.shift() {
back
} else {
next
}
}
Key::Named(NamedKey::ArrowLeft) if focused => back,
Key::Named(NamedKey::ArrowRight) if focused => next,
Key::Named(NamedKey::Home) if focused => 0,
Key::Named(NamedKey::End) if focused => count - 1,
_ => return Handled::No,
};
let index = tabs[to];
self.focus_group = Some(group);
if to != active {
self.activate(index);
self.restyle(cx);
cx.emit(ActionKind::Selected(index));
}
self.reveal(group, cx.rect(), cx.scale());
if !focused {
cx.request_focus();
}
cx.request_paint();
Handled::Yes
}
pub(super) fn focused_group(&self) -> Option<usize> {
let known = |g: &usize| self.frames.iter().any(|(f, _)| f == g);
self.focus_group
.filter(known)
.or_else(|| self.frames.first().map(|&(g, _)| g))
}
fn group_with_focus(&self, cx: &EventCx<'_>) -> Option<usize> {
self.tabs
.iter()
.enumerate()
.find(|(i, tab)| self.is_active(*i) && cx.focus_within(tab.widget))
.map(|(_, tab)| tab.group)
}
pub(super) fn hover(&mut self, cx: &mut EventCx<'_>, index: Option<usize>) {
if self.hovered != index {
self.hovered = index;
cx.request_paint();
}
}
pub(super) fn finish(&mut self, cx: &mut EventCx<'_>, dropped: bool) -> Handled {
let Some(drag) = self.drag.take() else {
return Handled::No;
};
cx.set_cursor(None);
cx.request_paint();
match drag {
Drag::Tab {
tab,
target: Some(target),
} if dropped => {
if self.move_tab(tab, target) {
self.hovered = None;
self.arrange();
self.reveal(self.tabs[tab].group, cx.rect(), cx.scale());
self.restyle(cx);
cx.emit(ActionKind::Changed);
}
}
Drag::Divider { moved: true, .. } => cx.emit(ActionKind::Changed),
_ => {}
}
Handled::Yes
}
pub(super) fn prune(&mut self, cx: &mut EventCx<'_>) {
while let Some(dead) = self
.tabs
.iter()
.position(|t| cx.style_of(t.widget).is_none())
{
self.drop_tab(cx, dead);
}
}
pub(super) fn drop_tab(&mut self, cx: &mut EventCx<'_>, index: usize) {
let renumber = |t: usize| if t > index { t - 1 } else { t };
self.drag = match self.drag {
Some(Drag::Pressed { tab, .. } | Drag::Tab { tab, .. }) if tab == index => {
cx.set_cursor(None);
None
}
Some(Drag::Pressed { tab, from }) => Some(Drag::Pressed {
tab: renumber(tab),
from,
}),
Some(Drag::Tab { tab, .. }) => Some(Drag::Tab {
tab: renumber(tab),
target: None,
}),
divider => divider,
};
self.hovered = None;
self.forget(index);
self.arrange();
if let Some(Drag::Divider { split, .. }) = self.drag {
if !self.is_split(split) {
self.drag = None;
cx.set_cursor(None);
}
}
self.restyle(cx);
}
}