forked from 1b-agent/opencadstudio-upstream
Dock: fix double-click on title bars, centre the strip bar, close + menu
- Double-clicking a docked or floating title bar did nothing. iced keeps a widget's state (including the first click of a double-click) only while the widget tree keeps its shape, and pressing a title bar starts a possible drag that wrapped the workspace in an extra pointer-capture layer, rebuilding the title bar and forgetting the first click. The workspace is now always inside the same stack and mouse area; the capture handlers are only attached while dragging. A test drives the real view through iced's UserInterface (keeping its cache between events, as the runtime does) and fails without this fix. - The shown group's edge bar was shorter than its row (the tooltip around the edge band sized it to its content), so it looked off centre. Band and bar now have explicit heights. - Picking a pallet in the + menu closes the menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013RyGiMXcsf9HTKfqDxaQGt
This commit is contained in:
2 files changed
+97
-24
No files matched your search
@@ -649,6 +649,8 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
}
|
||||
let task = self.dock_open_panel(id);
|
||||
self.dock.show_group_of(id);
|
||||
// Adding a pallet is the end of the menu's job.
|
||||
self.dock_edge_menu = None;
|
||||
self.save_config();
|
||||
task
|
||||
}
|
||||
@@ -2438,6 +2440,8 @@ mod tests {
|
||||
assert_eq!(app.dock.left.len(), 2);
|
||||
assert_eq!(app.dock.left[1].panels, vec![PanelId::Browser]);
|
||||
assert_eq!(app.dock_shown_group(DockSide::Left), Some(1));
|
||||
assert_eq!(app.dock_edge_menu, None, "the menu closes after adding");
|
||||
let _ = app.on_dock(DockMsg::EdgeMenu(Some(DockSide::Left)));
|
||||
// Unchecking hides it but keeps its place.
|
||||
let _ = app.on_dock(DockMsg::EdgeMenuToggle(DockSide::Left, PanelId::Browser));
|
||||
assert!(!app.show_browser);
|
||||
@@ -2489,6 +2493,72 @@ mod tests {
|
||||
assert!(app.dock.float_rect(PanelId::Layers).is_some());
|
||||
}
|
||||
|
||||
/// Drive the real view through iced's `UserInterface`: point at the
|
||||
/// first text `label`, feed `events` one at a time, and between events
|
||||
/// hand the published messages to the app and rebuild the view while
|
||||
/// keeping the widget-tree cache, as the runtime does.
|
||||
fn drive_view(app: &mut OpenCADStudio, label: &str, events: &[iced::Event]) {
|
||||
use iced_test::core::{mouse, shell, widget, window, Size};
|
||||
use iced_test::runtime::user_interface::{Cache, UserInterface};
|
||||
use iced_test::selector::Bounded;
|
||||
use iced_test::Selector;
|
||||
let mut renderer = iced_test::futures::futures::executor::block_on(
|
||||
<iced::Renderer as iced_test::core::renderer::Headless>::new(
|
||||
iced_test::core::renderer::Settings::default(),
|
||||
None,
|
||||
),
|
||||
)
|
||||
.expect("headless renderer");
|
||||
let size = Size::new(1600.0, 900.0);
|
||||
let mut cache = Cache::default();
|
||||
let mut cursor = mouse::Cursor::Unavailable;
|
||||
{
|
||||
let mut ui = UserInterface::build(app.view_main(), size, cache, &mut renderer);
|
||||
let mut find = Selector::find(label);
|
||||
ui.operate(&renderer, &mut widget::operation::black_box(&mut find));
|
||||
if let widget::operation::Outcome::Some(Some(target)) = widget::Operation::finish(&find) {
|
||||
if let Some(bounds) = target.visible_bounds() {
|
||||
cursor = mouse::Cursor::Available(bounds.center());
|
||||
}
|
||||
}
|
||||
cache = ui.into_cache();
|
||||
}
|
||||
assert!(matches!(cursor, mouse::Cursor::Available(_)), "{label} not found");
|
||||
for event in events {
|
||||
let mut messages = Vec::new();
|
||||
{
|
||||
let mut ui = UserInterface::build(app.view_main(), size, cache, &mut renderer);
|
||||
let _ = ui.update(
|
||||
&window::Headless,
|
||||
&shell::Waker::noop(),
|
||||
std::slice::from_ref(event),
|
||||
cursor,
|
||||
&mut renderer,
|
||||
&mut messages,
|
||||
);
|
||||
cache = ui.into_cache();
|
||||
}
|
||||
for message in messages {
|
||||
let _ = app.update(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn docked_title_bar_double_click_floats_the_pallet_in_the_real_view() {
|
||||
use crate::ui::dock::PanelId;
|
||||
let mut app = dock_app();
|
||||
let press = iced::Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left));
|
||||
let release =
|
||||
iced::Event::Mouse(iced::mouse::Event::ButtonReleased(iced::mouse::Button::Left));
|
||||
// The block palette's title text sits in its docked title bar.
|
||||
drive_view(&mut app, "Blocks", &[press.clone(), release.clone(), press, release]);
|
||||
assert!(
|
||||
app.dock.float_rect(PanelId::BlockPalette).is_some(),
|
||||
"a double-click on a docked title bar floats the pallet"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dock_double_clicks_float_and_dock_panels() {
|
||||
use crate::app::config::DockSide;
|
||||
|
||||
+27
-24
@@ -1969,14 +1969,14 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
if let (Some(id), Some(target)) = (self.dock_dragging, self.dock_drag_target) {
|
||||
layers.push(self.dock_drop_preview(id, target));
|
||||
}
|
||||
let workspace: Element<'_, Message> = if layers.len() == 1 {
|
||||
layers.pop().expect("workspace layer")
|
||||
} else {
|
||||
iced::widget::Stack::with_children(layers)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into()
|
||||
};
|
||||
// Always a stack (and, below, always a mouse area), even with nothing
|
||||
// on top: iced keeps a widget's state only while the tree keeps its
|
||||
// shape, and a title bar's double-click needs its first click to
|
||||
// survive the drag that pressing the bar starts.
|
||||
let workspace: Element<'_, Message> = iced::widget::Stack::with_children(layers)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into();
|
||||
let any_dragging = self.dock_dragging.is_some();
|
||||
let any_splitting = self.dock_split_drag.is_some();
|
||||
let any_resizing = self.dock_resizing.is_some()
|
||||
@@ -1984,8 +1984,10 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
|| self.layer_col_dragging
|
||||
|| self.xref_col_drag.is_some()
|
||||
|| self.xref_split_drag;
|
||||
let workspace: Element<'_, Message> = if any_dragging || any_resizing || any_splitting {
|
||||
mouse_area(workspace)
|
||||
let capturing = any_dragging || any_resizing || any_splitting;
|
||||
let mut capture = mouse_area(workspace);
|
||||
if capturing {
|
||||
capture = capture
|
||||
.on_move(move |p| Message::Dock(crate::ui::dock::DockMsg::DragMove(p)))
|
||||
.on_release(Message::Dock(crate::ui::dock::DockMsg::DragRelease))
|
||||
.interaction(if let Some((_, from_left)) = self.dock_float_resizing {
|
||||
@@ -2000,11 +2002,9 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
iced::mouse::Interaction::ResizingVertically
|
||||
} else {
|
||||
iced::mouse::Interaction::Grabbing
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
workspace
|
||||
};
|
||||
});
|
||||
}
|
||||
let workspace: Element<'_, Message> = capture.into();
|
||||
let command_line = self.command_line.view(
|
||||
allow_autocomplete,
|
||||
dyn_capturing,
|
||||
@@ -2905,8 +2905,16 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
// The group grip: a band on the window side holding the edge bar.
|
||||
// Explicit heights: the tooltip around the band sizes it to its
|
||||
// content, so a `Fill` bar would stop short of the row's bottom.
|
||||
let bar_w = if lit { 5.0 } else { 3.0 };
|
||||
let bar = container(Space::new().width(bar_w).height(Fill)).style(move |theme: &Theme| {
|
||||
let bar_inset = 5.0;
|
||||
let bar = container(
|
||||
Space::new()
|
||||
.width(bar_w)
|
||||
.height(Length::Fixed(row_h - 2.0 * bar_inset)),
|
||||
)
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
let (color, glow) = if lit {
|
||||
(Some(palette.primary.weak.color), true)
|
||||
@@ -2939,13 +2947,8 @@ impl OpenCADStudio {
|
||||
let band = mouse_area(
|
||||
container(bar)
|
||||
.width(Length::Fixed(STRIP_GRIP_W))
|
||||
.height(Fill)
|
||||
.padding(iced::Padding {
|
||||
top: 5.0,
|
||||
bottom: 5.0,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
})
|
||||
.height(Length::Fixed(row_h))
|
||||
.align_y(iced::alignment::Vertical::Center)
|
||||
.align_x(match side {
|
||||
DockSide::Left => iced::alignment::Horizontal::Left,
|
||||
DockSide::Right => iced::alignment::Horizontal::Right,
|
||||
@@ -2963,7 +2966,7 @@ impl OpenCADStudio {
|
||||
.gap(4);
|
||||
let band = container(band)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.height(Length::Fixed(row_h))
|
||||
.align_x(match side {
|
||||
DockSide::Left => iced::alignment::Horizontal::Left,
|
||||
DockSide::Right => iced::alignment::Horizontal::Right,
|
||||
|
||||
Reference in new issue
Block a user