diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 76798104..f361512d 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -25,6 +25,7 @@ pub enum Button { IconVertical, Image, Link, + LinkActive, ListItem([f32; 4]), MenuFolder, MenuItem, @@ -121,6 +122,13 @@ pub fn appearance( corner_radii = &cosmic.corner_radii.radius_0; } + Button::LinkActive => { + appearance.background = Some(Background::Color(cosmic.text_button.hover.into())); + appearance.icon_color = Some(cosmic.accent_text_color().into()); + appearance.text_color = Some(cosmic.accent_text_color().into()); + corner_radii = &cosmic.corner_radii.radius_xs; + } + Button::Custom { .. } => (), Button::AppletMenu => { let (background, _, _) = color(&cosmic.text_button); diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index b5cf6837..b4665f8f 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -34,6 +34,8 @@ pub fn context_menu<'a, Message: 'static + Clone>( close_on_escape: true, window_id: window::Id::RESERVED, item_width: ItemWidth::Uniform(240), + on_open: None, + on_close: None, on_surface_action: None, }; @@ -56,12 +58,31 @@ pub struct ContextMenu<'a, Message> { pub close_on_escape: bool, /// Width of each menu item, and therefore of the menu. pub item_width: ItemWidth, + /// Emitted when the menu opens, so the application can mark what was right-clicked. + #[setters(strip_option)] + pub on_open: Option, + /// Emitted when the menu closes by any path, including the compositor dismissing it. + #[setters(strip_option)] + pub on_close: Option, #[setters(skip)] pub(crate) on_surface_action: Option Message + Send + Sync + 'static>>, } impl ContextMenu<'_, Message> { + /// Publish `on_open`/`on_close` when the open state changed since the last report. + fn report_open_state(&self, state: &mut LocalState, shell: &mut iced_core::Shell<'_, Message>) { + let open = state.menu_bar_state.inner.with_data(|d| d.open); + if open == state.reported_open { + return; + } + state.reported_open = open; + let message = if open { &self.on_open } else { &self.on_close }; + if let Some(message) = message.clone() { + shell.publish(message); + } + } + #[cfg(wayland_platform)] #[allow(clippy::too_many_lines)] fn create_popup( @@ -238,6 +259,7 @@ impl Widget context_cursor: Point::default(), fingers_pressed: Default::default(), menu_bar_state: Default::default(), + reported_open: false, }) } @@ -351,6 +373,20 @@ impl Widget let state = tree.state.downcast_mut::(); let bounds = layout.bounds(); + // The compositor dismissed our popup: nothing else tells this state about it. + #[cfg(wayland_platform)] + if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland( + iced::event::wayland::Event::Popup(iced::event::wayland::PopupEvent::Done, _, popup), + )) = event + { + state.menu_bar_state.inner.with_data_mut(|d| { + if d.popup_id.get(&self.window_id) == Some(popup) { + d.popup_id.remove(&self.window_id); + d.reset(); + } + }); + } + // XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open. let reset = self.window_id != window::Id::NONE && state @@ -432,6 +468,7 @@ impl Widget shell.request_redraw(); shell.capture_event(); + self.report_open_state(tree.state.downcast_mut::(), shell); return; } else if !was_open && right_button_released(event) || (touch_lifted(event)) @@ -467,6 +504,7 @@ impl Widget shell, viewport, ); + self.report_open_state(tree.state.downcast_mut::(), shell); } fn overlay<'b>( @@ -565,4 +603,5 @@ pub struct LocalState { context_cursor: Point, fingers_pressed: HashSet, menu_bar_state: MenuBarState, + reported_open: bool, } diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index ba2a8991..f82a46e8 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -593,6 +593,21 @@ where let my_state = tree.state.downcast_mut::(); + // The compositor dismissed our popup: nothing else tells this state about it. + #[cfg(wayland_platform)] + if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland( + iced::event::wayland::Event::Popup(iced::event::wayland::PopupEvent::Done, _, popup), + )) = event + { + my_state.inner.with_data_mut(|d| { + if d.popup_id.get(&self.window_id) == Some(popup) { + // submenus were dismissed with it + d.popup_id.clear(); + d.reset(); + } + }); + } + // XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open. let reset = self.window_id != window::Id::NONE && my_state diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index ea1c1a31..4abf873a 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -1244,6 +1244,29 @@ where let my_bounds = layout.bounds(); let state = tree.state.downcast_mut::(); + // The compositor dismissed our context menu popup: nothing else tells this state about it. + #[cfg(wayland_platform)] + if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland( + iced::event::wayland::Event::Popup(iced::event::wayland::PopupEvent::Done, _, popup), + )) = &event + { + let dismissed = state.menu_state.inner.with_data_mut(|data| { + if data.popup_id.get(&self.window_id) == Some(popup) { + data.popup_id.clear(); + data.reset(); + true + } else { + false + } + }); + if dismissed { + state.show_context = None; + for key in self.model.order.iter().copied() { + self.update_entity_paragraph(state, key); + } + } + } + let hovered_before = state.hovered; let my_id = self.get_drag_id();