refactor(menu): fallback behavior for non wayland windowing system

This commit is contained in:
Ashley Wulber 2025-06-12 11:40:50 -04:00 committed by Jeremy Soller
parent ba72aed6fb
commit 00ba16fe01
7 changed files with 268 additions and 160 deletions

View file

@ -120,6 +120,7 @@ libc = { version = "0.2.171", optional = true }
license = { version = "3.6.0", optional = true } license = { version = "3.6.0", optional = true }
mime = { version = "0.3.17", optional = true } mime = { version = "0.3.17", optional = true }
palette = "0.7.6" palette = "0.7.6"
raw-window-handle = "0.6"
rfd = { version = "0.15.3", default-features = false, features = [ rfd = { version = "0.15.3", default-features = false, features = [
"xdg-portal", "xdg-portal",
], optional = true } ], optional = true }

2
iced

@ -1 +1 @@
Subproject commit fc9d49eb2f830fe394252ff6799d59ad828243bc Subproject commit 2d1511d0cf0296e6f0cfcfcd13f2a1aa334c6915

View file

@ -36,6 +36,8 @@ pub enum Action {
NavBar(nav_bar::Id), NavBar(nav_bar::Id),
/// Activates a context menu for an item from the nav bar. /// Activates a context menu for an item from the nav bar.
NavBarContext(nav_bar::Id), NavBarContext(nav_bar::Id),
/// A new window was opened.
Opened(iced::window::Id),
/// Set scaling factor /// Set scaling factor
ScaleFactor(f32), ScaleFactor(f32),
/// Show the window menu /// Show the window menu
@ -60,6 +62,8 @@ pub enum Action {
ToolkitConfig(CosmicTk), ToolkitConfig(CosmicTk),
/// Window focus lost /// Window focus lost
Unfocus(iced::window::Id), Unfocus(iced::window::Id),
/// Windowing system initialized
WindowingSystemInitialized,
/// Updates the window maximized state /// Updates the window maximized state
WindowMaximized(iced::window::Id, bool), WindowMaximized(iced::window::Id, bool),
/// Updates the tracked window geometry. /// Updates the tracked window geometry.

View file

@ -19,6 +19,65 @@ use iced::{Task, window};
use iced_futures::event::listen_with; use iced_futures::event::listen_with;
use palette::color_difference::EuclideanDistance; use palette::color_difference::EuclideanDistance;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum WindowingSystem {
UiKit,
AppKit,
Orbital,
OhosNdk,
Xlib,
Xcb,
Wayland,
Drm,
Gbm,
Win32,
WinRt,
Web,
WebCanvas,
WebOffscreenCanvas,
AndroidNdk,
Haiku,
}
pub(crate) static WINDOWING_SYSTEM: std::sync::OnceLock<WindowingSystem> =
std::sync::OnceLock::new();
pub fn windowing_system() -> Option<WindowingSystem> {
WINDOWING_SYSTEM.get().copied()
}
fn init_windowing_system<M>(handle: raw_window_handle::WindowHandle) -> crate::Action<M> {
let raw: &raw_window_handle::RawWindowHandle = handle.as_ref();
let system = match raw {
window::raw_window_handle::RawWindowHandle::UiKit(_) => WindowingSystem::UiKit,
window::raw_window_handle::RawWindowHandle::AppKit(_) => WindowingSystem::AppKit,
window::raw_window_handle::RawWindowHandle::Orbital(_) => WindowingSystem::Orbital,
window::raw_window_handle::RawWindowHandle::OhosNdk(_) => WindowingSystem::OhosNdk,
window::raw_window_handle::RawWindowHandle::Xlib(_) => WindowingSystem::Xlib,
window::raw_window_handle::RawWindowHandle::Xcb(_) => WindowingSystem::Xcb,
window::raw_window_handle::RawWindowHandle::Wayland(_) => WindowingSystem::Wayland,
window::raw_window_handle::RawWindowHandle::Web(_) => WindowingSystem::Web,
window::raw_window_handle::RawWindowHandle::WebCanvas(_) => WindowingSystem::WebCanvas,
window::raw_window_handle::RawWindowHandle::WebOffscreenCanvas(_) => {
WindowingSystem::WebOffscreenCanvas
}
window::raw_window_handle::RawWindowHandle::AndroidNdk(_) => WindowingSystem::AndroidNdk,
window::raw_window_handle::RawWindowHandle::Haiku(_) => WindowingSystem::Haiku,
window::raw_window_handle::RawWindowHandle::Drm(_) => WindowingSystem::Drm,
window::raw_window_handle::RawWindowHandle::Gbm(_) => WindowingSystem::Gbm,
window::raw_window_handle::RawWindowHandle::Win32(_) => WindowingSystem::Win32,
window::raw_window_handle::RawWindowHandle::WinRt(_) => WindowingSystem::WinRt,
_ => {
tracing::warn!("Unknown windowing system: {raw:?}");
return crate::Action::Cosmic(Action::WindowingSystemInitialized);
}
};
_ = WINDOWING_SYSTEM.set(system);
crate::Action::Cosmic(Action::WindowingSystemInitialized)
}
#[derive(Default)] #[derive(Default)]
pub struct Cosmic<App: Application> { pub struct Cosmic<App: Application> {
pub app: App, pub app: App,
@ -41,10 +100,17 @@ where
use iced_futures::futures::executor::block_on; use iced_futures::futures::executor::block_on;
core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok(); core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok();
} }
let id = core.main_window_id().unwrap_or(window::Id::RESERVED);
let (model, command) = T::init(core, flags); let (model, command) = T::init(core, flags);
(Self::new(model), command) (
Self::new(model),
Task::batch(vec![
command,
iced_runtime::window::run_with_handle(id, init_windowing_system),
]),
)
} }
#[cfg(not(feature = "multi-window"))] #[cfg(not(feature = "multi-window"))]
@ -57,6 +123,7 @@ where
self.app.title(id).to_string() self.app.title(id).to_string()
} }
#[allow(clippy::too_many_lines)]
pub fn surface_update( pub fn surface_update(
&mut self, &mut self,
_surface_message: crate::surface::Action, _surface_message: crate::surface::Action,
@ -255,6 +322,9 @@ where
iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => { iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => {
return Some(Action::WindowResize(id, width, height)); return Some(Action::WindowResize(id, width, height));
} }
iced::Event::Window(window::Event::Opened { .. }) => {
return Some(Action::Opened(id));
}
iced::Event::Window(window::Event::Closed) => { iced::Event::Window(window::Event::Closed) => {
return Some(Action::SurfaceClosed(id)); return Some(Action::SurfaceClosed(id));
} }
@ -786,6 +856,9 @@ impl<T: Application> Cosmic<T> {
let core = self.app.core_mut(); let core = self.app.core_mut();
core.applet.suggested_bounds = b; core.applet.suggested_bounds = b;
} }
Action::Opened(id) => {
return iced_runtime::window::run_with_handle(id, init_windowing_system);
}
_ => {} _ => {}
} }

