diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 1396b5cb..e51b4f6a 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -48,23 +48,12 @@ fn get_state_dir() -> Option { return Some(PathBuf::from(home).join(".local").join("state")); } } - dirs::state_dir() -} - -/// Get the data directory, with Flatpak sandbox support. -fn get_data_dir() -> Option { - // Check if we're running in Flatpak - if env::var_os("FLATPAK_ID").is_some() { - // Try HOST_XDG_DATA_HOME first - if let Some(host_data) = env::var_os("HOST_XDG_DATA_HOME") { - return Some(PathBuf::from(host_data)); - } - // Fallback: try to construct from HOME - if let Some(home) = env::var_os("HOME") { - return Some(PathBuf::from(home).join(".local").join("share")); - } + if let Some(state_dir) = dirs::state_dir() { + return Some(state_dir); } - dirs::data_dir() + // On platforms without state_dir, fall back to ~/.local/state + let home_dir = dirs::home_dir()?; + Some(home_dir.join(".local").join(".state")) } #[cfg(feature = "subscription")] @@ -327,40 +316,6 @@ impl Config { }) } - /// Get data for the given application name and config version. - /// pub fn new_state(name: &str, version: u64) -> Result { - pub fn new_data(name: &str, version: u64) -> Result { - Self::new_data_inner(name, version, true) - } - - pub fn new_data_inner( - name: &str, - version: u64, - look_for_previous: bool, - ) -> Result { - // Look for [name]/v[version] - let path = sanitize_name(name)?.join(format!("v{}", version)); - - // Get libcosmic user data directory - let mut user_path = get_data_dir().ok_or(Error::NoConfigDirectory)?; - user_path.push("cosmic"); - user_path.push(path); - // Create new data directory if not found. - fs::create_dir_all(&user_path)?; - - Ok(Self { - system_path: None, - user_path: Some(user_path), - previous: if version > 1 && look_for_previous { - Self::new_data_inner(name, version - 1, false) - .ok() - .map(Box::new) - } else { - None - }, - }) - } - // Start a transaction (to set multiple configs at the same time) #[inline] pub fn transaction(&self) -> ConfigTransaction<'_> { diff --git a/examples/config/src/main.rs b/examples/config/src/main.rs index dfcc5b99..f6fb5c0d 100644 --- a/examples/config/src/main.rs +++ b/examples/config/src/main.rs @@ -88,7 +88,4 @@ pub fn main() { println!("Testing state"); test_config(Config::new_state("com.system76.Example", 1).unwrap()); - - println!("Testing data"); - test_config(Config::new_data("com.system76.Example", 1).unwrap()); } diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index e5ca5878..50c76bfb 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -30,6 +30,7 @@ pub enum Message { WindowClose, Surface(cosmic::surface::Action), ToggleHideContent, + ToggleSomeAction, WindowNew, } @@ -82,6 +83,7 @@ impl cosmic::Application for App { /// Handle application events here. fn update(&mut self, message: Self::Message) -> Task { + tracing::error!("Clicked {message:?}"); match message { Message::Clicked => { self.button_label = format!("Clicked {message:?}"); @@ -91,9 +93,10 @@ impl cosmic::Application for App { cosmic::app::Action::Surface(action), )); } - Message::WindowClose => {} - Message::ToggleHideContent => {} - Message::WindowNew => {} + Message::WindowClose + | Message::ToggleHideContent + | Message::ToggleSomeAction + | Message::WindowNew => {} } Task::none() @@ -102,7 +105,7 @@ impl cosmic::Application for App { /// Creates a view after each update. fn view(&self) -> Element<'_, Self::Message> { let widget = cosmic::widget::context_menu( - cosmic::widget::button::text(self.button_label.to_string()).on_press(Message::Clicked), + cosmic::widget::button::text(self.button_label.clone()).on_press(Message::Clicked), self.context_menu(), ) .on_surface_action(Message::Surface); @@ -126,12 +129,20 @@ impl App { menu::Item::Divider, menu::Item::Folder( "View", - vec![menu::Item::CheckBox( - "Hide content", - None, - self.hide_content, - ContextMenuAction::ToggleHideContent, - )], + vec![ + menu::Item::CheckBox( + "Hide content", + None, + self.hide_content, + ContextMenuAction::ToggleHideContent, + ), + menu::Item::CheckBox( + "Test content", + None, + self.hide_content, + ContextMenuAction::ToggleSomeAction, + ), + ], ), menu::Item::Divider, menu::Item::Button("Quit", None, ContextMenuAction::WindowClose), @@ -144,6 +155,7 @@ impl App { pub enum ContextMenuAction { WindowClose, ToggleHideContent, + ToggleSomeAction, WindowNew, } @@ -153,6 +165,7 @@ impl menu::Action for ContextMenuAction { match self { ContextMenuAction::WindowClose => Message::WindowClose, ContextMenuAction::ToggleHideContent => Message::ToggleHideContent, + ContextMenuAction::ToggleSomeAction => Message::ToggleSomeAction, ContextMenuAction::WindowNew => Message::WindowNew, } } diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index 900d0322..35933b42 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -9,7 +9,7 @@ use std::{env, process}; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::keyboard::Key; -use cosmic::iced::{window, Length, Size}; +use cosmic::iced::{event, keyboard, window, Event, Length, Size, Subscription}; use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::key_bind::{KeyBind, Modifier}; use cosmic::widget::menu::{self, ItemHeight, ItemWidth}; @@ -112,6 +112,26 @@ impl cosmic::Application for App { vec![menu_bar(&self.config, &self.key_binds)] } + fn subscription(&self) -> Subscription { + event::listen_with(|event, _status, _window_id| { + let Event::Keyboard(keyboard::Event::KeyPressed { + key, + modifiers, + physical_key, + .. + }) = event + else { + return None; + }; + + let key_binds = key_binds(); + key_binds + .iter() + .find(|(key_bind, _)| key_bind.matches(modifiers, &key, Some(&physical_key))) + .map(|(_, action)| action.message()) + }) + } + /// Handle application events here. fn update(&mut self, message: Self::Message) -> Task { match message { diff --git a/iced b/iced index c34af044..e891f34b 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit c34af044311c986e5f8458cf61bab2508aca48fa +Subproject commit e891f34b3224b25fc4bdf286b3f9dba26c8abe5b diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 72dff7d4..50dc9b64 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -634,6 +634,7 @@ where return; } shell.capture_event(); + shell.request_redraw(); #[cfg(wayland_platform)] if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { self.create_popup(layout, view_cursor, renderer, shell, viewport, my_state); diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 2466d206..0bc3f6cf 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -650,12 +650,13 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> { state.pressed = false; // process close condition - if state - .view_cursor - .position() - .unwrap_or_default() - .distance(view_cursor.position().unwrap_or_default()) - < 2.0 + if state.open + && state + .view_cursor + .position() + .unwrap_or_default() + .distance(view_cursor.position().unwrap_or_default()) + < 2.0 { let is_inside = state.menu_states[..=if self.is_overlay { state.active_root.len().saturating_sub(1) @@ -1428,47 +1429,48 @@ where state.view_cursor = view_cursor; // * remove invalid menus + if state.open { + let mut prev_bounds = std::iter::once(menu.bar_bounds) + .chain( + if menu.is_overlay { + state.menu_states[..state.menu_states.len().saturating_sub(1)].iter() + } else { + state.menu_states[..menu.depth].iter() + } + .map(|s| s.menu_bounds.children_bounds), + ) + .collect::>(); - let mut prev_bounds = std::iter::once(menu.bar_bounds) - .chain( - if menu.is_overlay { - state.menu_states[..state.menu_states.len().saturating_sub(1)].iter() - } else { - state.menu_states[..menu.depth].iter() + if menu.is_overlay && menu.close_condition.leave { + for i in (0..state.menu_states.len()).rev() { + let mb = &state.menu_states[i].menu_bounds; + + if mb.parent_bounds.contains(overlay_cursor) + || menu.is_overlay && mb.children_bounds.contains(overlay_cursor) + || mb.offset_bounds.contains(overlay_cursor) + || (mb.check_bounds.contains(overlay_cursor) + && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))) + { + break; + } + prev_bounds.pop(); + state.active_root.pop(); + state.menu_states.pop(); } - .map(|s| s.menu_bounds.children_bounds), - ) - .collect::>(); + } else if menu.is_overlay { + for i in (0..state.menu_states.len()).rev() { + let mb = &state.menu_states[i].menu_bounds; - if menu.is_overlay && menu.close_condition.leave { - for i in (0..state.menu_states.len()).rev() { - let mb = &state.menu_states[i].menu_bounds; - - if mb.parent_bounds.contains(overlay_cursor) - || menu.is_overlay && mb.children_bounds.contains(overlay_cursor) - || mb.offset_bounds.contains(overlay_cursor) - || (mb.check_bounds.contains(overlay_cursor) - && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))) - { - break; + if mb.parent_bounds.contains(overlay_cursor) + || mb.children_bounds.contains(overlay_cursor) + || prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor)) + { + break; + } + prev_bounds.pop(); + state.active_root.pop(); + state.menu_states.pop(); } - prev_bounds.pop(); - state.active_root.pop(); - state.menu_states.pop(); - } - } else if menu.is_overlay { - for i in (0..state.menu_states.len()).rev() { - let mb = &state.menu_states[i].menu_bounds; - - if mb.parent_bounds.contains(overlay_cursor) - || mb.children_bounds.contains(overlay_cursor) - || prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor)) - { - break; - } - prev_bounds.pop(); - state.active_root.pop(); - state.menu_states.pop(); } } diff --git a/src/widget/popover.rs b/src/widget/popover.rs index fb904248..b9704244 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -24,6 +24,7 @@ pub enum Position { #[default] Center, Bottom, + Top, Point(Point), } @@ -259,6 +260,7 @@ where Position::Point(relative) => { bounds.position() + Vector::new(relative.x, relative.y) } + Position::Top => Point::new(bounds.x + bounds.width / 2.0, bounds.y), }; // Round position to prevent rendering issues @@ -353,6 +355,12 @@ where position.x = (position.x - width / 2.0).clamp(0.0, bounds.width - width); position.y = (position.y - height / 2.0).clamp(0.0, bounds.height - height); } + Position::Top => { + let width = node.size().width; + let height = node.size().height; + position.x = (position.x - width / 2.0).clamp(0.0, bounds.width - width); + position.y = (position.y - height).clamp(0.0, bounds.height - height); + } Position::Bottom => { // Position is set to the center bottom of the widget let width = node.size().width; diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index c5295562..ed90328f 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -1925,6 +1925,7 @@ where .. }) = event { + shell.request_redraw(); state.focused_visible = true; return if *modifiers == keyboard::Modifiers::SHIFT { self.focus_previous(state, shell);