From 784e13b6acd1981e909d918dff6a90f291d7d7a5 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Wed, 27 May 2026 10:05:07 -0500 Subject: [PATCH] [fix] Right click menu in service and library --- src/main.rs | 97 +++++++++++++++++++++++++++++++---------------- src/ui/library.rs | 75 ++++++++++++++++++++++++------------ 2 files changed, 115 insertions(+), 57 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4f9a284..d7ec617 100755 --- a/src/main.rs +++ b/src/main.rs @@ -13,8 +13,8 @@ use cosmic::iced::keyboard::{Key, Modifiers}; use cosmic::iced::widget::{column, row, stack}; use cosmic::iced::window::Position; use cosmic::iced::{ - self, Background as IcedBackground, Border, Color, Length, Subscription, event, - window, + self, Background as IcedBackground, Border, Color, Length, Point, Subscription, + event, window, }; use cosmic::widget::dnd_destination::dnd_destination; use cosmic::widget::image::Handle; @@ -24,8 +24,8 @@ use cosmic::widget::nav_bar::nav_bar_style; use cosmic::widget::space::{self, horizontal}; use cosmic::widget::{ Container, Space, button, container, context_menu, divider, icon, menu, mouse_area, - nav_bar, nav_bar_toggle, responsive, scrollable, search_input, settings, slider, - text, text_input, tooltip, + nav_bar, nav_bar_toggle, popover, responsive, scrollable, search_input, settings, + slider, text, text_input, tooltip, }; use cosmic::{ Application, ApplicationExt, Apply, Element, cosmic_config, executor, theme, @@ -211,6 +211,8 @@ struct App { obs_connection: String, view_mode: ViewMode, genius_token_hidden: bool, + hovered_point: iced::Point, + context_point: iced::Point, } #[allow(dead_code)] @@ -247,7 +249,7 @@ enum Message { AppendServiceItem(ServiceItem), AppendServiceItemKind(ServiceItemKind), ReorderService(usize, usize), - ContextMenuItem(usize), + ContextMenuItem(Option), SearchFocus, Search(String), CloseSearch, @@ -273,6 +275,7 @@ enum Message { InsertThumbnail((iced::core::image::Allocation, usize)), ClearFooterMsg, SavingReport, + ContextPoint(Point), } #[allow(dead_code)] @@ -464,6 +467,8 @@ impl cosmic::Application for App { hovered_item: None, hovered_dnd: None, context_menu: None, + hovered_point: Point::ORIGIN, + context_point: Point::ORIGIN, modifiers_pressed: None, settings_open: false, settings, @@ -1434,7 +1439,12 @@ impl cosmic::Application for App { Task::none() } Message::ContextMenuItem(index) => { - self.context_menu = Some(index); + self.context_menu = index; + self.context_point = self.hovered_point; + Task::none() + } + Message::ContextPoint(point) => { + self.hovered_point = point; Task::none() } Message::AddServiceItemDrop(index) => { @@ -2172,21 +2182,41 @@ where } (Key::Character(k), _) if k == *"/" => self.update(Message::SearchFocus), (Key::Named(iced::keyboard::key::Named::ArrowRight), _) => { - self.update(Message::Present(presenter::Message::NextSlide)) + if self.editor_mode.is_none() { + self.update(Message::Present(presenter::Message::NextSlide)) + } else { + Task::none() + } } (Key::Named(iced::keyboard::key::Named::ArrowLeft), _) => { - self.update(Message::Present(presenter::Message::PrevSlide)) + if self.editor_mode.is_none() { + self.update(Message::Present(presenter::Message::PrevSlide)) + } else { + Task::none() + } } (Key::Character(k), _) if k == *" " => { - self.update(Message::Present(presenter::Message::NextSlide)) + if self.editor_mode.is_none() { + self.update(Message::Present(presenter::Message::NextSlide)) + } else { + Task::none() + } } (Key::Character(k), _) if k == *"j" || k == *"l" => { - self.update(Message::Present(presenter::Message::NextSlide)) + if self.editor_mode.is_none() { + self.update(Message::Present(presenter::Message::NextSlide)) + } else { + Task::none() + } } (Key::Character(k), _) if k == *"k" || k == *"h" => { - self.update(Message::Present(presenter::Message::PrevSlide)) + if self.editor_mode.is_none() { + self.update(Message::Present(presenter::Message::PrevSlide)) + } else { + Task::none() + } } - (Key::Character(k), _) if k == *"q" => self.update(Message::Quit), + // (Key::Character(k), _) if k == *"q" => self.update(Message::Quit), _ => Task::none(), } } @@ -2251,32 +2281,35 @@ where container }; let mouse_area = mouse_area(visual_item) + .on_move(|point| Message::ContextPoint(point)) .on_enter(Message::HoveredServiceItem(Some(index))) .on_exit(Message::HoveredServiceItem(None)) .on_double_press(Message::ChangeServiceItem(index)) - .on_right_press(Message::ContextMenuItem(index)) + .on_right_press(Message::ContextMenuItem(Some(index))) .on_release(Message::SelectServiceItem(index)); let single_item = if let Some(context_menu_item) = self.context_menu { + let menu_item = |label, message| { + menu::menu_button(vec![ + text(label).into(), + space::horizontal().into(), + ]) + .on_press(message) + }; + let delete_button: Element = + menu_item("Delete", Message::RemoveServiceItem(index)).into(); + + let menu = column![delete_button] + .spacing(theme::spacing().space_s) + .apply(cosmic::widget::container) + .width(300) + .padding(theme::spacing().space_s) + .class(theme::Container::Dropdown); + if context_menu_item == index { - let context_menu = context_menu( - mouse_area, - self.context_menu.map_or_else( - || None, - |i| { - if i == index { - let menu = vec![menu::Item::Button( - "Delete", - None, - MenuAction::DeleteItem(index), - )]; - Some(menu::items(&HashMap::new(), menu)) - } else { - None - } - }, - ), - ) - .close_on_escape(true); + let context_menu = popover(mouse_area) + .position(popover::Position::Point(self.context_point)) + .on_close(Message::ContextMenuItem(None)) + .popup(menu); Element::from(context_menu) } else { Element::from(mouse_area) diff --git a/src/ui/library.rs b/src/ui/library.rs index f44ce7e..50e0f3f 100644 --- a/src/ui/library.rs +++ b/src/ui/library.rs @@ -9,14 +9,14 @@ use cosmic::iced::core::text::{Ellipsize, EllipsizeHeightLimit}; use cosmic::iced::core::widget::tree::State as TreeState; use cosmic::iced::keyboard::Modifiers; use cosmic::iced::widget::{column, row as rowm, text as textm}; -use cosmic::iced::{Background, Border, Color, Length}; +use cosmic::iced::{Background, Border, Color, Length, Point}; use cosmic::widget::menu::{self, Action as MenuAction}; use cosmic::widget::nav_bar::nav_bar_style; use cosmic::widget::space::{self, horizontal}; use cosmic::widget::{ Container, DndSource, Space, button, container, context_menu, divider, - dnd_destination, icon, indeterminate_circular, mouse_area, row, scrollable, text, - text_input, + dnd_destination, icon, indeterminate_circular, mouse_area, popover, row, scrollable, + text, text_input, }; use cosmic::{Apply, Element, Task, theme}; use itertools::Itertools; @@ -53,6 +53,8 @@ pub struct Library { modifiers_pressed: Option, state: State, video_popup_input: String, + hovered_point: cosmic::iced::Point, + context_point: cosmic::iced::Point, } #[derive(Debug, Clone, PartialEq)] @@ -108,7 +110,7 @@ pub enum Message { UpdatePresentation(Presentation), PresentationChanged, Error(String), - OpenContext(i32), + OpenContext(Option), None, AddFiles(Vec), ReaddSongs(Vec), @@ -124,6 +126,7 @@ pub enum Message { AddSongFromEditor(Song), PopupUpdate(String), PopupSearch(String), + HoverPoint(cosmic::iced::Point), } impl<'a> Library { @@ -148,6 +151,8 @@ impl<'a> Library { modifiers_pressed: None, state: State::Idle, video_popup_input: String::new(), + hovered_point: Point::ORIGIN, + context_point: Point::ORIGIN, } } @@ -526,10 +531,14 @@ impl<'a> Library { } Message::PresentationChanged => (), Message::Error(_) => (), - Message::OpenContext(index) => { + Message::OpenContext(None) => { + self.context_menu = None; + } + Message::OpenContext(Some(index)) => { let Some(kind) = self.library_open else { return Action::None; }; + self.context_point = self.hovered_point; debug!(index, "should context"); let Some(items) = self.selected_items.as_mut() else { self.selected_items = vec![(kind, index)].into(); @@ -547,6 +556,7 @@ impl<'a> Library { self.context_menu = Some(index); } Message::AddFiles(items) => return self.add_files(items), + Message::HoverPoint(point) => self.hovered_point = point, Message::PopupUpdate(_) => todo!(), Message::PopupSearch(_) => todo!(), } @@ -707,16 +717,17 @@ impl<'a> Library { let kind = model.kind; let visual_item = self.single_item(index, item, model); - DndSource::::new({ + let item = DndSource::::new({ let mouse_area = mouse_area(visual_item); let mouse_area = mouse_area + .on_move(|point| Message::HoverPoint(point)) .on_enter(Message::HoverItem(Some(( model.kind, i32_index, )))) .on_double_click(Message::OpenItem(Some(( model.kind, i32_index, )))) - .on_right_press(Message::OpenContext(i32_index)) + .on_right_press(Message::OpenContext(Some(i32_index))) .on_exit(Message::HoverItem(None)) .on_press(Message::SelectItem(Some(( model.kind, i32_index, @@ -747,8 +758,9 @@ impl<'a> Library { (icon.into(), state, i) } }) - .drag_content(move || KindWrapper((kind, i32_index))) - .into() + .drag_content(move || KindWrapper((kind, i32_index))); + + self.context_menu(item.into(), kind, i32_index).into() }) }) .spacing(2) @@ -764,8 +776,7 @@ impl<'a> Library { .on_press(Message::AddItem) ) .align_y(Vertical::Center); - let context_menu = self.context_menu(items.into()); - let library_column = column![library_toolbar, context_menu].spacing(3); + let library_column = column![library_toolbar, items].spacing(3); Container::new(library_column).padding(5) } else { Container::new(Space::new()) @@ -925,22 +936,36 @@ impl<'a> Library { .into() } - fn context_menu<'b>(&self, items: Element<'b, Message>) -> Element<'b, Message> { - if self.context_menu.is_some() { - let menu_items = vec![ - menu::Item::Button("Open", None, MenuMessage::Open), - menu::Item::Button("Delete", None, MenuMessage::Delete), - ]; - let context_menu = context_menu( - items, - self.context_menu.map_or_else( - || None, - |_| Some(menu::items(&self.menu_keys, menu_items)), - ), - ); + fn context_menu<'b>( + &self, + item: Element<'b, Message>, + library: LibraryKind, + id: i32, + ) -> Element<'b, Message> { + if self.context_menu.is_some_and(|index| index == id) + && self.library_open.is_some_and(|kind| kind == library) + { + let menu_item = |label, message| { + menu::menu_button(vec![text(label).into(), space::horizontal().into()]) + .on_press(message) + }; + + let menu_items = column![ + menu_item("Open", Message::OpenItem(Some((library, id)))), + menu_item("Delete", Message::DeleteItem), + ] + .spacing(theme::spacing().space_s) + .apply(container) + .width(300) + .padding(theme::spacing().space_s) + .class(theme::Container::Dropdown); + let context_menu = popover(item) + .position(popover::Position::Point(self.context_point)) + .on_close(Message::OpenContext(None)) + .popup(menu_items); Element::from(context_menu) } else { - items + item } }