Files
OneCAD-Draft/src/app/dock/update.rs
T
Claude 33433b8924 fix(dock): keep an auto-hiding panel out while its colour popup is open
The Layer Manager's colour dropdown draws as an overlay, which the
docked edge's mouse area reads as the pointer leaving. The auto-hide
timer then collapsed the panel and the dropdown with it, so no colour
could be picked. Defer the hide while a pallet colour dropdown or the
Select Color window is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ihuqzxdUhd9UbioPn5MGu
2026-10-15 18:15:51 +00:00

850 lines
35 KiB
Rust

//! Dock state changes: drags, hover, open / close, and the queries the view
//! and drop targeting share.
use crate::app::{Message, OpenCADStudio};
/// Pointer travel (px) before pressing a title bar or icon becomes a drag,
/// so a plain click never moves anything.
const DOCK_DRAG_THRESHOLD: f32 = 5.0;
/// Resolves once the dock hover delay has passed (a sleeping helper thread;
/// the web build has none and resolves at once).
fn hover_delay() -> impl std::future::Future<Output = ()> {
#[cfg(not(target_arch = "wasm32"))]
let rx = {
let (tx, rx) = iced::futures::channel::oneshot::channel::<()>();
std::thread::spawn(move || {
std::thread::sleep(crate::ui::dock::DOCK_HOVER_DELAY);
let _ = tx.send(());
});
rx
};
async move {
#[cfg(not(target_arch = "wasm32"))]
let _ = rx.await;
}
}
impl OpenCADStudio {
/// Dock chrome interaction (grab / pin / resize / hover / move) applied to
/// whichever panel the message names.
pub(crate) fn on_dock(&mut self, m: crate::ui::dock::DockMsg) -> iced::Task<Message> {
use crate::app::config::DockSide;
use crate::ui::dock::{DockDrag, DockMsg, FloatPanel};
match m {
DockMsg::DockGrab(id) => self.dock_begin(DockDrag::moving(id, None), Some(id)),
DockMsg::IconPress(id) => {
// An icon press shows the icon's group and may also start
// dragging the pallet; a plain click never passes the drag
// threshold.
if self.dock.show_group_of(id) {
self.save_config();
}
self.dock_begin(DockDrag::moving(id, None), Some(id));
}
DockMsg::GroupGrab(side, gi) => {
if let Some(&id) = self.dock_group_visible(side, gi).first() {
self.dock_begin(DockDrag::moving(id, Some((side, gi))), Some(id));
}
}
DockMsg::ResizeGrab(id) => self.dock_begin(DockDrag::Width(id), Some(id)),
DockMsg::SplitGrab(side, group, upper, lower) => self.dock_begin(
DockDrag::Split {
side,
group,
upper,
lower,
},
None,
),
DockMsg::FloatResizeGrab(panel, from_left) => {
self.dock.raise_float(panel);
self.dock_begin(DockDrag::FloatSize { panel, from_left }, None);
}
DockMsg::SplitReset(side, gi) => {
self.dock.reset_splits(side, gi);
self.save_config();
}
DockMsg::WidthReset(id) => {
// Only docked pallets have the divider that sends this.
if let Some((side, gi)) = self.dock.location(id) {
self.dock.reset_group_width(side, gi);
self.save_config();
}
}
DockMsg::TitleHover(id) => self.dock_title_hover = Some(id),
DockMsg::TitleHoverEnd(id) => {
if self.dock_title_hover == Some(id) {
self.dock_title_hover = None;
}
}
DockMsg::GripHover(side, gi) => self.dock_grip_hover = Some((side, gi)),
DockMsg::GripHoverEnd(side, gi) => {
if self.dock_grip_hover == Some((side, gi)) {
self.dock_grip_hover = None;
}
}
DockMsg::FloatRaise(id) => self.dock.raise_float(id),
DockMsg::FloatOut(id) => {
// Float beside the edge it was docked on, near the top.
let (ww, _) = self.dock_workspace_size();
let (w, h) = self.dock_float_size(id);
let strip = crate::ui::dock::DOCK_STRIP_W;
let x = match self.dock.location(id) {
Some((DockSide::Right, _)) => {
ww - strip - self.dock_column_width(DockSide::Right) - w - 60.0
}
_ => strip + self.dock_column_width(DockSide::Left) + 40.0,
};
self.dock_drag = None;
if self.dock.float(FloatPanel::new(id, x.max(0.0), 40.0, w, h)) {
self.save_config();
}
}
DockMsg::DockTo(id, side) => {
self.dock_drag = None;
self.dock_float_menu = None;
if self.dock.dock(id, side, usize::MAX) {
self.save_config();
}
}
DockMsg::FloatMenu(id) => {
self.dock_float_menu = id;
self.dock_float_menu_at = self.dock_float_pointer;
if let Some(id) = id {
self.dock.raise_float(id);
}
}
DockMsg::FloatBarPointer(p) => self.dock_float_pointer = p,
DockMsg::FloatDockingToggle(id) => {
self.dock_float_menu = None;
if self.dock.toggle_float_docking(id) {
self.save_config();
}
}
DockMsg::EdgeMenu(side) => self.dock_edge_menu = side,
DockMsg::EdgeMenuToggle(side, id) => {
// Checked = open and docked on this edge: unchecking hides it.
// Anything else (closed, floating, on the other edge) moves
// here and opens, in its own group here if it has one, else
// as a new group; adding a pallet ends the menu's job.
let here = self.dock.location(id).map(|(s, _)| s) == Some(side);
if !here {
self.dock.dock(id, side, usize::MAX);
} else if self.dock_panel_visible(id) {
let task = self.dock_set_open(id, false);
self.save_config();
return task;
}
self.dock_edge_menu = None;
let task = self.dock_set_open(id, true);
self.save_config();
return task;
}
DockMsg::AutoCollapseToggle(id) => {
// Docked: auto-hide for the whole edge; floating: this pallet.
if self.dock.toggle_auto_hide(id) {
self.dock_peek = None;
}
self.save_config();
}
DockMsg::Close(id) => return self.dock_set_open(id, false),
DockMsg::Hover(_)
| DockMsg::HoverEnd(_)
| DockMsg::HoverExit
| DockMsg::HoverStay
| DockMsg::HoverSettled(_) => return self.dock_hover(m),
DockMsg::DragMove(point) => self.dock_drag_move(point),
DockMsg::DragRelease => self.dock_drag_release(),
}
iced::Task::none()
}
/// Pointer entering / leaving strip icons, edge columns and floating
/// frames, and the delayed auto-hide reveal / hide that follows.
fn dock_hover(&mut self, m: crate::ui::dock::DockMsg) -> iced::Task<Message> {
use crate::ui::dock::{DockMsg, HoverPending};
match m {
DockMsg::Hover(id) => {
self.dock_icon_hover = Some(id);
// Only an auto-hiding edge (or floating pallet) reacts to
// hover: after a short rest it reveals the hovered pallet's
// group. Otherwise groups switch on click. Ignored mid-drag,
// when the pointer is over the preview rather than the strip.
if self.dock_drag.is_none() && self.dock.auto_hides(id) {
if self.dock_peek == Some(id) {
self.dock_hover_cancel();
} else {
return self.dock_hover_arm(HoverPending::Reveal(id));
}
}
}
DockMsg::HoverEnd(id) => {
if self.dock_icon_hover == Some(id) {
self.dock_icon_hover = None;
}
}
DockMsg::HoverExit => {
if self.dock_drag.is_none()
&& self.dock_peek.is_some_and(|id| self.dock.auto_hides(id))
{
return self.dock_hover_arm(HoverPending::Hide);
}
// Left before a pending reveal ran out: it never happens.
self.dock_hover_cancel();
}
DockMsg::HoverStay => {
if self.dock_hover_pending == Some(HoverPending::Hide) {
self.dock_hover_cancel();
}
}
DockMsg::HoverSettled(gen) => {
if gen == self.dock_hover_gen && self.dock_drag.is_none() {
match self.dock_hover_pending.take() {
Some(HoverPending::Reveal(id)) if self.dock.auto_hides(id) => {
self.dock_reveal(id)
}
Some(HoverPending::Hide)
if self.dock_peek.is_some_and(|id| self.dock.auto_hides(id)) =>
{
// A dropdown or the colour window opened from the
// panel draws as an overlay, which the edge's
// mouse area reads as the pointer leaving. Keep
// the panel out until it closes, so a pick can
// still land; then hide unless the pointer is
// back (its re-entry cancels this).
if self.dock_popup_open() {
return self.dock_hover_arm(HoverPending::Hide);
}
self.dock_peek = None;
}
_ => {}
}
}
}
_ => {}
}
iced::Task::none()
}
/// Pointer released: land what was dragged and save a changed layout.
fn dock_drag_release(&mut self) {
use crate::ui::dock::{DockDrag, DropTarget, FloatPanel};
let mut changed = false;
match self.dock_drag.take() {
Some(DockDrag::Move {
group: Some((side, gi)),
target: Some(DropTarget::Edge { side: to, index }),
..
}) => changed = self.dock.move_group(side, gi, to, index),
Some(DockDrag::Move {
panel,
group: None,
target: Some(target),
..
}) => {
changed = match target {
DropTarget::Edge { side, index } => self.dock.dock(panel, side, index),
DropTarget::Join { side, group, index } => {
self.dock.join_group(panel, side, group, index)
}
DropTarget::Float { x, y } => {
let (w, h) = self.dock_float_size(panel);
// A floating panel keeps its docking choice.
let docking = self.dock.float_rect(panel).is_none_or(|f| f.docking);
self.dock.float(FloatPanel {
docking,
..FloatPanel::new(panel, x, y, w, h)
})
}
};
}
Some(DockDrag::Move { .. } | DockDrag::LayerColumn) | None => {}
Some(DockDrag::Width(_) | DockDrag::Split { .. } | DockDrag::FloatSize { .. }) => {
changed = true;
}
}
if changed {
self.save_config();
}
self.dock_drag_last = None;
self.xref_col_drag = None;
self.xref_col_last = None;
self.xref_split_drag = false;
}
/// Pointer motion while something in the dock is dragged.
fn dock_drag_move(&mut self, point: iced::Point) {
// NOTE: xref column drags intentionally do NOT ride this path: its
// points live in workspace space while the header tracker reports
// header-local points, and mixing the two produced a one-time jump
// plus a stuck drag. Column moves arrive via Message::XrefColMove
// only.
use crate::app::config::DockSide;
use crate::ui::dock::{DockDrag, DropTarget};
let last = self.dock_drag_last.replace(point);
let Some(drag) = self.dock_drag else {
return;
};
match drag {
DockDrag::Move {
panel,
group,
origin,
target,
grab,
} => {
let origin = origin.unwrap_or(point);
let moved = (point.x - origin.x).hypot(point.y - origin.y);
let started = target.is_some() || moved >= DOCK_DRAG_THRESHOLD;
// Hold a floating panel where it was grabbed; a docked one by
// its title bar.
let grab = if target.is_none() && started {
match self.dock.float_rect(panel) {
Some(f) => iced::Vector::new(origin.x - f.x, origin.y - f.y),
None => iced::Vector::new(
(self.dock.width(panel, self.win_size.0) * 0.5).min(120.0),
14.0,
),
}
} else {
grab
};
let target = if started {
// A floating panel that may not dock only floats.
let found = match self.dock.float_rect(panel) {
Some(f) if !f.docking => self.dock_float_target(point, grab),
_ => self.dock_drop_target(point, grab),
};
// A whole group only moves between group positions: it
// cannot join another group or float.
match (group, found) {
(Some(_), found @ DropTarget::Edge { .. }) | (None, found) => Some(found),
(Some(_), _) => None,
}
} else {
target
};
self.dock_drag = Some(DockDrag::Move {
panel,
group,
origin: Some(origin),
target,
grab,
});
}
DockDrag::Split {
side,
group,
upper,
lower,
} => {
let Some(last) = last else { return };
let Some(weights) = self.dock.groups(side).get(group).map(|g| &g.weights) else {
return;
};
let (_, avail) = self.dock_workspace_size();
let total: f32 = self
.dock_slot_spans(side)
.iter()
.filter_map(|(i, _, _)| weights.get(*i))
.sum();
if avail > 0.0 && total > 0.0 {
let per_px = total / avail;
self.dock.shift_split(
side,
group,
upper,
lower,
(point.y - last.y) * per_px,
crate::ui::dock::GROUP_MIN_H * per_px,
);
}
}
DockDrag::FloatSize { panel, from_left } => {
let (Some(last), Some(f)) = (last, self.dock.float_rect(panel)) else {
return;
};
let dx = point.x - last.x;
self.dock.resize_float(
panel,
if from_left { f.w - dx } else { f.w + dx },
f.h + point.y - last.y,
from_left,
);
}
DockDrag::LayerColumn => {
let Some(last) = last else { return };
self.layer_name_col_w =
(self.layer_name_col_w + point.x - last.x).clamp(60.0, 640.0);
}
DockDrag::Width(id) => {
let Some(last) = last else { return };
// The divider sizes the docked pallet's group.
if let Some((side, gi)) = self.dock.location(id) {
let dx = point.x - last.x;
let delta = if side == DockSide::Left { dx } else { -dx };
let cur = self.dock_group_width(side, gi) + delta;
self.dock.set_group_width(side, gi, cur);
}
}
}
}
/// Start `drag`, peeking `peek` (a pallet being dragged or resized stays
/// shown on an auto-hiding edge).
fn dock_begin(
&mut self,
drag: crate::ui::dock::DockDrag,
peek: Option<crate::ui::dock::PanelId>,
) {
if let crate::ui::dock::DockDrag::Move { panel, .. } = drag {
self.dock.raise_float(panel);
}
self.dock_drag = Some(drag);
self.dock_drag_last = None;
self.dock_edge_menu = None;
self.dock_float_menu = None;
if peek.is_some() {
self.dock_peek = peek;
}
}
/// Arm a delayed reveal or hide; it applies when its timer reports back
/// unless a newer hover superseded it.
fn dock_hover_arm(&mut self, target: crate::ui::dock::HoverPending) -> iced::Task<Message> {
self.dock_hover_gen = self.dock_hover_gen.wrapping_add(1);
self.dock_hover_pending = Some(target);
let gen = self.dock_hover_gen;
iced::Task::perform(hover_delay(), move |()| {
Message::Dock(crate::ui::dock::DockMsg::HoverSettled(gen))
})
}
/// Whether a popup a pallet opened is showing: a colour dropdown in the
/// Layer Manager or Properties, or the Select Color window.
fn dock_popup_open(&self) -> bool {
let tab = &self.tabs[self.active_tab];
tab.layers.color_picker_row.is_some()
|| tab.properties.color_picker_open
|| tab.properties.bg_color_picker_open
|| tab.properties.open_color_field.is_some()
|| self.color_picker.is_some()
}
/// Drop any pending delayed reveal / hide.
fn dock_hover_cancel(&mut self) {
self.dock_hover_gen = self.dock_hover_gen.wrapping_add(1);
self.dock_hover_pending = None;
}
/// Bring `id` into view: show its group on its edge and, where it
/// auto-hides, keep it revealed until the pointer leaves.
pub(crate) fn dock_reveal(&mut self, id: crate::ui::dock::PanelId) {
self.dock.show_group_of(id);
self.dock_peek = Some(id);
}
/// Open or close pallet `id`, running its usual open path (refreshes,
/// ribbon highlight) or close path. An opened pallet is revealed where
/// the layout has it.
pub(crate) fn dock_set_open(
&mut self,
id: crate::ui::dock::PanelId,
open: bool,
) -> iced::Task<Message> {
use crate::ui::dock::{DockDrag, PanelId};
let mut task = iced::Task::none();
if open
&& id == PanelId::AuthoringPalettes
&& self.tabs[self.active_tab]
.active_block_edit_session()
.is_none()
{
self.command_line
.push_error(crate::t!("Open the Block Editor to use Authoring Palettes.").as_ref());
return task;
}
if open {
if !self.dock_panel_visible(id) {
match id {
PanelId::Properties => {
self.show_properties = true;
self.ribbon.set_properties(true);
}
PanelId::GraphicAttributes => {
self.show_graphic_attributes = true;
self.ribbon.set_graphic_attributes(true);
self.save_config();
}
PanelId::BlockPalette => self.open_blocks_palette(None),
PanelId::AuthoringPalettes => {
self.show_authoring_palettes = true;
self.ribbon.show_authoring_palettes = true;
self.dock_open_at_default(id);
self.save_config();
}
PanelId::ExternalReferences => {
self.show_external_references = true;
self.refresh_xref_manager();
}
PanelId::Browser => self.show_browser = true,
PanelId::NodeGraph => {
task = self.on_graph(crate::ui::node_graph::GraphMsg::Toggle);
}
PanelId::PointCloudManager => self.pc_manager.show = true,
PanelId::Count => self.set_count_palette(true),
PanelId::SheetSetManager => self.show_sheet_set_manager(true),
PanelId::Layers => {
self.sync_ribbon_layers();
self.show_layers = true;
}
}
}
self.dock_reveal(id);
return task;
}
match id {
PanelId::BlockPalette => {
self.show_block_palette = false;
self.block_palette.placing = None;
}
PanelId::AuthoringPalettes => {
self.show_authoring_palettes = false;
self.ribbon.show_authoring_palettes = false;
}
PanelId::ExternalReferences => self.show_external_references = false,
PanelId::Browser => self.show_browser = false,
PanelId::NodeGraph => self.show_node_graph = false,
PanelId::PointCloudManager => self.pc_manager.show = false,
PanelId::Count => {
self.count_palette.show = false;
self.ribbon.set_count_palette(false);
}
PanelId::SheetSetManager => {
self.sheet_set.show = false;
self.ribbon.set_sheet_set(false);
}
PanelId::Properties => {
self.show_properties = false;
self.ribbon.set_properties(false);
}
PanelId::Layers => {
self.show_layers = false;
self.ribbon.deactivate_tool_if("LAYERS");
}
PanelId::GraphicAttributes => {
self.show_graphic_attributes = false;
self.ribbon.set_graphic_attributes(false);
self.graphic_attributes.close_popups();
self.save_config();
}
}
self.dock_unpeek(id);
// A closed pallet cannot stay held by the pointer.
let held = match self.dock_drag {
Some(DockDrag::Move { panel, .. } | DockDrag::Width(panel)) => panel == id,
_ => false,
};
if held {
self.dock_drag = None;
}
task
}
/// Show `id` where the layout has it, first docking it on the right edge
/// when it has no place yet (opened for the first time, or never moved).
pub(crate) fn dock_open_at_default(&mut self, id: crate::ui::dock::PanelId) {
if !self.dock.is_placed(id) {
self.dock
.dock(id, crate::app::config::DockSide::Right, usize::MAX);
}
self.dock_reveal(id);
}
/// Open the Layer Manager pallet (LAYERS). The first time it floats over
/// the middle of the drawing, where the dialog it replaced used to open;
/// after that it opens wherever the user left it.
pub(crate) fn open_layers_pallet(&mut self) {
use crate::ui::dock::{FloatPanel, PanelId};
self.sync_ribbon_layers();
self.show_layers = true;
if !self.dock.is_placed(PanelId::Layers) {
let (ww, wh) = self.dock_workspace_size();
let (w, h) = self.dock_float_size(PanelId::Layers);
let (x, y) = (((ww - w) * 0.5).max(0.0), ((wh - h) * 0.5).max(0.0));
self.dock
.float(FloatPanel::new(PanelId::Layers, x, y, w, h));
self.save_config();
}
self.dock.raise_float(PanelId::Layers);
self.dock_reveal(PanelId::Layers);
}
/// Stop revealing `id` (it closed).
pub(crate) fn dock_unpeek(&mut self, id: crate::ui::dock::PanelId) {
if self.dock_peek == Some(id) {
self.dock_peek = None;
}
}
/// Whether `id` is currently rendered (not closed, not on the start screen /
/// clean-screen viewport). Mirrors the visibility filter the edge column
/// renderer applies before building each side's stack.
pub(crate) fn dock_panel_visible(&self, id: crate::ui::dock::PanelId) -> bool {
use crate::ui::dock::PanelId;
if self.tabs[self.active_tab].is_start {
return false;
}
if self.clean_screen && !(id == PanelId::Properties && self.clean_screen_properties) {
return false;
}
match id {
PanelId::Properties => self.show_properties,
PanelId::BlockPalette => self.show_block_palette,
PanelId::AuthoringPalettes => {
self.show_authoring_palettes
&& self.tabs[self.active_tab]
.active_block_edit_session()
.is_some()
}
PanelId::ExternalReferences => self.show_external_references,
PanelId::Browser => self.show_browser,
PanelId::NodeGraph => self.show_node_graph,
PanelId::PointCloudManager => self.pc_manager.show,
PanelId::Count => self.count_palette.show,
PanelId::SheetSetManager => self.sheet_set.show,
PanelId::Layers => self.show_layers,
PanelId::GraphicAttributes => self.show_graphic_attributes,
}
}
/// Size of the workspace the dock lays out in: window width by viewport
/// height.
pub(crate) fn dock_workspace_size(&self) -> (f32, f32) {
(
self.win_size.0,
self.tabs[self.active_tab]
.scene
.selection
.borrow()
.view
.vp_size
.1,
)
}
/// The open pallets of group `gi` on `side`, top → bottom. Closed
/// pallets keep their place but take no space, so a group with none open
/// is not drawn at all.
pub(crate) fn dock_group_visible(
&self,
side: crate::app::config::DockSide,
gi: usize,
) -> Vec<crate::ui::dock::PanelId> {
// A stale index (a message from before the layout changed) reads as
// an empty group rather than a crash.
self.dock
.groups(side)
.get(gi)
.into_iter()
.flat_map(|g| g.panels.iter().copied())
.filter(|id| self.dock_panel_visible(*id))
.collect()
}
/// Indices of the groups on `side` with an open pallet (listed in the
/// icon strip).
pub(crate) fn dock_visible_groups(&self, side: crate::app::config::DockSide) -> Vec<usize> {
(0..self.dock.groups(side).len())
.filter(|gi| !self.dock_group_visible(side, *gi).is_empty())
.collect()
}
/// The group `side` draws: the one it shows, or the first with an open
/// pallet when that one has none.
pub(crate) fn dock_shown_group(&self, side: crate::app::config::DockSide) -> Option<usize> {
let visible = self.dock_visible_groups(side);
let shown = self.dock.shown(side);
if visible.contains(&shown) {
Some(shown)
} else {
visible.first().copied()
}
}
/// Whether `side` draws its shown group beside the icon strip: always,
/// unless the edge auto-hides and none of the group's icons is hovered.
pub(crate) fn dock_edge_expanded(&self, side: crate::app::config::DockSide) -> bool {
let Some(gi) = self.dock_shown_group(side) else {
return false;
};
!self.dock.edge_auto_hide(side)
|| self
.dock_peek
.is_some_and(|e| self.dock_group_visible(side, gi).contains(&e))
}
/// On-screen width of group `gi` on `side`.
pub(crate) fn dock_group_width(&self, side: crate::app::config::DockSide, gi: usize) -> f32 {
self.dock.group_width_px(side, gi, self.win_size.0)
}
/// Width of the pallet column beside `side`'s icon strip: the shown
/// group's width, or nothing when it is hidden.
pub(crate) fn dock_column_width(&self, side: crate::app::config::DockSide) -> f32 {
match self.dock_shown_group(side) {
Some(gi) if self.dock_edge_expanded(side) => self.dock_group_width(side, gi),
_ => 0.0,
}
}
/// The icon strip of `side`: each listed group's place, and the top of
/// the + button.
pub(crate) fn dock_strip_layout(
&self,
side: crate::app::config::DockSide,
) -> (Vec<crate::ui::dock::StripGroup>, f32) {
let groups: Vec<(usize, usize)> = self
.dock_visible_groups(side)
.into_iter()
.map(|gi| (gi, self.dock_group_visible(side, gi).len()))
.collect();
crate::ui::dock::strip_layout(&groups)
}
/// The open pallets of `side`'s shown group, each as (position in the
/// group, top, bottom) in the workspace.
pub(crate) fn dock_slot_spans(
&self,
side: crate::app::config::DockSide,
) -> Vec<(usize, f32, f32)> {
match self.dock_shown_group(side) {
Some(gi) => self.dock_open_spans(&self.dock.groups(side)[gi], None),
None => Vec::new(),
}
}
/// The open pallets of `group` (plus `extra`, a pallet about to land
/// there), each as (position in the group, top, bottom) down the
/// workspace height.
pub(crate) fn dock_open_spans(
&self,
group: &crate::ui::dock::DockGroup,
extra: Option<crate::ui::dock::PanelId>,
) -> Vec<(usize, f32, f32)> {
let (_, avail) = self.dock_workspace_size();
let open: Vec<usize> = (0..group.panels.len())
.filter(|i| {
let id = group.panels[*i];
Some(id) == extra || self.dock_panel_visible(id)
})
.collect();
let weights: Vec<f32> = open.iter().map(|i| group.weights[*i]).collect();
open.into_iter()
.zip(crate::ui::dock::slot_spans(&weights, avail))
.map(|(i, (top, bottom))| (i, top, bottom))
.collect()
}
/// Size a panel takes when floated: its current floating size, or its
/// docked width by a comfortable share of the workspace height.
pub(crate) fn dock_float_size(&self, id: crate::ui::dock::PanelId) -> (f32, f32) {
if let Some(f) = self.dock.float_rect(id) {
return (f.w, f.h);
}
let (_, avail) = self.dock_workspace_size();
(
self.dock.width(id, self.win_size.0),
(avail * 0.6).clamp(crate::ui::dock::FLOAT_MIN_H, 520.0),
)
}
/// Where a dragged pallet lands if released at workspace point `p`:
/// on an edge's icon strip it joins a group between its icons or starts a
/// new group; over the shown group's pallets it stacks above / below the
/// one under the pointer; on an empty edge it starts a group; anywhere
/// else it floats, held at `grab` from its top-left corner.
pub(crate) fn dock_drop_target(
&self,
p: iced::Point,
grab: iced::Vector,
) -> crate::ui::dock::DropTarget {
use crate::app::config::DockSide;
use crate::ui::dock::{DropTarget, StripHit, DOCK_EDGE_ZONE, DOCK_STRIP_W};
let (ww, _) = self.dock_workspace_size();
for side in [DockSide::Left, DockSide::Right] {
let listed = !self.dock_visible_groups(side).is_empty();
let from_edge = match side {
DockSide::Left => p.x,
DockSide::Right => ww - p.x,
};
if !listed {
if from_edge <= DOCK_EDGE_ZONE {
return DropTarget::Edge {
side,
index: self.dock.groups(side).len(),
};
}
continue;
}
// Over the icon strip.
if from_edge <= DOCK_STRIP_W {
let (layout, _) = self.dock_strip_layout(side);
return match crate::ui::dock::strip_hit(p.y, &layout) {
StripHit::NewGroup(at) => DropTarget::Edge {
side,
index: at.unwrap_or(self.dock.groups(side).len()),
},
StripHit::Join { group, index } => {
// Map the gap among open pallets to a position among
// all of the group's pallets.
let open = self.dock_group_visible(side, group);
let all = &self.dock.groups(side)[group].panels;
let index = open
.get(index)
.and_then(|t| all.iter().position(|a| a == t))
.unwrap_or(all.len());
DropTarget::Join { side, group, index }
}
};
}
// Over the shown group's pallets: above or below the one there.
if from_edge <= DOCK_STRIP_W + self.dock_column_width(side) {
let gi = self.dock_shown_group(side).expect("listed edge");
let spans = self.dock_slot_spans(side);
if let Some(&(pos, top, bottom)) = spans
.iter()
.find(|(_, _, bottom)| p.y < *bottom)
.or(spans.last())
{
let index = if p.y < (top + bottom) * 0.5 {
pos
} else {
pos + 1
};
return DropTarget::Join {
side,
group: gi,
index,
};
}
}
}
self.dock_float_target(p, grab)
}
/// Floating at workspace point `p`, held at `grab` from its top-left
/// corner (kept on the workspace).
pub(crate) fn dock_float_target(
&self,
p: iced::Point,
grab: iced::Vector,
) -> crate::ui::dock::DropTarget {
let (ww, wh) = self.dock_workspace_size();
let x = (p.x - grab.x).clamp(0.0, (ww - 60.0).max(0.0));
let y = (p.y - grab.y).clamp(0.0, (wh - 30.0).max(0.0));
crate::ui::dock::DropTarget::Float { x, y }
}
}