View file

@ -3,6 +3,8 @@
//! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. //! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation.
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
use crate::widget::menu::{ use crate::widget::menu::{
self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight, self, CloseCondition, Direction, ItemHeight, ItemWidth, MenuBarState, PathHighlight,
init_root_menu, menu_roots_diff, init_root_menu, menu_roots_diff,
@ -361,6 +363,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
feature = "winit", feature = "winit",
feature = "surface-message" feature = "surface-message"
))] ))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
state.menu_bar_state.inner.with_data_mut(|state| { state.menu_bar_state.inner.with_data_mut(|state| {
if let Some(id) = state.popup_id.remove(&self.window_id) { if let Some(id) = state.popup_id.remove(&self.window_id) {
state.menu_states.clear(); state.menu_states.clear();
@ -377,6 +380,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
} }
}); });
} }
}
_ => (), _ => (),
} }
@ -392,7 +396,9 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
state.view_cursor = cursor; state.view_cursor = cursor;
}); });
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
self.create_popup(layout, cursor, renderer, shell, viewport, state); self.create_popup(layout, cursor, renderer, shell, viewport, state);
}
return event::Status::Captured; return event::Status::Captured;
} else if right_button_released(&event) } else if right_button_released(&event)
@ -400,33 +406,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|| left_button_released(&event) || left_button_released(&event)
{ {
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
state.menu_bar_state.inner.with_data_mut(|state| { if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
if let Some(id) = state.popup_id.remove(&self.window_id) {
state.menu_states.clear();
state.active_root.clear();
state.open = false;
{
let surface_action = self.on_surface_action.as_ref().unwrap();
shell
.publish(surface_action(crate::surface::action::destroy_popup(id)));
}
state.view_cursor = cursor;
}
});
}
} else if open {
match event {
Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Right | mouse::Button::Left,
))
| Event::Touch(touch::Event::FingerLifted { .. }) => {
#[cfg(all(
feature = "wayland",
feature = "winit",
feature = "surface-message"
))]
state.menu_bar_state.inner.with_data_mut(|state| { state.menu_bar_state.inner.with_data_mut(|state| {
if let Some(id) = state.popup_id.remove(&self.window_id) { if let Some(id) = state.popup_id.remove(&self.window_id) {
state.menu_states.clear(); state.menu_states.clear();
@ -444,6 +424,37 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
} }
}); });
} }
}
} else if open {
match event {
Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Right | mouse::Button::Left,
))
| Event::Touch(touch::Event::FingerLifted { .. }) => {
#[cfg(all(
feature = "wayland",
feature = "winit",
feature = "surface-message"
))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
state.menu_bar_state.inner.with_data_mut(|state| {
if let Some(id) = state.popup_id.remove(&self.window_id) {
state.menu_states.clear();
state.active_root.clear();
state.open = false;
{
let surface_action = self.on_surface_action.as_ref().unwrap();
shell.publish(surface_action(
crate::surface::action::destroy_popup(id),
));
}
state.view_cursor = cursor;
}
});
}
}
_ => (), _ => (),
} }
} }
@ -468,7 +479,10 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
translation: Vector, translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
&& self.window_id != window::Id::NONE
&& self.on_surface_action.is_some()
{
return None; return None;
} }

