forked from 1b-agent/opencadstudio-upstream
fix(ribbon): scrollable layer dropdown, stop mouse leak to viewport (#227)
The ribbon layer dropdown grew unbounded so a long layer list ran off the bottom of the screen with no way to scroll. Cap the panel height and make the list scrollable; short lists still shrink to fit. While a dropdown was open, the 3D viewport beneath it kept tracking the cursor over the panel's empty areas, and the OS cursor vanished there. In iced 0.14 mouse_area/opaque only capture button presses, never CursorMoved, so a higher stack layer can't swallow motion — gate it state-side instead: - ignore viewport motion in on_viewport_move while ribbon.open_dropdown is set - suppress the crosshair overlay's Hidden cursor (and crosshair draw) so the normal OS cursor shows over an open dropdown - route all ribbon dropdown backdrops through a shared dropdown_backdrop() Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
1b198adc10
commit
3da81571a1
4 files changed
+46
-37
No files matched your search
@@ -482,6 +482,15 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
}
|
||||
|
||||
pub(super) fn on_viewport_move(&mut self, p: Point) -> Task<Message> {
|
||||
// A ribbon dropdown is open over the viewport. Its backdrop
|
||||
// cannot swallow cursor motion — in iced 0.14 mouse_area/opaque
|
||||
// capture only button presses, never CursorMoved — so the move
|
||||
// leaks through the stack to the pane mouse_area beneath and
|
||||
// would track the crosshair over the dropdown's empty areas.
|
||||
// Drop the move here instead. (#227)
|
||||
if self.ribbon.open_dropdown.is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
|
||||
// UCS icon grip drag: map the cursor onto the UCS plane and
|
||||
|
||||
@@ -401,6 +401,7 @@ impl OpenCADStudio {
|
||||
pane_move_rect,
|
||||
pane_drop_rect,
|
||||
tab.pan_mode,
|
||||
self.ribbon.open_dropdown.is_some(),
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
+13
-1
@@ -189,6 +189,7 @@ pub fn selection_overlay<'a>(
|
||||
pane_move_rect: Option<iced::Rectangle>,
|
||||
pane_drop_rect: Option<iced::Rectangle>,
|
||||
pan_mode: bool,
|
||||
suppressed: bool,
|
||||
) -> Element<'a, Message> {
|
||||
canvas(SelectionCanvas {
|
||||
selection,
|
||||
@@ -202,6 +203,7 @@ pub fn selection_overlay<'a>(
|
||||
pane_move_rect,
|
||||
pane_drop_rect,
|
||||
pan_mode,
|
||||
suppressed,
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
@@ -230,6 +232,10 @@ struct SelectionCanvas {
|
||||
/// Interactive PAN mode: the crosshair is hidden and the cursor becomes a
|
||||
/// hand so the viewport reads as a draggable surface.
|
||||
pan_mode: bool,
|
||||
/// A ribbon dropdown (or similar overlay) is open over the viewport. The
|
||||
/// crosshair is not drawn and the OS cursor is shown normally so the panel
|
||||
/// is usable instead of the cursor vanishing over it. (#227)
|
||||
suppressed: bool,
|
||||
}
|
||||
|
||||
impl SelectionCanvas {
|
||||
@@ -259,6 +265,12 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
bounds: iced::Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
) -> mouse::Interaction {
|
||||
// A dropdown/overlay is open over the viewport — show the normal OS
|
||||
// cursor over the whole canvas instead of hiding it for the crosshair,
|
||||
// so the cursor doesn't vanish while using the panel. (#227)
|
||||
if self.suppressed {
|
||||
return mouse::Interaction::default();
|
||||
}
|
||||
// PAN mode owns the whole viewport: an open hand when hovering, a
|
||||
// closed hand while dragging.
|
||||
if self.pan_mode && cursor.is_over(bounds) {
|
||||
@@ -826,7 +838,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
// top of it would double up the visual feedback.
|
||||
let over_divider = self.divider_under(cursor, bounds);
|
||||
// PAN mode replaces the crosshair with a hand cursor.
|
||||
if !over_viewcube && !over_divider && !self.pan_mode {
|
||||
if !over_viewcube && !over_divider && !self.pan_mode && !self.suppressed {
|
||||
if let Some(cp) = self.selection.last_move_pos {
|
||||
let color = Color {
|
||||
r: 0.85,
|
||||
|
||||
+23
-36
@@ -64,6 +64,16 @@ pub struct LayerInfo {
|
||||
pub locked: bool,
|
||||
}
|
||||
|
||||
/// Full-screen backdrop for an open ribbon dropdown: closes the dropdown when
|
||||
/// the user clicks outside the panel. (Cursor motion leaking to the viewport
|
||||
/// beneath is blocked in `on_viewport_move`, not here — iced 0.14's mouse_area
|
||||
/// can only capture button presses, never CursorMoved. #227.)
|
||||
fn dropdown_backdrop<'a>(positioned: Element<'a, Message>) -> Element<'a, Message> {
|
||||
mouse_area(positioned)
|
||||
.on_press(Message::CloseRibbonDropdown)
|
||||
.into()
|
||||
}
|
||||
|
||||
impl Ribbon {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -547,11 +557,7 @@ impl Ribbon {
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
return Some(
|
||||
mouse_area(positioned)
|
||||
.on_press(Message::CloseRibbonDropdown)
|
||||
.into(),
|
||||
);
|
||||
return Some(dropdown_backdrop(positioned.into()));
|
||||
}
|
||||
|
||||
if open_id == LAYER_COMBO_ID {
|
||||
@@ -677,11 +683,7 @@ impl Ribbon {
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
Some(
|
||||
mouse_area(positioned)
|
||||
.on_press(Message::CloseRibbonDropdown)
|
||||
.into(),
|
||||
)
|
||||
Some(dropdown_backdrop(positioned.into()))
|
||||
}
|
||||
|
||||
fn layer_combo_overlay(&self) -> Option<Element<'_, Message>> {
|
||||
@@ -774,7 +776,12 @@ impl Ribbon {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let panel = container(column(rows))
|
||||
// Cap the panel height and make the list scrollable so a long layer
|
||||
// list stays reachable instead of running off the bottom of the
|
||||
// screen (#227). Short lists shrink to fit; longer ones scroll.
|
||||
let row_count = self.layer_infos.len().max(1);
|
||||
let list_h = (row_count as f32 * 26.0).min(420.0);
|
||||
let panel = container(scrollable(column(rows)).height(Length::Fixed(list_h)))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
@@ -800,11 +807,7 @@ impl Ribbon {
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
Some(
|
||||
mouse_area(positioned)
|
||||
.on_press(Message::CloseRibbonDropdown)
|
||||
.into(),
|
||||
)
|
||||
Some(dropdown_backdrop(positioned.into()))
|
||||
}
|
||||
|
||||
/// Floating popup for an annotate-tab style combo (text / dimension /
|
||||
@@ -927,11 +930,7 @@ impl Ribbon {
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
Some(
|
||||
mouse_area(positioned)
|
||||
.on_press(Message::CloseRibbonDropdown)
|
||||
.into(),
|
||||
)
|
||||
Some(dropdown_backdrop(positioned.into()))
|
||||
}
|
||||
|
||||
fn prop_color_overlay(&self) -> Option<Element<'_, Message>> {
|
||||
@@ -970,11 +969,7 @@ impl Ribbon {
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
Some(
|
||||
mouse_area(positioned)
|
||||
.on_press(Message::CloseRibbonDropdown)
|
||||
.into(),
|
||||
)
|
||||
Some(dropdown_backdrop(positioned.into()))
|
||||
}
|
||||
|
||||
fn prop_linetype_overlay(&self) -> Option<Element<'_, Message>> {
|
||||
@@ -1063,11 +1058,7 @@ impl Ribbon {
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
Some(
|
||||
mouse_area(positioned)
|
||||
.on_press(Message::CloseRibbonDropdown)
|
||||
.into(),
|
||||
)
|
||||
Some(dropdown_backdrop(positioned.into()))
|
||||
}
|
||||
|
||||
fn prop_lw_overlay(&self) -> Option<Element<'_, Message>> {
|
||||
@@ -1143,11 +1134,7 @@ impl Ribbon {
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
Some(
|
||||
mouse_area(positioned)
|
||||
.on_press(Message::CloseRibbonDropdown)
|
||||
.into(),
|
||||
)
|
||||
Some(dropdown_backdrop(positioned.into()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user