feat(dock): stacked edge rendering and drag-to-reorder preview

This commit is contained in:
Karim Jerbi committed 2026-08-11 23:13:05 +01:00
1 parent 10a1d89f19
commit 7bfb617745
8 files changed
+326 -448

No files matched your search

-58
View File
@@ -196,15 +196,6 @@ impl DockState {
true
}
/// True when the panel is in a non-empty reduced state on `side`, i.e. the
/// number of reduced panels that share the side's column height.
pub fn reduced_count(&self, side: DockSide, expanded: Option<PanelId>) -> usize {
self.stack(side)
.iter()
.filter(|id| Some(**id) != expanded)
.count()
}
/// Number of panels currently docked on `side`.
pub fn len(&self, side: DockSide) -> usize {
self.stack(side).len()
@@ -228,49 +219,10 @@ pub const DOCK_MIN_W: f32 = 200.0;
/// Largest docked width a panel may be dragged or sized to.
pub const DOCK_MAX_W: f32 = 600.0;
/// Height allocated to one reduced panel sharing an edge with `count` others.
pub fn slice_height(count: usize, avail: f32) -> f32 {
if count == 0 {
0.0
} else {
avail / count as f32
}
}
/// Index (top → bottom) of the reduced slice whose bounds contain screen-local
/// `y` measured from the top of the edge column. Returns `None` for a y outside
/// the column or when there are no reduced panels. Even when an expanded panel
/// overlays the column, this computed index lets a reduced sibling be raised.
pub fn slice_index_at_y(y: f32, count: usize, avail: f32) -> Option<usize> {
if count == 0 || avail <= 0.0 || y < 0.0 {
return None;
}
let h = avail / count as f32;
let i = (y / h) as usize;
(i < count).then_some(i)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slice_height_divides_equally() {
assert_eq!(slice_height(3, 300.0), 100.0);
assert_eq!(slice_height(1, 300.0), 300.0);
assert_eq!(slice_height(0, 300.0), 0.0);
}
#[test]
fn slice_index_maps_y_to_panel() {
assert_eq!(slice_index_at_y(50.0, 3, 300.0), Some(0));
assert_eq!(slice_index_at_y(110.0, 3, 300.0), Some(1));
assert_eq!(slice_index_at_y(299.0, 3, 300.0), Some(2));
assert_eq!(slice_index_at_y(300.0, 3, 300.0), None);
assert_eq!(slice_index_at_y(-1.0, 3, 300.0), None);
assert_eq!(slice_index_at_y(50.0, 0, 300.0), None);
}
#[test]
fn default_docks_each_known_panel_on_an_edge() {
let state = DockState::default();
@@ -336,16 +288,6 @@ mod tests {
assert_eq!(state.width(PanelId::BlockPalette, 800.0), DOCK_MAX_W.min(360.0));
}
#[test]
fn reduced_count_excludes_expanded() {
let state = DockState::default(); // right: [BlockPalette]
assert_eq!(state.reduced_count(DockSide::Right, None), 1);
assert_eq!(
state.reduced_count(DockSide::Right, Some(PanelId::BlockPalette)),
0
);
}
#[test]
fn drop_index_maps_between_zero_and_total() {
assert_eq!(drop_index(0.0, 3, 300.0), 0);
+4 -3
View File
@@ -369,6 +369,7 @@ impl PropertiesPanel {
}
pub fn view(&self, width: f32, auto_collapse: bool) -> Element<'_, Message> {
use crate::ui::dock::{DockMsg, PanelId};
// ── Header ──────────────────────────────────────────────────────────
let pin_icon = if auto_collapse {
crate::ui::icons::themed_primary_weak_text(crate::ui::icons::PIN, 12.0)
@@ -376,7 +377,7 @@ impl PropertiesPanel {
crate::ui::icons::themed_secondary(crate::ui::icons::PIN, 12.0)
};
let pin = button(pin_icon)
.on_press(Message::PropertiesAutoCollapseToggle)
.on_press(Message::Dock(DockMsg::AutoCollapseToggle(PanelId::Properties)))
.style(move |theme: &Theme, status| {
let mut style = button::subtle(theme, status);
if auto_collapse {
@@ -395,7 +396,7 @@ impl PropertiesPanel {
crate::ui::icons::CLOSE,
12.0,
))
.on_press(Message::PropertiesClose)
.on_press(Message::Dock(DockMsg::Close(PanelId::Properties)))
.style(button::subtle)
.padding([3, 5]);
let close = tooltip(
@@ -425,7 +426,7 @@ impl PropertiesPanel {
.width(Length::Fill)
.padding([3, 6]),
)
.on_press(Message::PropertiesDockGrab)
.on_press(Message::Dock(DockMsg::DockGrab(PanelId::Properties)))
.interaction(iced::mouse::Interaction::Grab);
// ── Title bar (entity type / "No Selection") ─────────────────────