View file

@ -9,6 +9,8 @@ use super::{
}, },
menu_tree::MenuTree, menu_tree::MenuTree,
}; };
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
use crate::{ use crate::{
Renderer, Renderer,
style::menu_bar::StyleSheet, style::menu_bar::StyleSheet,
@ -629,14 +631,18 @@ where
return event::Status::Ignored; return event::Status::Ignored;
} }
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state);
} }
}
Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
if open && view_cursor.is_over(layout.bounds()) => if open && view_cursor.is_over(layout.bounds()) =>
{ {
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state);
} }
}
_ => (), _ => (),
} }
@ -710,7 +716,12 @@ where
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
&& self.on_surface_action.is_some()
&& self.window_id != window::Id::NONE
{
return None; return None;
}
let state = tree.state.downcast_ref::<MenuBarState>(); let state = tree.state.downcast_ref::<MenuBarState>();
if state.inner.with_data(|state| !state.open) { if state.inner.with_data(|state| !state.open) {

View file

@ -4,6 +4,8 @@
use std::{borrow::Cow, sync::Arc}; use std::{borrow::Cow, sync::Arc};
use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; use super::{menu_bar::MenuBarState, menu_tree::MenuTree};
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
use crate::style::menu_bar::StyleSheet; use crate::style::menu_bar::StyleSheet;
use iced::window; use iced::window;
@ -665,6 +667,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
feature = "winit", feature = "winit",
feature = "surface-message" feature = "surface-message"
))] ))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
if let Some(handler) = self.on_surface_action.as_ref() { if let Some(handler) = self.on_surface_action.as_ref() {
let mut root = self.window_id; let mut root = self.window_id;
let mut depth = self.depth; let mut depth = self.depth;
@ -678,8 +681,10 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
root = *parent.0; root = *parent.0;
depth = depth.saturating_sub(1); depth = depth.saturating_sub(1);
} }
shell shell.publish((handler)(crate::surface::Action::DestroyPopup(
.publish((handler)(crate::surface::Action::DestroyPopup(root))); root,
)));
}
} }
state.reset(); state.reset();
@ -922,6 +927,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
let (new_root, status) = self.on_event(event, layout, cursor, renderer, clipboard, shell); let (new_root, status) = self.on_event(event, layout, cursor, renderer, clipboard, shell);
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) {
if let Some((new_root, new_ms)) = new_root { if let Some((new_root, new_ms)) = new_root {
use iced_runtime::platform_specific::wayland::popup::{ use iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner, SctkPopupSettings, SctkPositioner,
@ -1059,6 +1065,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
return status; return status;
} }
}
status status
} }
} }
@ -1460,8 +1467,7 @@ where
.is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty()); .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty());
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
{ if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove {
if remove {
if let Some(id) = state.popup_id.remove(&menu.window_id) { if let Some(id) = state.popup_id.remove(&menu.window_id) {
state.active_root.truncate(menu.depth + 1); state.active_root.truncate(menu.depth + 1);
_shell.publish((menu.on_surface_action.as_ref().unwrap())({ _shell.publish((menu.on_surface_action.as_ref().unwrap())({
@ -1469,7 +1475,6 @@ where
})); }));
} }
} }
}
let item = &active_menu[new_index]; let item = &active_menu[new_index];
// set new index // set new index
let old_index = last_menu_state.index.replace(new_index); let old_index = last_menu_state.index.replace(new_index);