Merge pull request #1631 from jasonrhansen/close-context-menu

fix: close context menus in various cases
This commit is contained in:
Jeremy Soller 2026-02-19 14:01:33 -07:00 committed by GitHub
commit f9d4ca4867
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 99 additions and 48 deletions

View file

@ -28,7 +28,7 @@ use cosmic::{
event, event,
futures::{self, SinkExt}, futures::{self, SinkExt},
keyboard::{Event as KeyEvent, Key, Modifiers}, keyboard::{Event as KeyEvent, Key, Modifiers},
stream, mouse, stream,
widget::scrollable, widget::scrollable,
window::{self, Event as WindowEvent, Id as WindowId}, window::{self, Event as WindowEvent, Id as WindowId},
}, },
@ -369,6 +369,7 @@ pub enum Message {
ModifiersChanged(window::Id, Modifiers), ModifiersChanged(window::Id, Modifiers),
MounterItems(MounterKey, MounterItems), MounterItems(MounterKey, MounterItems),
MountResult(MounterKey, MounterItem, Result<bool, String>), MountResult(MounterKey, MounterItem, Result<bool, String>),
Mouse(window::Id, mouse::Button),
MoveTo(Option<Entity>), MoveTo(Option<Entity>),
MoveToResult(DialogResult), MoveToResult(DialogResult),
NavBarClose(Entity), NavBarClose(Entity),
@ -1554,6 +1555,21 @@ impl App {
} }
} }
fn close_context_menus(&mut self) -> Task<Message> {
let active = self.tab_model.active();
if let Some(tab) = self.tab_model.data_mut::<Tab>(active) {
tab.location_context_menu_index = None;
if tab.context_menu.is_some() {
return self.update(Message::TabMessage(
Some(active),
tab::Message::ContextMenu(None, None),
));
}
}
Task::none()
}
fn update_nav_model(&mut self) { fn update_nav_model(&mut self) {
let mut nav_model = segmented_button::ModelBuilder::default(); let mut nav_model = segmented_button::ModelBuilder::default();
@ -2594,11 +2610,12 @@ impl Application for App {
self.set_show_context(false); self.set_show_context(false);
return cosmic::task::message(cosmic::action::app(Message::SetShowDetails(false))); return cosmic::task::message(cosmic::action::app(Message::SetShowDetails(false)));
} }
if self.search_get().is_some() {
// Close search if open
return self.search_set_active(None);
}
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) { if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
if tab.location_context_menu_index.is_some() {
tab.location_context_menu_index = None;
return Task::none();
}
if tab.context_menu.is_some() { if tab.context_menu.is_some() {
return self.update(Message::TabMessage( return self.update(Message::TabMessage(
Some(entity), Some(entity),
@ -2621,6 +2638,11 @@ impl Application for App {
} }
} }
if self.search_get().is_some() {
// Close search if open
return self.search_set_active(None);
}
Task::none() Task::none()
} }
@ -3304,6 +3326,12 @@ impl Application for App {
); );
} }
}, },
Message::Mouse(window_id, _button) => {
// Close context menu when clicking outside.
if self.core.main_window_id() == Some(window_id) {
return self.close_context_menus();
}
}
Message::MoveTo(entity_opt) => { Message::MoveTo(entity_opt) => {
let selected_paths: Box<[_]> = self.selected_paths(entity_opt).collect(); let selected_paths: Box<[_]> = self.selected_paths(entity_opt).collect();
return self.move_to(&selected_paths); return self.move_to(&selected_paths);
@ -4046,14 +4074,18 @@ impl Application for App {
)); ));
} }
Message::SearchActivate => { Message::SearchActivate => {
return if self.search_get().is_none() { let mut tasks = vec![self.close_context_menus()];
self.search_set_active(Some(String::new()))
if self.search_get().is_none() {
tasks.push(self.search_set_active(Some(String::new())));
} else { } else {
widget::text_input::focus(self.search_id.clone()) tasks.push(widget::text_input::focus(self.search_id.clone()));
}; };
return Task::batch(tasks);
} }
Message::SearchClear => { Message::SearchClear => {
return self.search_set_active(None); return Task::batch([self.close_context_menus(), self.search_set_active(None)]);
} }
Message::SearchInput(input) => { Message::SearchInput(input) => {
return self.search_set_active(Some(input)); return self.search_set_active(Some(input));
@ -4074,18 +4106,7 @@ impl Application for App {
return self.update_config(); return self.update_config();
} }
Message::TabActivate(entity) => { Message::TabActivate(entity) => {
let mut tasks = Vec::new(); let mut tasks = vec![self.close_context_menus()];
// Close old context menu
let active = self.tab_model.active();
if let Some(tab) = self.tab_model.data_mut::<Tab>(active)
&& tab.context_menu.is_some()
{
tasks.push(self.update(Message::TabMessage(
Some(active),
tab::Message::ContextMenu(None, None),
)));
}
// Activate new tab // Activate new tab
self.tab_model.activate(entity); self.tab_model.activate(entity);
@ -4316,6 +4337,7 @@ impl Application for App {
} }
tab::Command::OpenFile(paths) => commands.push(self.open_file(&paths)), tab::Command::OpenFile(paths) => commands.push(self.open_file(&paths)),
tab::Command::OpenInNewTab(path) => { tab::Command::OpenInNewTab(path) => {
commands.push(self.close_context_menus());
commands.push(self.open_tab(Location::Path(path), false, None)); commands.push(self.open_tab(Location::Path(path), false, None));
} }
tab::Command::OpenInNewWindow(path) => match env::current_exe() { tab::Command::OpenInNewWindow(path) => match env::current_exe() {
@ -4772,25 +4794,21 @@ impl Application for App {
} }
} }
NavMenuAction::OpenInNewTab(entity) => { NavMenuAction::OpenInNewTab(entity) => {
match self.nav_model.data::<Location>(entity) { let open_task = match self.nav_model.data::<Location>(entity) {
Some(Location::Network(uri, display_name, path)) => { Some(Location::Network(uri, display_name, path)) => self.open_tab(
return self.open_tab( Location::Network(uri.clone(), display_name.clone(), path.clone()),
Location::Network(uri.clone(), display_name.clone(), path.clone()), false,
false, None,
None, ),
);
}
Some(Location::Path(path)) => { Some(Location::Path(path)) => {
return self.open_tab(Location::Path(path.clone()), false, None); self.open_tab(Location::Path(path.clone()), false, None)
} }
Some(Location::Recents) => { Some(Location::Recents) => self.open_tab(Location::Recents, false, None),
return self.open_tab(Location::Recents, false, None); Some(Location::Trash) => self.open_tab(Location::Trash, false, None),
} _ => Task::none(),
Some(Location::Trash) => { };
return self.open_tab(Location::Trash, false, None);
} return Task::batch([self.close_context_menus(), open_task]);
_ => {}
}
} }
// Open the selected path in a new cosmic-files window. // Open the selected path in a new cosmic-files window.
@ -6185,6 +6203,10 @@ impl Application for App {
let mut subscriptions = vec![ let mut subscriptions = vec![
//TODO: filter more events by window id //TODO: filter more events by window id
event::listen_with(|event, status, window_id| match event { event::listen_with(|event, status, window_id| match event {
Event::Mouse(mouse::Event::ButtonPressed(button)) => match status {
event::Status::Ignored => Some(Message::Mouse(window_id, button)),
event::Status::Captured => None,
},
Event::Keyboard(KeyEvent::KeyPressed { Event::Keyboard(KeyEvent::KeyPressed {
key, key,
modifiers, modifiers,

View file

@ -11,7 +11,7 @@ use cosmic::{
event, event,
futures::{self, SinkExt}, futures::{self, SinkExt},
keyboard::{Event as KeyEvent, Key, Modifiers, key::Named}, keyboard::{Event as KeyEvent, Key, Modifiers, key::Named},
stream, mouse, stream,
widget::scrollable, widget::scrollable,
window, window,
}, },
@ -468,6 +468,7 @@ enum Message {
Key(Modifiers, Key, Option<SmolStr>), Key(Modifiers, Key, Option<SmolStr>),
ModifiersChanged(Modifiers), ModifiersChanged(Modifiers),
MounterItems(MounterKey, MounterItems), MounterItems(MounterKey, MounterItems),
Mouse(window::Id, mouse::Button),
NewFolder, NewFolder,
NotifyEvents(Vec<DebouncedEvent>), NotifyEvents(Vec<DebouncedEvent>),
NotifyWatcher(WatcherWrapper), NotifyWatcher(WatcherWrapper),
@ -854,6 +855,15 @@ impl App {
} }
} }
fn close_context_menus(&mut self) -> Task<Message> {
self.tab.location_context_menu_index = None;
if self.tab.context_menu.is_some() {
return self.update(Message::TabMessage(tab::Message::ContextMenu(None, None)));
}
Task::none()
}
fn update_nav_model(&mut self) { fn update_nav_model(&mut self) {
let mut nav_model = segmented_button::ModelBuilder::default(); let mut nav_model = segmented_button::ModelBuilder::default();
@ -1300,9 +1310,9 @@ impl Application for App {
return Task::none(); return Task::none();
} }
if self.search_get().is_some() { if self.tab.location_context_menu_index.is_some() {
// Close search if open self.tab.location_context_menu_index = None;
return self.search_set(None); return Task::none();
} }
if self.tab.context_menu.is_some() { if self.tab.context_menu.is_some() {
@ -1315,6 +1325,11 @@ impl Application for App {
return Task::none(); return Task::none();
} }
if self.search_get().is_some() {
// Close search if open
return self.search_set(None);
}
let had_focused_button = self.tab.select_focus_id().is_some(); let had_focused_button = self.tab.select_focus_id().is_some();
if self.tab.select_none() { if self.tab.select_none() {
if had_focused_button { if had_focused_button {
@ -1529,6 +1544,12 @@ impl Application for App {
return Task::batch(commands); return Task::batch(commands);
} }
Message::Mouse(window_id, _button) => {
// Close context menu when clicking outside.
if self.core.main_window_id() == Some(window_id) {
return self.close_context_menus();
}
}
Message::NewFolder => { Message::NewFolder => {
if let Some(path) = self.tab.location.path_opt() { if let Some(path) = self.tab.location.path_opt() {
self.dialog_pages.push_back(DialogPage::NewFolder { self.dialog_pages.push_back(DialogPage::NewFolder {
@ -1686,14 +1707,18 @@ impl Application for App {
))); )));
} }
Message::SearchActivate => { Message::SearchActivate => {
return if self.search_get().is_none() { let mut tasks = vec![self.close_context_menus()];
self.search_set(Some(String::new()))
if self.search_get().is_none() {
tasks.push(self.search_set(Some(String::new())));
} else { } else {
widget::text_input::focus(self.search_id.clone()) tasks.push(widget::text_input::focus(self.search_id.clone()));
}; }
return Task::batch(tasks);
} }
Message::SearchClear => { Message::SearchClear => {
return self.search_set(None); return Task::batch([self.close_context_menus(), self.search_set(None)]);
} }
Message::SearchInput(input) => { Message::SearchInput(input) => {
return self.search_set(Some(input)); return self.search_set(Some(input));
@ -1974,7 +1999,11 @@ impl Application for App {
struct WatcherSubscription; struct WatcherSubscription;
struct TimeSubscription; struct TimeSubscription;
let mut subscriptions = vec![ let mut subscriptions = vec![
event::listen_with(|event, status, _window_id| match event { event::listen_with(|event, status, window_id| match event {
Event::Mouse(mouse::Event::ButtonPressed(button)) => match status {
event::Status::Ignored => Some(Message::Mouse(window_id, button)),
event::Status::Captured => None,
},
Event::Keyboard(KeyEvent::KeyPressed { Event::Keyboard(KeyEvent::KeyPressed {
key, key,
modifiers, modifiers,