From 75fb9e32ddaeb74a5faf4f932de316db1a71dd51 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 11:18:42 +0000 Subject: [PATCH] Dock: floating menu at the pointer, hover-only bar buttons, Allow docking, clip pallets - The floating title bar's right-click menu opens where the pointer is, flipped to stay on screen; the Pallets submenu sits beside it. - Close and pin on a floating title bar only show while it is hovered, as on docked title bars. - New Allow docking check in that menu: off, dragging the window (or a pallet out of it) only floats it, never docks it on an edge. - Floating windows and stacked pallets clip their content, so a pallet sized below its content no longer draws outside its frame. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013RyGiMXcsf9HTKfqDxaQGt --- locales/ar-SA/opencadstudio.ftl | 1 + locales/bg-BG/opencadstudio.ftl | 1 + locales/cs-CZ/opencadstudio.ftl | 1 + locales/de-DE/opencadstudio.ftl | 1 + locales/el-GR/opencadstudio.ftl | 1 + locales/en-US/opencadstudio.ftl | 1 + locales/es-ES/opencadstudio.ftl | 1 + locales/fi-FI/opencadstudio.ftl | 1 + locales/fr-FR/opencadstudio.ftl | 1 + locales/hi-IN/opencadstudio.ftl | 1 + locales/hu-HU/opencadstudio.ftl | 1 + locales/it-IT/opencadstudio.ftl | 1 + locales/ja-JP/opencadstudio.ftl | 1 + locales/ko-KR/opencadstudio.ftl | 1 + locales/nl-NL/opencadstudio.ftl | 1 + locales/pl-PL/opencadstudio.ftl | 1 + locales/pt-BR/opencadstudio.ftl | 1 + locales/ru-RU/opencadstudio.ftl | 1 + locales/tr-TR/opencadstudio.ftl | 1 + locales/zh-CN/opencadstudio.ftl | 1 + locales/zh-TW/opencadstudio.ftl | 1 + src/app/dock/tests.rs | 17 +++++++++ src/app/dock/update.rs | 42 +++++++++++++++++++--- src/app/dock/view.rs | 64 ++++++++++++++++++++++----------- src/app/mod.rs | 6 ++++ src/locale_catalog.rs | 1 + src/ui/dock.rs | 35 +++++++++++++++++- 27 files changed, 161 insertions(+), 25 deletions(-) diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 9e78732e8..729a484b7 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -6166,6 +6166,7 @@ count-feature = .invalid-point = نقطة غير صالحة. dock = + .allow-docking = السماح بالإرساء .auto-hide = إخفاء تلقائي .dock-left = إرساء إلى اليسار .dock-right = إرساء إلى اليمين diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 0de11349e..59d517521 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -6154,6 +6154,7 @@ count-feature = .invalid-point = Невалидна точка. dock = + .allow-docking = Разреши закачане .auto-hide = Автоматично скриване .dock-left = Закачи вляво .dock-right = Закачи вдясно diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index b77e80e31..a209e1049 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -6151,6 +6151,7 @@ count-feature = .invalid-point = Neplatný bod. dock = + .allow-docking = Povolit ukotvení .auto-hide = Automaticky skrýt .dock-left = Ukotvit vlevo .dock-right = Ukotvit vpravo diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 59a0369b7..a697eca8b 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -6152,6 +6152,7 @@ count-feature = .invalid-point = Ungültiger Punkt. dock = + .allow-docking = Andocken zulassen .auto-hide = Automatisch ausblenden .dock-left = Links andocken .dock-right = Rechts andocken diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index eee2a7975..82058463b 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -6154,6 +6154,7 @@ count-feature = .invalid-point = Μη έγκυρο σημείο. dock = + .allow-docking = Να επιτρέπεται η πρόσδεση .auto-hide = Αυτόματη απόκρυψη .dock-left = Πρόσδεση αριστερά .dock-right = Πρόσδεση δεξιά diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 6ebc848e4..b1a57b60a 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -6155,6 +6155,7 @@ count-feature = .invalid-point = Invalid point. dock = + .allow-docking = Allow docking .auto-hide = Auto-hide .dock-left = Dock left .dock-right = Dock right diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 1f75e7eb7..4947517a1 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -6154,6 +6154,7 @@ count-feature = .invalid-point = Punto no válido. dock = + .allow-docking = Permitir acoplar .auto-hide = Ocultar automáticamente .dock-left = Acoplar a la izquierda .dock-right = Acoplar a la derecha diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 85f83d9f2..35de0cf29 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -6151,6 +6151,7 @@ count-feature = .invalid-point = Virheellinen piste. dock = + .allow-docking = Salli telakointi .auto-hide = Piilota automaattisesti .dock-left = Telakoi vasemmalle .dock-right = Telakoi oikealle diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 7507461fd..de078bafb 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -6153,6 +6153,7 @@ count-feature = .invalid-point = Point non valide. dock = + .allow-docking = Autoriser l'ancrage .auto-hide = Masquage automatique .dock-left = Ancrer à gauche .dock-right = Ancrer à droite diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index cd1686ba4..cdd91b104 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -6142,6 +6142,7 @@ count-feature = .invalid-point = अमान्य बिंदु। dock = + .allow-docking = डॉकिंग की अनुमति दें .auto-hide = स्वतः छिपाएँ .dock-left = बाएँ डॉक करें .dock-right = दाएँ डॉक करें diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index a7ba41b25..a1a260f5c 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -6153,6 +6153,7 @@ count-feature = .invalid-point = Érvénytelen pont. dock = + .allow-docking = Dokkolás engedélyezése .auto-hide = Automatikus elrejtés .dock-left = Dokkolás balra .dock-right = Dokkolás jobbra diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 90cc6785e..2e36787be 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -6153,6 +6153,7 @@ count-feature = .invalid-point = Punto non valido. dock = + .allow-docking = Consenti ancoraggio .auto-hide = Nascondi automaticamente .dock-left = Ancora a sinistra .dock-right = Ancora a destra diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index b8ac215be..b0ea367e4 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -6142,6 +6142,7 @@ count-feature = .invalid-point = 点が無効です。 dock = + .allow-docking = ドッキングを許可 .auto-hide = 自動的に隠す .dock-left = 左にドッキング .dock-right = 右にドッキング diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 28ac8e053..067ddd8f2 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -6153,6 +6153,7 @@ count-feature = .invalid-point = 점이 잘못되었습니다. dock = + .allow-docking = 도킹 허용 .auto-hide = 자동 숨기기 .dock-left = 왼쪽에 도킹 .dock-right = 오른쪽에 도킹 diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 91b99f044..08632e9c1 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -6152,6 +6152,7 @@ count-feature = .invalid-point = Ongeldig punt. dock = + .allow-docking = Koppelen toestaan .auto-hide = Automatisch verbergen .dock-left = Links koppelen .dock-right = Rechts koppelen diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 4ba9b0a52..0d0319030 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -6153,6 +6153,7 @@ count-feature = .invalid-point = Nieprawidłowy punkt. dock = + .allow-docking = Zezwalaj na dokowanie .auto-hide = Autoukrywanie .dock-left = Zadokuj po lewej .dock-right = Zadokuj po prawej diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 8c1274a9a..550f62bf6 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -6154,6 +6154,7 @@ count-feature = .invalid-point = Ponto inválido. dock = + .allow-docking = Permitir encaixe .auto-hide = Ocultar automaticamente .dock-left = Encaixar à esquerda .dock-right = Encaixar à direita diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index a3e79f208..db6cadab3 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -6155,6 +6155,7 @@ count-feature = .invalid-point = Недопустимая точка. dock = + .allow-docking = Разрешить закрепление .auto-hide = Автоскрытие .dock-left = Закрепить слева .dock-right = Закрепить справа diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 4683551d7..266869dfa 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -6077,6 +6077,7 @@ count-feature = .invalid-point = Geçersiz nokta. dock = + .allow-docking = Yerleştirmeye izin ver .auto-hide = Otomatik gizle .dock-left = Sola yerleştir .dock-right = Sağa yerleştir diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index ac07a0696..2b2118aac 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -6137,6 +6137,7 @@ count-feature = .invalid-point = 无效的点。 dock = + .allow-docking = 允许停靠 .auto-hide = 自动隐藏 .dock-left = 停靠到左侧 .dock-right = 停靠到右侧 diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index aedf9a45f..fb5d8d65f 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -6149,6 +6149,7 @@ count-feature = .invalid-point = 無效的點。 dock = + .allow-docking = 允許停駐 .auto-hide = 自動隱藏 .dock-left = 停駐到左側 .dock-right = 停駐到右側 diff --git a/src/app/dock/tests.rs b/src/app/dock/tests.rs index 30522c614..8c84ac664 100644 --- a/src/app/dock/tests.rs +++ b/src/app/dock/tests.rs @@ -628,3 +628,20 @@ fn blockpalette_width_reset() { let _ = app.on_dock(crate::ui::dock::DockMsg::WidthReset(id)); assert_eq!(app.dock.group_width(DockSide::Right, 0), 260.0); } + +#[test] +fn a_window_without_allow_docking_only_floats_when_dragged() { + use crate::ui::dock::{DockMsg, DropTarget, PanelId}; + let mut app = dock_app(); + let _ = app.on_dock(DockMsg::FloatOut(PanelId::BlockPalette)); + let _ = app.on_dock(DockMsg::FloatDockingToggle(PanelId::BlockPalette)); + assert!(!app.dock.float_rect(PanelId::BlockPalette).unwrap().docking); + // Dragged onto the left edge's strip, it stays a floating window. + let _ = app.on_dock(DockMsg::FloatGrab(PanelId::BlockPalette)); + let _ = app.on_dock(DockMsg::DragMove(iced::Point::new(500.0, 300.0))); + let _ = app.on_dock(DockMsg::DragMove(iced::Point::new(10.0, 60.0))); + assert!(matches!(drag_target(&app), Some(DropTarget::Float { .. }))); + let _ = app.on_dock(DockMsg::DragRelease); + assert!(app.dock.float_rect(PanelId::BlockPalette).is_some()); + assert_eq!(app.dock.location(PanelId::BlockPalette), None); +} diff --git a/src/app/dock/update.rs b/src/app/dock/update.rs index 3b43df3ec..9601846fd 100644 --- a/src/app/dock/update.rs +++ b/src/app/dock/update.rs @@ -149,12 +149,20 @@ impl OpenCADStudio { } DockMsg::FloatMenu(id) => { self.dock_float_menu = id; + self.dock_float_menu_at = self.dock_float_pointer; self.dock_float_menu_pallets = false; if let Some(id) = id { self.dock.raise_float(id); } } DockMsg::FloatMenuPallets(open) => self.dock_float_menu_pallets = open, + 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::FloatMenuToggle(window, id) => { // Checked = open in this window: unchecking hides it. // Anything else (closed, docked, in another window) moves @@ -289,7 +297,13 @@ impl OpenCADStudio { } DropTarget::Float { x, y } => { let (w, h) = self.dock_float_size(panel); - self.dock.float(FloatPanel::new(panel, x, y, w, h)) + // Pulled out of a window, it keeps the + // window's 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) + }) } }; } @@ -356,9 +370,18 @@ impl OpenCADStudio { grab }; let target = if started { + // A window that may not dock (or a pallet pulled out of + // one) only floats. + let floats_only = group.is_none() + && self.dock.float_rect(panel).is_some_and(|f| !f.docking); + let found = if floats_only { + self.dock_float_target(point, grab) + } else { + self.dock_drop_target(point, grab) + }; // A whole docked group moves between group positions or // floats; it cannot join another group. - match (group, self.dock_drop_target(point, grab)) { + match (group, found) { (Some(_), DropTarget::Join { .. }) => None, (_, found) => Some(found), } @@ -786,7 +809,7 @@ impl OpenCADStudio { ) -> crate::ui::dock::DropTarget { use crate::app::config::DockSide; use crate::ui::dock::{DropTarget, StripHit, DOCK_EDGE_ZONE, DOCK_STRIP_W}; - let (ww, wh) = self.dock_workspace_size(); + 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 { @@ -841,8 +864,19 @@ impl OpenCADStudio { } } } + 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)); - DropTarget::Float { x, y } + crate::ui::dock::DropTarget::Float { x, y } } } diff --git a/src/app/dock/view.rs b/src/app/dock/view.rs index 994b55464..bea980338 100644 --- a/src/app/dock/view.rs +++ b/src/app/dock/view.rs @@ -140,6 +140,7 @@ impl OpenCADStudio { .height(Length::FillPortion(crate::ui::dock::portion( group.weights[pos], ))) + .clip(true) .into(), ); prev = Some(pos); @@ -474,7 +475,8 @@ impl OpenCADStudio { let (x, y, bar_left, hidden) = self.dock_float_frame(f); // Messages name the window by its first open pallet. let anchor = visible[0]; - let bar = floating_title_bar(&visible, self.dock.auto_hides(anchor), bar_left); + let hovered = self.dock_title_hover.is_some_and(|p| f.contains(p)); + let bar = floating_title_bar(&visible, self.dock.auto_hides(anchor), bar_left, hovered); let content: Element<'_, Message> = if hidden { bar } else { @@ -511,6 +513,7 @@ impl OpenCADStudio { pallets.push( container(self.panel_body(*id, f.w, true, tab)) .height(Length::FillPortion(crate::ui::dock::portion(weight))) + .clip(true) .into(), ); prev = Some(pos); @@ -535,8 +538,11 @@ impl OpenCADStudio { row![body, bar].into() } }; + // Clipped, so a pallet squeezed below its content's height never + // draws outside the window. let framed = container(content) .height(Length::Fixed(f.h)) + .clip(true) .style(|theme: &Theme| container::Style { border: Border { color: theme.palette().background.strong.color, @@ -561,8 +567,8 @@ impl OpenCADStudio { } /// The open right-click menu of a floating window: Pallets (a submenu - /// to add or hide pallets in the window), Dock left and Dock right. It - /// hangs beside the title bar, toward the window's inside. + /// to add or hide pallets in the window), Allow docking, Dock left and + /// Dock right. It opens at the pointer, flipped to stay on screen. fn dock_float_menu_overlay(&self) -> Option> { use crate::app::config::DockSide; use crate::ui::dock::{DockMsg, PanelId}; @@ -571,17 +577,16 @@ impl OpenCADStudio { if self.dock_float_visible(f).is_empty() { return None; } - let (ww, _) = self.dock_workspace_size(); + let (ww, wh) = self.dock_workspace_size(); let (x, y, bar_left, hidden) = self.dock_float_frame(f); let total_w = if hidden { DOCK_FLOAT_BAR_W } else { f.w + DOCK_FLOAT_BAR_W }; let bar_x = if bar_left { x } else { x + total_w - DOCK_FLOAT_BAR_W }; + // Where the right-click happened, in the workspace. + let at = iced::Point::new(bar_x + self.dock_float_menu_at.x, y + self.dock_float_menu_at.y); let max_x = (ww - DOCK_MENU_W).max(0.0); - let menu_x = if bar_left { - bar_x + DOCK_FLOAT_BAR_W + 2.0 - } else { - bar_x - DOCK_MENU_W - 2.0 - } - .clamp(0.0, max_x); + let menu_x = if at.x + DOCK_MENU_W <= ww { at.x } else { at.x - DOCK_MENU_W }; + let menu_x = menu_x.clamp(0.0, max_x); + let menu_y = at.y.min(wh - FLOAT_MENU_H).max(0.0); let open = self.dock_float_menu_pallets; // Hovering a row opens (Pallets) or closes (the others) the submenu. let item = |label: Element<'static, Message>, press: DockMsg, sub: bool| -> Element<'static, Message> { @@ -607,9 +612,17 @@ impl OpenCADStudio { background: Some(Background::Color(theme.palette().background.neutral.color)), ..Default::default() }); + let check: Element<'static, Message> = if f.docking { + crate::ui::icons::themed(crate::ui::icons::CHECK, 12.0) + } else { + Space::new().width(12).height(12).into() + }; + let docking = row![text(t!("Allow docking")).size(12).width(Fill), check] + .align_y(iced::Center); let main = column![ item(pallets.into(), DockMsg::FloatMenuPallets(!open), true), container(separator).padding([3, 4]), + item(docking.into(), DockMsg::FloatDockingToggle(anchor), false), item( text(t!("Dock left")).size(12).into(), DockMsg::DockTo(anchor, DockSide::Left), @@ -623,24 +636,22 @@ impl OpenCADStudio { ] .spacing(1) .padding(4); - let mut layers = vec![place_at(menu_frame(main.into()), menu_x, y + 4.0)]; + let mut layers = vec![place_at(menu_frame(main.into()), menu_x, menu_y)]; if open { let checked: Vec<_> = PanelId::ALL .iter() .map(|p| (*p, f.contains(*p) && self.dock_panel_visible(*p))) .collect(); let toggle = move |id| Message::Dock(DockMsg::FloatMenuToggle(anchor, id)); - // Beside the menu, on the side it opened toward, unless there is - // no room there. - let right = menu_x + 2.0 * DOCK_MENU_W + 2.0 <= ww; - let left = menu_x - DOCK_MENU_W - 2.0 >= 0.0; - let sub_x = if (bar_left && right) || !left { + // To the right of the menu, or its left when there is no room. + let sub_x = if menu_x + 2.0 * DOCK_MENU_W + 2.0 <= ww { menu_x + DOCK_MENU_W + 2.0 } else { menu_x - DOCK_MENU_W - 2.0 } .clamp(0.0, max_x); - layers.push(place_at(dock_pallet_menu(&checked, toggle), sub_x, y + 4.0)); + let sub_y = menu_y.min(wh - PALLET_MENU_H).max(0.0); + layers.push(place_at(dock_pallet_menu(&checked, toggle), sub_x, sub_y)); } Some( iced::widget::Stack::with_children(layers) @@ -954,6 +965,7 @@ fn floating_title_bar( visible: &[crate::ui::dock::PanelId], auto_collapse: bool, on_left: bool, + hovered: bool, ) -> Element<'static, Message> { use crate::ui::dock::DockMsg; let anchor = visible[0]; @@ -962,9 +974,14 @@ fn floating_title_bar( } else { iced::widget::tooltip::Position::Right }; - let close = crate::ui::dock::close_button(DockMsg::FloatClose(anchor), tip); - let pin = crate::ui::dock::pin_button(anchor, auto_collapse, tip); - let mut bar = column![close, pin].spacing(2); + // Close and pin only show while the pointer is over the bar, as on a + // docked title bar. + let mut bar = column![].spacing(2); + if hovered { + bar = bar + .push(crate::ui::dock::close_button(DockMsg::FloatClose(anchor), tip)) + .push(crate::ui::dock::pin_button(anchor, auto_collapse, tip)); + } if let [id] = visible { let title = canvas(VBarLabel { text: t!(id.title()).into_owned(), @@ -1000,6 +1017,9 @@ fn floating_title_bar( })) .on_press(Message::Dock(DockMsg::FloatGrab(anchor))) .on_right_press(Message::Dock(DockMsg::FloatMenu(Some(anchor)))) + .on_enter(Message::Dock(DockMsg::TitleHover(anchor))) + .on_move(|p| Message::Dock(DockMsg::FloatBarPointer(p))) + .on_exit(Message::Dock(DockMsg::TitleHoverEnd(anchor))) // Double-click docks it against the edge its title bar faces. .on_double_click(Message::Dock(DockMsg::DockTo( anchor, @@ -1058,6 +1078,10 @@ fn dock_divider(id: crate::ui::dock::PanelId) -> Element<'static, Message> { /// Width of the icon strip's pallet menu. const DOCK_MENU_W: f32 = 230.0; +/// Rough heights of a floating window's menu and of a pallet menu, to keep +/// them on screen. +const FLOAT_MENU_H: f32 = 150.0; +const PALLET_MENU_H: f32 = 270.0; /// A pallet menu (an edge's + button, a floating window's Pallets /// submenu), styled like the right-click menu: one row per pallet with its diff --git a/src/app/mod.rs b/src/app/mod.rs index 0887f0718..bb4190dfe 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -869,6 +869,10 @@ pub(super) struct OpenCADStudio { /// is open, and whether its Pallets submenu shows. pub(crate) dock_float_menu: Option, pub(crate) dock_float_menu_pallets: bool, + /// Pointer position over a floating title bar (bar-local), and where + /// its open menu was asked for. + pub(crate) dock_float_pointer: iced::Point, + pub(crate) dock_float_menu_at: iced::Point, /// Group whose edge band (group grip) the pointer is over. pub(crate) dock_grip_hover: Option<(crate::app::config::DockSide, usize)>, /// Reference-table column currently being width-resized (column index), @@ -4303,6 +4307,8 @@ impl OpenCADStudio { dock_hover_gen: 0, dock_float_menu: None, dock_float_menu_pallets: false, + dock_float_pointer: iced::Point::ORIGIN, + dock_float_menu_at: iced::Point::ORIGIN, dock_drag: None, dock_drag_last: None, dock_title_hover: None, diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 730c2fe0c..91bb128bf 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -5755,6 +5755,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Block \"{part}\" cannot be found." => Some(("count-feature", "block-not-found")), "Invalid point." => Some(("count-feature", "invalid-point")), "Auto-hide" => Some(("dock", "auto-hide")), + "Allow docking" => Some(("dock", "allow-docking")), "Dock left" => Some(("dock", "dock-left")), "Dock right" => Some(("dock", "dock-right")), "New group" => Some(("dock", "new-group")), diff --git a/src/ui/dock.rs b/src/ui/dock.rs index d1fb5dd67..3b8c21621 100644 --- a/src/ui/dock.rs +++ b/src/ui/dock.rs @@ -85,6 +85,12 @@ pub enum DockMsg { FloatMenu(Option), /// Show (`true`) or hide the floating menu's Pallets submenu. FloatMenuPallets(bool), + /// Floating menu's Allow docking check: flip it for the window holding + /// the pallet. + FloatDockingToggle(PanelId), + /// The pointer moved over a floating title bar, at this point in it + /// (where its right-click menu opens). + FloatBarPointer(iced::Point), /// Pallets submenu pick (window, pallet): hide the pallet when it shows /// in the window, else add it to the window and open it. FloatMenuToggle(PanelId, PanelId), @@ -342,6 +348,9 @@ pub struct FloatPanel { pub y: f32, pub w: f32, pub h: f32, + /// Dragging the window (or a pallet out of it) may dock it on an edge; + /// off, it only ever floats. + pub docking: bool, } /// On-disk form of a floating window. Configs from before floating groups @@ -358,6 +367,12 @@ struct FloatRepr { y: f32, w: f32, h: f32, + #[serde(default = "docking_default")] + docking: bool, +} + +fn docking_default() -> bool { + true } impl From for FloatPanel { @@ -373,6 +388,7 @@ impl From for FloatPanel { y: r.y, w: r.w, h: r.h, + docking: r.docking, } } } @@ -386,6 +402,7 @@ impl FloatPanel { y, w, h, + docking: true, } } @@ -830,7 +847,14 @@ impl DockState { } let g = self.groups_mut(side).remove(group); self.after_group_removed(side, group); - let mut f = FloatPanel { group: g, x, y, w, h }; + let mut f = FloatPanel { + group: g, + x, + y, + w, + h, + docking: true, + }; f.heal(); self.floating.push(f); true @@ -902,6 +926,15 @@ impl DockState { true } + /// Flip whether dragging the floating window holding `id` may dock it. + pub fn toggle_float_docking(&mut self, id: PanelId) -> bool { + let Some(i) = self.float_index(id) else { + return false; + }; + self.floating[i].docking = !self.floating[i].docking; + true + } + /// Bring the floating window holding `id` to the front. pub fn raise_float(&mut self, id: PanelId) { if let Some(i) = self.float_index(id) {