A greyed-out option in a status-bar menu made the mouse pointer vanish. A disabled button reports no mouse interaction at all, and a menu is an overlay: when the overlay reports none, the cursor is decided by whatever lies beneath it, which over the drawing is the canvas hiding the pointer so it can draw the crosshair itself. Enabled rows were fine only because they ask for a pointer and so never fall through. Every row now answers for the cursor over it, doing something or not. `mouse_area` fills in only where the content stays silent, so an enabled row keeps the pointer it asked for. Fixed for every status-bar menu rather than the one that showed it: they all build their rows through the same place, and the next menu with a disabled row would have had the same hole. Closes #684 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
98 lines
3.8 KiB
Rust
98 lines
3.8 KiB
Rust
//! Shared `iced_aw::MenuBar` plumbing for status-bar menus.
|
|
|
|
use iced::{Background, Border, Color, Element, Length, Shadow, Theme};
|
|
use iced_aw::menu::{DrawPath, Item, Menu, MenuBar};
|
|
|
|
use crate::app::Message;
|
|
|
|
/// One row in a status-bar menu.
|
|
pub struct Entry<'a> {
|
|
content: Element<'a, Message>,
|
|
close_on_click: bool,
|
|
}
|
|
|
|
impl<'a> Entry<'a> {
|
|
/// Keep the menu open after this row is clicked.
|
|
pub fn stay(content: impl Into<Element<'a, Message>>) -> Self {
|
|
Self {
|
|
content: content.into(),
|
|
close_on_click: false,
|
|
}
|
|
}
|
|
|
|
/// Close the menu after this row is clicked.
|
|
pub fn close(content: impl Into<Element<'a, Message>>) -> Self {
|
|
Self {
|
|
content: content.into(),
|
|
close_on_click: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Attach a menu to `root`. Each status-bar menu uses one root so the existing
|
|
/// wrapping layout can still move pills independently between rows.
|
|
pub fn menu_bar<'a>(
|
|
root: impl Into<Element<'a, Message>>,
|
|
entries: Vec<Entry<'a>>,
|
|
width: f32,
|
|
) -> Element<'a, Message> {
|
|
let items = entries
|
|
.into_iter()
|
|
.map(|entry| {
|
|
// Every row answers for the cursor over it, even one that does
|
|
// nothing. A disabled button reports no interaction at all, and the
|
|
// menu is an overlay: when the overlay reports none, the cursor is
|
|
// decided by what lies beneath, which over the drawing is the
|
|
// canvas hiding it for the crosshair. Hovering a greyed-out option
|
|
// therefore made the pointer vanish. `mouse_area` only fills in
|
|
// where the content stays silent, so an enabled row keeps its own
|
|
// pointer. (#684)
|
|
let content = iced::widget::mouse_area(entry.content)
|
|
.interaction(iced::mouse::Interaction::Idle);
|
|
Item::new(content).close_on_click(entry.close_on_click)
|
|
})
|
|
.collect();
|
|
let menu = Menu::new(items)
|
|
.width(Length::Fixed(width))
|
|
.padding(0)
|
|
.spacing(0)
|
|
.offset(1.0)
|
|
.close_on_background_click(true);
|
|
|
|
MenuBar::new(vec![Item::with_menu(root, menu)])
|
|
// The cursor may leave the menu by this much before it closes. At zero
|
|
// the safe area was the menu's own rectangle, and these menus hang off
|
|
// a pill barely wider than its icon: reaching for a row's text meant
|
|
// moving up and across at once, and the diagonal left the rectangle
|
|
// between the pill and the menu. One row of the bar the menu belongs to
|
|
// is enough room for that diagonal, in every direction. (#682)
|
|
.safe_bounds_margin(super::ROW_HEIGHT)
|
|
.close_on_background_click_global(true)
|
|
.draw_path(DrawPath::Backdrop)
|
|
.style(|theme: &Theme, _| {
|
|
let palette = theme.palette();
|
|
iced_aw::style::menu_bar::Style {
|
|
bar_background: Background::Color(Color::TRANSPARENT),
|
|
bar_border: Border::default(),
|
|
bar_shadow: Shadow::default(),
|
|
menu_background: Background::Color(palette.background.weakest.color),
|
|
menu_border: Border {
|
|
color: palette.background.neutral.color,
|
|
width: 1.0,
|
|
radius: 3.0.into(),
|
|
},
|
|
menu_shadow: Shadow {
|
|
color: palette.background.strongest.color.scale_alpha(0.35),
|
|
offset: iced::Vector::new(0.0, -2.0),
|
|
blur_radius: 6.0,
|
|
},
|
|
path: Background::Color(palette.primary.weak.color),
|
|
path_border: Border {
|
|
color: palette.primary.base.color,
|
|
width: 1.0,
|
|
radius: 2.0.into(),
|
|
},
|
|
}
|
|
})
|
|
.into()
|
|
}
|