feat: use libcosmic's context_menu widget for the breadcrumb menu

This commit is contained in:
Hojjat 2026-09-02 19:20:30 -06:00 • committed by Michael Murphy
parent 77f062647c
commit b7005660f1
4 changed files with 55 additions and 142 deletions

View file

@ -1737,15 +1737,6 @@ 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;
}
Task::none()
}
fn update_nav_model(&mut self) {
let mut nav_model = segmented_button::ModelBuilder::default();
@ -2802,11 +2793,6 @@ impl Application for App {
return cosmic::task::message(cosmic::action::app(Message::SetShowDetails(false)));
}
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.edit_location.is_some() {
tab.edit_location = None;
return Task::none();
@ -3558,7 +3544,7 @@ 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();
return Task::none();
}
}
Message::MoveTo(entity_opt) => {
@ -4348,7 +4334,7 @@ impl Application for App {
));
}
Message::SearchActivate => {
let mut tasks = vec![self.close_context_menus()];
let mut tasks = vec![];
if self.search_get().is_none() {
tasks.push(self.search_set_active(Some(String::new())));
@ -4359,7 +4345,7 @@ impl Application for App {
return Task::batch(tasks);
}
Message::SearchClear => {
return Task::batch([self.close_context_menus(), self.search_set_active(None)]);
return self.search_set_active(None);
}
Message::SearchInput(input) => {
return self.search_set_active(Some(input));
@ -4380,7 +4366,7 @@ impl Application for App {
return self.update_config();
}
Message::TabActivate(entity) => {
let mut tasks = vec![self.close_context_menus()];
let mut tasks = vec![];
// Activate new tab
self.tab_model.activate(entity);
@ -4584,7 +4570,6 @@ impl Application for App {
}
tab::Command::OpenFile(paths) => commands.push(self.open_file(&paths)),
tab::Command::OpenInNewTab(path) => {
commands.push(self.close_context_menus());
commands.push(self.open_tab(Location::Path(path), false, None));
}
tab::Command::OpenInNewWindow(path) => match env::current_exe() {
@ -5021,8 +5006,6 @@ impl Application for App {
if let Some(tab) = self.tab_model.data_mut::<Tab>(tab_entity) {
// Close location editing if enabled
tab.edit_location = None;
// Close other context menus.
tab.location_context_menu_index = None;
}
}
Message::NavMenuAction(action) => match action {
@ -5120,7 +5103,7 @@ impl Application for App {
_ => Task::none(),
};
return Task::batch([self.close_context_menus(), open_task]);
return open_task;
}
// Open the selected path in a new cosmic-files window.

View file

@ -864,12 +864,6 @@ impl App {
}
}
fn close_context_menus(&mut self) -> Task<Message> {
self.tab.location_context_menu_index = None;
Task::none()
}
fn update_nav_model(&mut self) {
let mut nav_model = segmented_button::ModelBuilder::default();
@ -1314,11 +1308,6 @@ impl Application for App {
return Task::none();
}
if self.tab.location_context_menu_index.is_some() {
self.tab.location_context_menu_index = None;
return Task::none();
}
if self.tab.edit_location.is_some() {
// Close location editing if enabled
self.tab.edit_location = None;
@ -1553,7 +1542,7 @@ 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();
return Task::none();
}
}
Message::NewFolder => {
@ -1713,7 +1702,7 @@ impl Application for App {
)));
}
Message::SearchActivate => {
let mut tasks = vec![self.close_context_menus()];
let mut tasks = vec![];
if self.search_get().is_none() {
tasks.push(self.search_set(Some(String::new())));
@ -1724,7 +1713,7 @@ impl Application for App {
return Task::batch(tasks);
}
Message::SearchClear => {
return Task::batch([self.close_context_menus(), self.search_set(None)]);
return self.search_set(None);
}
Message::SearchInput(input) => {
return self.search_set(Some(input));

View file

@ -1,13 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{Element, theme};
use cosmic::app::Core;
use cosmic::iced::keyboard::Modifiers;
use cosmic::iced::{Alignment, Background, Border, Length};
use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
use cosmic::widget::{self, Row, button, column, container, divider, responsive_menu_bar, text};
use cosmic::{Element, theme};
use cosmic::widget::{self, responsive_menu_bar};
use i18n_embed::LanguageLoader;
use mime_guess::Mime;
use std::collections::HashMap;
@ -24,21 +23,6 @@ use crate::trash::{Trash, TrashExt};
static MENU_ID: LazyLock<cosmic::widget::Id> =
LazyLock::new(|| cosmic::widget::Id::new("responsive-menu"));
macro_rules! menu_button {
($($x:expr),+ $(,)?) => (
button::custom(
Row::with_children(
[$(Element::from($x)),+]
)
.height(Length::Fixed(24.0))
.align_y(Alignment::Center)
)
.padding([theme::spacing().space_xxs, 16])
.width(Length::Fill)
.class(theme::Button::MenuItem)
);
}
const fn menu_button_optional(
label: String,
action: Action,
@ -765,50 +749,33 @@ pub fn menu_bar<'a>(
)
}
pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Message> {
pub fn location_context_menu(ancestor_index: usize) -> Vec<menu::Tree<tab::Message>> {
//TODO: only add some of these when in App mode
let children = [
menu_button!(text::body(fl!("open-in-new-tab")))
.on_press(tab::Message::LocationMenuAction(
menu::items(
&HashMap::new(),
vec![
menu::Item::Button(
fl!("open-in-new-tab"),
None,
LocationMenuAction::OpenInNewTab(ancestor_index),
))
.into(),
menu_button!(text::body(fl!("open-in-new-window")))
.on_press(tab::Message::LocationMenuAction(
),
menu::Item::Button(
fl!("open-in-new-window"),
None,
LocationMenuAction::OpenInNewWindow(ancestor_index),
))
.into(),
divider::horizontal::light().into(),
menu_button!(text::body(fl!("show-details")))
.on_press(tab::Message::LocationMenuAction(
),
menu::Item::Divider,
menu::Item::Button(
fl!("show-details"),
None,
LocationMenuAction::Preview(ancestor_index),
))
.into(),
divider::horizontal::light().into(),
menu_button!(text::body(fl!("add-to-sidebar")))
.on_press(tab::Message::LocationMenuAction(
),
menu::Item::Divider,
menu::Item::Button(
fl!("add-to-sidebar"),
None,
LocationMenuAction::AddToSidebar(ancestor_index),
))
.into(),
];
container(column::with_children(children))
.padding(1)
.style(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background(theme.transparent).component;
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
border: Border {
radius: cosmic.radius_s().map(|x| x + 1.0).into(),
width: 1.0,
color: component.divider.into(),
},
..Default::default()
}
})
.width(Length::Fixed(360.0))
.into()
),
],
)
}

View file

@ -1779,8 +1779,7 @@ pub enum Message {
ContextAction(Action),
RightClickBackground,
Surface(cosmic::surface::Action),
LocationContextMenuPoint(Option<Point>),
LocationContextMenuIndex(Option<Point>, Option<usize>),
LocationContextMenuIndex(Option<usize>),
LocationMenuAction(LocationMenuAction),
Drag(Option<Rectangle>),
DragEnd,
@ -2806,7 +2805,7 @@ pub struct Tab {
pub location: Location,
pub location_ancestors: Vec<(Location, String)>,
pub location_title: String,
pub location_context_menu_point: Option<Point>,
/// Breadcrumb whose context menu is open, drawn as active while it shows
pub location_context_menu_index: Option<usize>,
pub mode: Mode,
pub scroll_opt: Option<AbsoluteOffset>,
@ -2952,7 +2951,6 @@ impl Tab {
location,
location_ancestors,
location_title,
location_context_menu_point: None,
location_context_menu_index: None,
mode: Mode::App,
scroll_opt: None,
@ -3572,7 +3570,6 @@ impl Tab {
}
if click_i_opt != self.clicked.take() {
self.location_context_menu_index = None;
if let Some(ref mut items) = self.items_opt {
for (i, item) in items.iter_mut().enumerate() {
if mod_ctrl {
@ -3615,7 +3612,6 @@ impl Tab {
Message::Click(click_i_opt) => {
self.selected_clicked = false;
self.edit_location = None;
self.location_context_menu_index = None;
if click_i_opt.is_none() {
self.clicked = click_i_opt;
}
@ -3764,7 +3760,6 @@ impl Tab {
}
Message::RightClickBackground => {
self.edit_location = None;
self.location_context_menu_index = None;
//TODO: hack for clearing selecting when right clicking empty space
if self.last_right_click.take().is_none()
@ -3778,15 +3773,10 @@ impl Tab {
Message::Surface(action) => {
commands.push(Command::Surface(action));
}
Message::LocationContextMenuPoint(point_opt) => {
self.location_context_menu_point = point_opt;
}
Message::LocationContextMenuIndex(p, index_opt) => {
self.location_context_menu_point = p;
self.location_context_menu_index = index_opt;
Message::LocationContextMenuIndex(index) => {
self.location_context_menu_index = index;
}
Message::LocationMenuAction(action) => {
self.location_context_menu_index = None;
let path_for_index = |ancestor_index| {
self.location
.path_opt()
@ -3838,7 +3828,6 @@ impl Tab {
Message::Drag(rect_opt) => {
self.watch_drag = false;
if let Some(rect) = rect_opt {
self.location_context_menu_index = None;
if self.mode.multiple() {
self.select_rect(rect, mod_ctrl, mod_shift);
}
@ -5570,10 +5559,14 @@ impl Tab {
}
let location = self.location.with_path(ancestor.to_path_buf());
let mut mouse_area = crate::mouse_area::MouseArea::new(
let mouse_area = crate::mouse_area::MouseArea::new(
widget::button::custom(row)
.padding(space_xxxs)
.class(theme::Button::Link)
.class(if self.location_context_menu_index == Some(index) {
theme::Button::LinkActive
} else {
theme::Button::Link
})
.on_press(if ancestor == path {
Message::EditLocation(Some(self.location.clone().into()))
} else {
@ -5581,21 +5574,6 @@ impl Tab {
}),
);
if self.location_context_menu_index.is_some() {
mouse_area = mouse_area
.on_right_press(move |point_opt| {
Message::LocationContextMenuIndex(point_opt, None)
})
.wayland_on_right_press_window_position();
} else {
mouse_area = mouse_area
.on_right_press_no_capture()
.on_right_press(move |point_opt| {
Message::LocationContextMenuIndex(point_opt, Some(index))
})
.wayland_on_right_press_window_position();
}
let mouse_area = if let Location::Path(_) = &self.location {
mouse_area
.on_middle_press(move |_| Message::OpenInNewTab(ancestor.to_path_buf()))
@ -5603,7 +5581,16 @@ impl Tab {
mouse_area
};
children.push(self.dnd_dest(&location, mouse_area));
// Each breadcrumb carries the menu for its own ancestor index
let mut context_menu =
widget::context_menu(mouse_area, Some(menu::location_context_menu(index)))
.on_open(Message::LocationContextMenuIndex(Some(index)))
.on_close(Message::LocationContextMenuIndex(None))
.on_surface_action(Message::Surface);
if let Some(window_id) = self.window_id {
context_menu = context_menu.window_id(window_id);
}
children.push(self.dnd_dest(&location, context_menu));
if found_home || overflow {
break;
@ -5654,20 +5641,7 @@ impl Tab {
column = column.push(heading_rule);
}
let mouse_area = crate::mouse_area::MouseArea::new(column)
.on_right_press(Message::LocationContextMenuPoint);
let mut popover = widget::popover(mouse_area);
if let (Some(point), Some(index)) = (
self.location_context_menu_point,
self.location_context_menu_index,
) {
popover = popover
.popup(menu::location_context_menu(index))
.position(widget::popover::Position::Point(point));
}
popover.into()
column.into()
}
pub fn empty_view(&self, has_hidden: bool) -> Element<'_, Message> {