feat: use libcosmic's context_menu widget for the file context menu
This commit is contained in:
parent
089ad2b417
commit
77f062647c
4 changed files with 229 additions and 532 deletions
159
src/app.rs
159
src/app.rs
|
|
@ -17,12 +17,10 @@ use cosmic::iced::widget::button::focus;
|
||||||
use cosmic::iced::widget::scrollable;
|
use cosmic::iced::widget::scrollable;
|
||||||
use cosmic::iced::widget::scrollable::AbsoluteOffset;
|
use cosmic::iced::widget::scrollable::AbsoluteOffset;
|
||||||
use cosmic::iced::window::{self, Event as WindowEvent, Id as WindowId};
|
use cosmic::iced::window::{self, Event as WindowEvent, Id as WindowId};
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{self, Alignment, Event, Length, Size, Subscription, event, mouse, stream};
|
||||||
self, Alignment, Event, Length, Rectangle, Size, Subscription, event, mouse, stream,
|
|
||||||
};
|
|
||||||
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{
|
||||||
Limits, Point,
|
Limits, Point, Rectangle,
|
||||||
event::wayland::{Event as WaylandEvent, OutputEvent, OverlapNotifyEvent},
|
event::wayland::{Event as WaylandEvent, OutputEvent, OverlapNotifyEvent},
|
||||||
platform_specific::runtime::wayland::layer_surface::{
|
platform_specific::runtime::wayland::layer_surface::{
|
||||||
IcedMargin, IcedOutput, SctkLayerSurfaceSettings,
|
IcedMargin, IcedOutput, SctkLayerSurfaceSettings,
|
||||||
|
|
@ -672,7 +670,6 @@ pub struct MounterData(MounterKey, MounterItem);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum WindowKind {
|
pub enum WindowKind {
|
||||||
ContextMenu(Entity, widget::Id),
|
|
||||||
Desktop(Entity),
|
Desktop(Entity),
|
||||||
DesktopViewOptions,
|
DesktopViewOptions,
|
||||||
Dialogs(widget::Id),
|
Dialogs(widget::Id),
|
||||||
|
|
@ -1457,12 +1454,6 @@ impl App {
|
||||||
fn remove_window(&mut self, id: &window::Id) {
|
fn remove_window(&mut self, id: &window::Id) {
|
||||||
if let Some(window) = self.windows.remove(id) {
|
if let Some(window) = self.windows.remove(id) {
|
||||||
match window.kind {
|
match window.kind {
|
||||||
WindowKind::ContextMenu(entity, _) => {
|
|
||||||
// Close context menu
|
|
||||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
|
||||||
tab.context_menu = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowKind::Desktop(entity) => {
|
WindowKind::Desktop(entity) => {
|
||||||
// Remove the tab from the tab model
|
// Remove the tab from the tab model
|
||||||
self.tab_model.remove(entity);
|
self.tab_model.remove(entity);
|
||||||
|
|
@ -1750,12 +1741,6 @@ impl App {
|
||||||
let active = self.tab_model.active();
|
let active = self.tab_model.active();
|
||||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(active) {
|
if let Some(tab) = self.tab_model.data_mut::<Tab>(active) {
|
||||||
tab.location_context_menu_index = None;
|
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()
|
Task::none()
|
||||||
|
|
@ -1774,9 +1759,7 @@ impl App {
|
||||||
|
|
||||||
for (favorite_i, favorite) in self.config.favorites.iter().enumerate() {
|
for (favorite_i, favorite) in self.config.favorites.iter().enumerate() {
|
||||||
if let Some(path) = favorite.path_opt() {
|
if let Some(path) = favorite.path_opt() {
|
||||||
let name = favorite
|
let name = favorite.display_name().unwrap_or_else(|| fl!("filesystem"));
|
||||||
.display_name()
|
|
||||||
.unwrap_or_else(|| fl!("filesystem"));
|
|
||||||
nav_model = nav_model.insert(move |b| {
|
nav_model = nav_model.insert(move |b| {
|
||||||
b.text(name.clone())
|
b.text(name.clone())
|
||||||
.icon(
|
.icon(
|
||||||
|
|
@ -2824,13 +2807,6 @@ impl Application for App {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
|
||||||
if tab.context_menu.is_some() {
|
|
||||||
return self.update(Message::TabMessage(
|
|
||||||
Some(entity),
|
|
||||||
tab::Message::ContextMenu(None, None),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if tab.edit_location.is_some() {
|
if tab.edit_location.is_some() {
|
||||||
tab.edit_location = None;
|
tab.edit_location = None;
|
||||||
return Task::none();
|
return Task::none();
|
||||||
|
|
@ -4505,6 +4481,8 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
Message::TabMessage(entity_opt, tab_message) => {
|
Message::TabMessage(entity_opt, tab_message) => {
|
||||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||||
|
// The context menu opens on right-button release, so refresh paste availability now
|
||||||
|
let right_click = matches!(tab_message, tab::Message::RightClick(..));
|
||||||
|
|
||||||
let tab_commands = match self.tab_model.data_mut::<Tab>(entity) {
|
let tab_commands = match self.tab_model.data_mut::<Tab>(entity) {
|
||||||
Some(tab) => tab.update(tab_message, self.modifiers),
|
Some(tab) => tab.update(tab_message, self.modifiers),
|
||||||
|
|
@ -4512,6 +4490,9 @@ impl Application for App {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut commands = Vec::new();
|
let mut commands = Vec::new();
|
||||||
|
if right_click {
|
||||||
|
commands.push(self.update(Message::CheckClipboard));
|
||||||
|
}
|
||||||
for tab_command in tab_commands {
|
for tab_command in tab_commands {
|
||||||
match tab_command {
|
match tab_command {
|
||||||
tab::Command::Action(action) => {
|
tab::Command::Action(action) => {
|
||||||
|
|
@ -4552,93 +4533,8 @@ impl Application for App {
|
||||||
self.update_tab(entity, tab_path, selection_paths),
|
self.update_tab(entity, tab_path, selection_paths),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
tab::Command::ContextMenu(point_opt, parent_id) => {
|
tab::Command::Surface(action) => {
|
||||||
#[cfg(feature = "wayland")]
|
commands.push(self.update(Message::Surface(action)));
|
||||||
if let Some(point) = point_opt {
|
|
||||||
if crate::is_wayland() {
|
|
||||||
// Open context menu
|
|
||||||
use cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{
|
|
||||||
Anchor, Gravity,
|
|
||||||
};
|
|
||||||
use cosmic::{iced::runtime::platform_specific::wayland::popup::{
|
|
||||||
SctkPopupSettings, SctkPositioner,
|
|
||||||
}, widget::menu::StyleSheet as _};
|
|
||||||
|
|
||||||
let window_id = WindowId::unique();
|
|
||||||
self.windows.insert(
|
|
||||||
window_id,
|
|
||||||
Window::new(WindowKind::ContextMenu(
|
|
||||||
entity,
|
|
||||||
widget::Id::unique(),
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
commands.push(self.update(Message::CheckClipboard));
|
|
||||||
let t = self.core.system_theme();
|
|
||||||
let styling = t.appearance(
|
|
||||||
&cosmic::theme::menu_bar::MenuBarStyle::Default,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
let rad = styling.menu_border_radius;
|
|
||||||
|
|
||||||
commands.push(self.update(Message::Surface(
|
|
||||||
cosmic::surface::action::app_popup(
|
|
||||||
move |_| cosmic::surface::action::LiveSettings {
|
|
||||||
corners: Some(iced::runtime::platform_specific::wayland::CornerRadius {
|
|
||||||
top_left: rad[0] as u32,
|
|
||||||
top_right: rad[1] as u32,
|
|
||||||
bottom_left: rad[2] as u32,
|
|
||||||
bottom_right: rad[3] as u32,
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
}, move |app: &mut Self| -> SctkPopupSettings {
|
|
||||||
let anchor_rect = Rectangle {
|
|
||||||
x: point.x as i32,
|
|
||||||
y: point.y as i32,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
let positioner = SctkPositioner {
|
|
||||||
size: None,
|
|
||||||
anchor_rect,
|
|
||||||
anchor: Anchor::None,
|
|
||||||
gravity: Gravity::BottomRight,
|
|
||||||
reactive: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
SctkPopupSettings {
|
|
||||||
parent: parent_id.unwrap_or(
|
|
||||||
app.core
|
|
||||||
.main_window_id()
|
|
||||||
.unwrap_or(WindowId::NONE),
|
|
||||||
),
|
|
||||||
id: window_id,
|
|
||||||
positioner,
|
|
||||||
parent_size: None,
|
|
||||||
grab: true,
|
|
||||||
close_with_children: false,
|
|
||||||
input_zone: None,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Destroy previous popup
|
|
||||||
let mut window_ids = Vec::new();
|
|
||||||
for (window_id, window) in &self.windows {
|
|
||||||
if let WindowKind::ContextMenu(e, _) = &window.kind
|
|
||||||
&& *e == entity
|
|
||||||
{
|
|
||||||
window_ids.push(*window_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for window_id in window_ids {
|
|
||||||
commands.push(self.update(Message::Surface(
|
|
||||||
cosmic::surface::action::destroy_popup(window_id),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
tab::Command::Delete(paths) => commands.push(self.delete(paths)),
|
tab::Command::Delete(paths) => commands.push(self.delete(paths)),
|
||||||
tab::Command::DropFiles(to, from) => {
|
tab::Command::DropFiles(to, from) => {
|
||||||
|
|
@ -5127,10 +5023,6 @@ impl Application for App {
|
||||||
tab.edit_location = None;
|
tab.edit_location = None;
|
||||||
// Close other context menus.
|
// Close other context menus.
|
||||||
tab.location_context_menu_index = None;
|
tab.location_context_menu_index = None;
|
||||||
return Task::done(cosmic::Action::App(Message::TabMessage(
|
|
||||||
Some(tab_entity),
|
|
||||||
tab::Message::ContextMenu(None, None),
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::NavMenuAction(action) => match action {
|
Message::NavMenuAction(action) => match action {
|
||||||
|
|
@ -5311,16 +5203,10 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
NavMenuAction::ChangeSidebarLabel(entity) => {
|
NavMenuAction::ChangeSidebarLabel(entity) => {
|
||||||
if let Some(favorite) = self
|
if let Some(favorite) = self.nav_model.data::<FavoriteIndex>(entity).and_then(
|
||||||
.nav_model
|
|FavoriteIndex(favorite_i)| self.config.favorites.get(*favorite_i),
|
||||||
.data::<FavoriteIndex>(entity)
|
) {
|
||||||
.and_then(|FavoriteIndex(favorite_i)| {
|
let label = favorite.display_name().unwrap_or_else(|| fl!("filesystem"));
|
||||||
self.config.favorites.get(*favorite_i)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
let label = favorite
|
|
||||||
.display_name()
|
|
||||||
.unwrap_or_else(|| fl!("filesystem"));
|
|
||||||
return Task::batch([
|
return Task::batch([
|
||||||
self.dialog_pages
|
self.dialog_pages
|
||||||
.push_back(DialogPage::ChangeSidebarLabel { entity, label }),
|
.push_back(DialogPage::ChangeSidebarLabel { entity, label }),
|
||||||
|
|
@ -6672,23 +6558,6 @@ impl Application for App {
|
||||||
fn view_window(&self, id: WindowId) -> Element<'_, Self::Message> {
|
fn view_window(&self, id: WindowId) -> Element<'_, Self::Message> {
|
||||||
let content = match self.windows.get(&id) {
|
let content = match self.windows.get(&id) {
|
||||||
Some(window) => match &window.kind {
|
Some(window) => match &window.kind {
|
||||||
WindowKind::ContextMenu(entity, id) => match self.tab_model.data::<Tab>(*entity) {
|
|
||||||
Some(tab) => {
|
|
||||||
return widget::autosize::autosize(
|
|
||||||
menu::context_menu(
|
|
||||||
tab,
|
|
||||||
&self.key_binds,
|
|
||||||
&window.modifiers,
|
|
||||||
self.clipboard_has_content(),
|
|
||||||
&self.config.context_actions,
|
|
||||||
)
|
|
||||||
.map(|x| Message::TabMessage(Some(*entity), x)),
|
|
||||||
id.clone(),
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
}
|
|
||||||
None => widget::text("Unknown tab ID").into(),
|
|
||||||
},
|
|
||||||
WindowKind::Desktop(entity) => {
|
WindowKind::Desktop(entity) => {
|
||||||
let mut tab_column = widget::column::with_capacity(3);
|
let mut tab_column = widget::column::with_capacity(3);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -541,7 +541,6 @@ struct App {
|
||||||
title: String,
|
title: String,
|
||||||
accept_label: DialogLabel,
|
accept_label: DialogLabel,
|
||||||
choices: Vec<DialogChoice>,
|
choices: Vec<DialogChoice>,
|
||||||
context_menu_window: Option<window::Id>,
|
|
||||||
context_page: ContextPage,
|
context_page: ContextPage,
|
||||||
dialog_pages: VecDeque<DialogPage>,
|
dialog_pages: VecDeque<DialogPage>,
|
||||||
dialog_text_input: widget::Id,
|
dialog_text_input: widget::Id,
|
||||||
|
|
@ -867,9 +866,6 @@ impl App {
|
||||||
|
|
||||||
fn close_context_menus(&mut self) -> Task<Message> {
|
fn close_context_menus(&mut self) -> Task<Message> {
|
||||||
self.tab.location_context_menu_index = None;
|
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()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -1048,7 +1044,6 @@ impl Application for App {
|
||||||
title,
|
title,
|
||||||
accept_label: DialogLabel::from(accept_label),
|
accept_label: DialogLabel::from(accept_label),
|
||||||
choices: Vec::new(),
|
choices: Vec::new(),
|
||||||
context_menu_window: None,
|
|
||||||
context_page: ContextPage::Preview(None, PreviewKind::Selected),
|
context_page: ContextPage::Preview(None, PreviewKind::Selected),
|
||||||
dialog_pages: VecDeque::new(),
|
dialog_pages: VecDeque::new(),
|
||||||
dialog_text_input: widget::Id::new("Dialog Text Input"),
|
dialog_text_input: widget::Id::new("Dialog Text Input"),
|
||||||
|
|
@ -1324,10 +1319,6 @@ impl Application for App {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.tab.context_menu.is_some() {
|
|
||||||
return self.update(Message::TabMessage(tab::Message::ContextMenu(None, None)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.tab.edit_location.is_some() {
|
if self.tab.edit_location.is_some() {
|
||||||
// Close location editing if enabled
|
// Close location editing if enabled
|
||||||
self.tab.edit_location = None;
|
self.tab.edit_location = None;
|
||||||
|
|
@ -1769,94 +1760,8 @@ impl Application for App {
|
||||||
self.rescan_tab(selection_paths),
|
self.rescan_tab(selection_paths),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
tab::Command::ContextMenu(point_opt, parent_id) => {
|
tab::Command::Surface(action) => {
|
||||||
#[cfg(feature = "wayland")]
|
commands.push(self.update(Message::Surface(action)));
|
||||||
match point_opt {
|
|
||||||
Some(point) => {
|
|
||||||
if crate::is_wayland() {
|
|
||||||
// Open context menu
|
|
||||||
use cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{
|
|
||||||
Anchor, Gravity,
|
|
||||||
};
|
|
||||||
use cosmic::iced::runtime::platform_specific::wayland::popup::{
|
|
||||||
SctkPopupSettings, SctkPositioner,
|
|
||||||
};
|
|
||||||
use cosmic::iced::Rectangle;
|
|
||||||
use cosmic::widget::menu::StyleSheet as _;
|
|
||||||
|
|
||||||
let window_id = window::Id::unique();
|
|
||||||
self.context_menu_window = Some(window_id);
|
|
||||||
let autosize_id = widget::Id::unique();
|
|
||||||
let t = self.core.system_theme();
|
|
||||||
let styling = t.appearance(
|
|
||||||
&cosmic::theme::menu_bar::MenuBarStyle::Default,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
let rad = styling.menu_border_radius;
|
|
||||||
commands.push(self.update(Message::Surface(
|
|
||||||
cosmic::surface::action::app_popup(
|
|
||||||
move |_| cosmic::surface::action::LiveSettings {
|
|
||||||
corners: Some(iced::runtime::platform_specific::wayland::CornerRadius {
|
|
||||||
top_left: rad[0] as u32,
|
|
||||||
top_right: rad[1] as u32,
|
|
||||||
bottom_left: rad[2] as u32,
|
|
||||||
bottom_right: rad[3] as u32,
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
move |app: &mut Self| -> SctkPopupSettings {
|
|
||||||
let anchor_rect = Rectangle {
|
|
||||||
x: point.x as i32,
|
|
||||||
y: point.y as i32,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
let positioner = SctkPositioner {
|
|
||||||
size: None,
|
|
||||||
anchor_rect,
|
|
||||||
anchor: Anchor::None,
|
|
||||||
gravity: Gravity::BottomRight,
|
|
||||||
reactive: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
SctkPopupSettings {
|
|
||||||
parent: parent_id
|
|
||||||
.unwrap_or(app.flags.window_id),
|
|
||||||
id: window_id,
|
|
||||||
positioner,
|
|
||||||
parent_size: None,
|
|
||||||
grab: true,
|
|
||||||
close_with_children: false,
|
|
||||||
input_zone: None,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(Box::new(move |app: &Self| {
|
|
||||||
widget::autosize::autosize(
|
|
||||||
menu::context_menu(
|
|
||||||
&app.tab,
|
|
||||||
&app.key_binds,
|
|
||||||
&app.modifiers,
|
|
||||||
false, // Paste not used in dialogs
|
|
||||||
&app.flags.config.context_actions,
|
|
||||||
)
|
|
||||||
.map(Message::TabMessage)
|
|
||||||
.map(cosmic::Action::App),
|
|
||||||
autosize_id.clone(),
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if let Some(window_id) = self.context_menu_window.take() {
|
|
||||||
commands.push(self.update(Message::Surface(
|
|
||||||
cosmic::surface::action::destroy_popup(window_id),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
tab::Command::Iced(iced_command) => {
|
tab::Command::Iced(iced_command) => {
|
||||||
commands.push(iced_command.0.map(|tab_message| {
|
commands.push(iced_command.0.map(|tab_message| {
|
||||||
|
|
|
||||||
323
src/menu.rs
323
src/menu.rs
|
|
@ -1,14 +1,12 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::app::Core;
|
use cosmic::app::Core;
|
||||||
use cosmic::iced::advanced::widget::text::Style as TextStyle;
|
|
||||||
use cosmic::iced::keyboard::Modifiers;
|
use cosmic::iced::keyboard::Modifiers;
|
||||||
use cosmic::iced::{Alignment, Background, Border, Length};
|
use cosmic::iced::{Alignment, Background, Border, Length};
|
||||||
|
use cosmic::widget::menu::action::MenuAction;
|
||||||
use cosmic::widget::menu::key_bind::KeyBind;
|
use cosmic::widget::menu::key_bind::KeyBind;
|
||||||
use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
|
use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
|
||||||
use cosmic::widget::{
|
use cosmic::widget::{self, Row, button, column, container, divider, responsive_menu_bar, text};
|
||||||
self, Row, button, column, container, divider, responsive_menu_bar, space, text,
|
|
||||||
};
|
|
||||||
use cosmic::{Element, theme};
|
use cosmic::{Element, theme};
|
||||||
use i18n_embed::LanguageLoader;
|
use i18n_embed::LanguageLoader;
|
||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
|
|
@ -53,82 +51,46 @@ const fn menu_button_optional(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A menu action dispatched to the tab the menu was opened on.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct TabAction(pub Action);
|
||||||
|
|
||||||
|
impl MenuAction for TabAction {
|
||||||
|
type Message = tab::Message;
|
||||||
|
|
||||||
|
fn message(&self) -> tab::Message {
|
||||||
|
tab::Message::ContextAction(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn context_menu<'a>(
|
pub fn context_menu<'a>(
|
||||||
tab: &Tab,
|
tab: &Tab,
|
||||||
key_binds: &HashMap<KeyBind, Action>,
|
key_binds: &HashMap<KeyBind, Action>,
|
||||||
modifiers: &Modifiers,
|
modifiers: &Modifiers,
|
||||||
clipboard_paste_available: bool,
|
clipboard_paste_available: bool,
|
||||||
context_actions: &[ContextActionPreset],
|
context_actions: &[ContextActionPreset],
|
||||||
) -> Element<'a, tab::Message> {
|
) -> Vec<menu::Tree<tab::Message>> {
|
||||||
let find_key = |action: &Action| -> String {
|
let menu_item =
|
||||||
for (key_bind, key_action) in key_binds {
|
|label: String, action: Action| menu::Item::Button(label, None, TabAction(action));
|
||||||
if action == key_action {
|
let menu_item_disabled =
|
||||||
return key_bind.to_string();
|
|label: String, action: Action| menu::Item::ButtonDisabled(label, None, TabAction(action));
|
||||||
}
|
|
||||||
}
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
fn key_style(theme: &cosmic::Theme) -> TextStyle {
|
|
||||||
let mut color = theme.cosmic().background(theme.transparent).component.on;
|
|
||||||
color.alpha *= 0.75;
|
|
||||||
TextStyle {
|
|
||||||
color: Some(color.into()),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn disabled_style(theme: &cosmic::Theme) -> TextStyle {
|
|
||||||
let mut color = theme.cosmic().background(theme.transparent).component.on;
|
|
||||||
color.alpha *= 0.5;
|
|
||||||
TextStyle {
|
|
||||||
color: Some(color.into()),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let menu_item = |label, action| {
|
|
||||||
let key = find_key(&action);
|
|
||||||
menu_button!(
|
|
||||||
text::body(label),
|
|
||||||
space::horizontal(),
|
|
||||||
text::body(key).class(theme::Text::Custom(key_style))
|
|
||||||
)
|
|
||||||
.on_press(tab::Message::ContextAction(action))
|
|
||||||
};
|
|
||||||
|
|
||||||
let menu_item_disabled = |label, action: Action| {
|
|
||||||
let key = find_key(&action);
|
|
||||||
menu_button!(
|
|
||||||
text::body(label).class(theme::Text::Custom(disabled_style)),
|
|
||||||
space::horizontal(),
|
|
||||||
text::body(key).class(theme::Text::Custom(disabled_style))
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Allow paste when clipboard has data and we're in a location that supports it
|
// Allow paste when clipboard has data and we're in a location that supports it
|
||||||
let can_paste = clipboard_paste_available && tab.location.supports_paste();
|
let can_paste = clipboard_paste_available && tab.location.supports_paste();
|
||||||
|
|
||||||
let (sort_name, sort_direction, _) = tab.sort_options();
|
let (sort_name, sort_direction, _) = tab.sort_options();
|
||||||
let sort_item = |label, variant| {
|
let sort_item = |label: String, variant| {
|
||||||
let key = find_key(&Action::ToggleSort(variant));
|
let entry = menu::Entry::new(label, TabAction(Action::ToggleSort(variant)));
|
||||||
let leading: Element<'a, tab::Message> = if sort_name == variant {
|
menu::Item::Entry(if sort_name == variant {
|
||||||
let icon_name = if sort_direction {
|
let icon_name = if sort_direction {
|
||||||
"view-sort-ascending-symbolic"
|
"view-sort-ascending-symbolic"
|
||||||
} else {
|
} else {
|
||||||
"view-sort-descending-symbolic"
|
"view-sort-descending-symbolic"
|
||||||
};
|
};
|
||||||
widget::icon::from_name(icon_name).size(14).into()
|
entry.icon(widget::icon::from_name(icon_name).size(14).handle())
|
||||||
} else {
|
} else {
|
||||||
space::horizontal().width(Length::Fixed(14.0)).into()
|
entry.reserve_icon()
|
||||||
};
|
})
|
||||||
menu_button!(
|
|
||||||
leading,
|
|
||||||
space::horizontal().width(Length::Fixed(theme::spacing().space_xxs.into())),
|
|
||||||
text::body(label),
|
|
||||||
space::horizontal(),
|
|
||||||
text::body(key).class(theme::Text::Custom(key_style))
|
|
||||||
)
|
|
||||||
.on_press(tab::Message::ContextAction(Action::ToggleSort(variant)))
|
|
||||||
.into()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut selected_dir = 0;
|
let mut selected_dir = 0;
|
||||||
|
|
@ -173,8 +135,8 @@ pub fn context_menu<'a>(
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, action)| action.matches_selection(selected, selected_dir))
|
.filter(|(_, action)| action.matches_selection(selected, selected_dir))
|
||||||
.map(|(i, action)| menu_item(action.name.clone(), Action::RunContextAction(i)).into())
|
.map(|(i, action)| menu_item(action.name.clone(), Action::RunContextAction(i)))
|
||||||
.collect::<Vec<Element<'a, tab::Message>>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
// Parse the desktop entry if it is the only selection
|
// Parse the desktop entry if it is the only selection
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
|
|
@ -189,7 +151,7 @@ pub fn context_menu<'a>(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut children: Vec<Element<_>> = Vec::new();
|
let mut children: Vec<menu::Item<TabAction, String>> = Vec::new();
|
||||||
match (&tab.mode, &tab.location) {
|
match (&tab.mode, &tab.location) {
|
||||||
(
|
(
|
||||||
tab::Mode::App | tab::Mode::Desktop,
|
tab::Mode::App | tab::Mode::Desktop,
|
||||||
|
|
@ -201,106 +163,113 @@ pub fn context_menu<'a>(
|
||||||
| Location::Network(_, _, Some(_)),
|
| Location::Network(_, _, Some(_)),
|
||||||
) => {
|
) => {
|
||||||
if selected_trash_only {
|
if selected_trash_only {
|
||||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
children.push(menu_item(fl!("open"), Action::Open));
|
||||||
if !Trash::is_empty() {
|
if !Trash::is_empty() {
|
||||||
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
|
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash));
|
||||||
}
|
}
|
||||||
} else if let Some(entry) = selected_desktop_entry {
|
} else if let Some(entry) = selected_desktop_entry {
|
||||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
children.push(menu_item(fl!("open"), Action::Open));
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
{
|
{
|
||||||
children.extend(entry.desktop_actions.into_iter().enumerate().map(
|
children.extend(
|
||||||
|(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(),
|
entry
|
||||||
));
|
.desktop_actions
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, action)| menu_item(action.name, Action::ExecEntryAction(i))),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
children.push(menu_item(fl!("rename"), Action::Rename).into());
|
children.push(menu_item(fl!("rename"), Action::Rename));
|
||||||
children.push(menu_item(fl!("cut"), Action::Cut).into());
|
children.push(menu_item(fl!("cut"), Action::Cut));
|
||||||
if modifiers.shift() && !modifiers.control() {
|
if modifiers.shift() && !modifiers.control() {
|
||||||
children.push(menu_item(fl!("copy-path"), Action::CopyPath).into());
|
children.push(menu_item(fl!("copy-path"), Action::CopyPath));
|
||||||
} else {
|
} else {
|
||||||
children.push(menu_item(fl!("copy"), Action::Copy).into());
|
children.push(menu_item(fl!("copy"), Action::Copy));
|
||||||
}
|
}
|
||||||
// Should this simply bypass trash and remove the shortcut?
|
// Should this simply bypass trash and remove the shortcut?
|
||||||
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
|
children.push(menu_item(fl!("move-to-trash"), Action::Delete));
|
||||||
let action_items = context_action_items(selected, selected_dir);
|
let action_items = context_action_items(selected, selected_dir);
|
||||||
if !action_items.is_empty() {
|
if !action_items.is_empty() {
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
children.extend(action_items);
|
children.extend(action_items);
|
||||||
}
|
}
|
||||||
} else if selected > 0 {
|
} else if selected > 0 {
|
||||||
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
|
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
|
||||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
children.push(menu_item(fl!("open"), Action::Open));
|
||||||
}
|
}
|
||||||
if selected == 1 {
|
if selected == 1 {
|
||||||
children.push(menu_item(fl!("menu-open-with"), Action::OpenWith).into());
|
children.push(menu_item(fl!("menu-open-with"), Action::OpenWith));
|
||||||
if selected_dir == 1 {
|
if selected_dir == 1 {
|
||||||
children
|
children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal));
|
||||||
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tab.location.is_recents() || matches!(tab.location, Location::Search(..)) {
|
if tab.location.is_recents() || matches!(tab.location, Location::Search(..)) {
|
||||||
children.push(
|
children.push(menu_item(
|
||||||
menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(),
|
fl!("open-item-location"),
|
||||||
);
|
Action::OpenItemLocation,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
// All selected items are directories
|
// All selected items are directories
|
||||||
if selected == selected_dir && matches!(tab.mode, tab::Mode::App) {
|
if selected == selected_dir && matches!(tab.mode, tab::Mode::App) {
|
||||||
children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab).into());
|
children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab));
|
||||||
children
|
children.push(menu_item(
|
||||||
.push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into());
|
fl!("open-in-new-window"),
|
||||||
|
Action::OpenInNewWindow,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let action_items = context_action_items(selected, selected_dir);
|
let action_items = context_action_items(selected, selected_dir);
|
||||||
if !action_items.is_empty() {
|
if !action_items.is_empty() {
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
children.extend(action_items);
|
children.extend(action_items);
|
||||||
}
|
}
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
if selected_mount_point == 0 {
|
if selected_mount_point == 0 {
|
||||||
children.push(menu_item(fl!("rename"), Action::Rename).into());
|
children.push(menu_item(fl!("rename"), Action::Rename));
|
||||||
children.push(menu_item(fl!("cut"), Action::Cut).into());
|
children.push(menu_item(fl!("cut"), Action::Cut));
|
||||||
}
|
}
|
||||||
if modifiers.shift() && !modifiers.control() {
|
if modifiers.shift() && !modifiers.control() {
|
||||||
children.push(menu_item(fl!("copy-path"), Action::CopyPath).into());
|
children.push(menu_item(fl!("copy-path"), Action::CopyPath));
|
||||||
} else {
|
} else {
|
||||||
children.push(menu_item(fl!("copy"), Action::Copy).into());
|
children.push(menu_item(fl!("copy"), Action::Copy));
|
||||||
}
|
}
|
||||||
if selected_mount_point == 0 {
|
if selected_mount_point == 0 {
|
||||||
children.push(menu_item(fl!("move-to"), Action::MoveTo).into());
|
children.push(menu_item(fl!("move-to"), Action::MoveTo));
|
||||||
}
|
}
|
||||||
children.push(menu_item(fl!("copy-to"), Action::CopyTo).into());
|
children.push(menu_item(fl!("copy-to"), Action::CopyTo));
|
||||||
|
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES;
|
let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES;
|
||||||
selected_types.retain(|t| supported_archive_types.iter().copied().all(|m| *t != m));
|
selected_types.retain(|t| supported_archive_types.iter().copied().all(|m| *t != m));
|
||||||
if selected_types.is_empty() {
|
if selected_types.is_empty() {
|
||||||
children.push(menu_item(fl!("extract-here"), Action::ExtractHere).into());
|
children.push(menu_item(fl!("extract-here"), Action::ExtractHere));
|
||||||
children.push(menu_item(fl!("extract-to"), Action::ExtractTo).into());
|
children.push(menu_item(fl!("extract-to"), Action::ExtractTo));
|
||||||
}
|
}
|
||||||
children.push(menu_item(fl!("compress"), Action::Compress).into());
|
children.push(menu_item(fl!("compress"), Action::Compress));
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
|
|
||||||
//TODO: Print?
|
//TODO: Print?
|
||||||
children.push(menu_item(fl!("show-details"), Action::Preview).into());
|
children.push(menu_item(fl!("show-details"), Action::Preview));
|
||||||
if any_trash_item {
|
if any_trash_item {
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
children.push(
|
children.push(menu_item(
|
||||||
menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into(),
|
fl!("restore-from-trash"),
|
||||||
);
|
Action::RestoreFromTrash,
|
||||||
children.push(divider::horizontal::light().into());
|
));
|
||||||
children.push(menu_item(fl!("delete-permanently"), Action::Delete).into());
|
children.push(menu::Item::Divider);
|
||||||
|
children.push(menu_item(fl!("delete-permanently"), Action::Delete));
|
||||||
} else {
|
} else {
|
||||||
if matches!(tab.mode, tab::Mode::App) {
|
if matches!(tab.mode, tab::Mode::App) {
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
children
|
children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar));
|
||||||
.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into());
|
|
||||||
}
|
}
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
if tab.location.is_recents() {
|
if tab.location.is_recents() {
|
||||||
children.push(
|
children.push(menu_item(
|
||||||
menu_item(fl!("remove-from-recents"), Action::RemoveFromRecents).into(),
|
fl!("remove-from-recents"),
|
||||||
);
|
Action::RemoveFromRecents,
|
||||||
children.push(divider::horizontal::light().into());
|
));
|
||||||
|
children.push(menu::Item::Divider);
|
||||||
}
|
}
|
||||||
if selected_mount_point == 0 {
|
if selected_mount_point == 0 {
|
||||||
if modifiers.shift() && !modifiers.control() {
|
if modifiers.shift() && !modifiers.control() {
|
||||||
|
|
@ -309,55 +278,59 @@ pub fn context_menu<'a>(
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
|
children.push(menu_item(fl!("move-to-trash"), Action::Delete));
|
||||||
}
|
}
|
||||||
} else if selected == 1 {
|
} else if selected == 1 {
|
||||||
children.push(menu_item(fl!("eject"), Action::Eject).into());
|
children.push(menu_item(fl!("eject"), Action::Eject));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO: need better designs for menu with no selection
|
//TODO: need better designs for menu with no selection
|
||||||
//TODO: have things like properties but they apply to the folder?
|
//TODO: have things like properties but they apply to the folder?
|
||||||
if tab.location != Location::Recents {
|
if tab.location != Location::Recents {
|
||||||
children.push(menu_item(fl!("new-folder"), Action::NewFolder).into());
|
children.push(menu_item(fl!("new-folder"), Action::NewFolder));
|
||||||
children.push(menu_item(fl!("new-file"), Action::NewFile).into());
|
children.push(menu_item(fl!("new-file"), Action::NewFile));
|
||||||
children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
|
children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal));
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
if tab.mode.multiple() {
|
if tab.mode.multiple() {
|
||||||
children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
|
children.push(menu_item(fl!("select-all"), Action::SelectAll));
|
||||||
}
|
}
|
||||||
if can_paste {
|
if can_paste {
|
||||||
children.push(menu_item(fl!("paste"), Action::Paste).into());
|
children.push(menu_item(fl!("paste"), Action::Paste));
|
||||||
} else {
|
} else {
|
||||||
children.push(menu_item_disabled(fl!("paste"), Action::Paste).into());
|
children.push(menu_item_disabled(fl!("paste"), Action::Paste));
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: only show if cosmic-settings is found?
|
//TODO: only show if cosmic-settings is found?
|
||||||
if matches!(tab.mode, tab::Mode::Desktop) {
|
if matches!(tab.mode, tab::Mode::Desktop) {
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
children.push(
|
children.push(menu_item(
|
||||||
menu_item(fl!("change-wallpaper"), Action::CosmicSettingsWallpaper).into(),
|
fl!("change-wallpaper"),
|
||||||
);
|
Action::CosmicSettingsWallpaper,
|
||||||
children.push(
|
));
|
||||||
menu_item(fl!("desktop-appearance"), Action::CosmicSettingsDesktop).into(),
|
children.push(menu_item(
|
||||||
);
|
fl!("desktop-appearance"),
|
||||||
children.push(
|
Action::CosmicSettingsDesktop,
|
||||||
menu_item(fl!("display-settings"), Action::CosmicSettingsDisplays).into(),
|
));
|
||||||
);
|
children.push(menu_item(
|
||||||
|
fl!("display-settings"),
|
||||||
|
Action::CosmicSettingsDisplays,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
// TODO: Nested menu
|
// TODO: Nested menu
|
||||||
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
|
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
|
||||||
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
|
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
|
||||||
children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size));
|
children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size));
|
||||||
if matches!(tab.location, Location::Desktop(..)) {
|
if matches!(tab.location, Location::Desktop(..)) {
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
children.push(
|
children.push(menu_item(
|
||||||
menu_item(fl!("desktop-view-options"), Action::DesktopViewOptions).into(),
|
fl!("desktop-view-options"),
|
||||||
);
|
Action::DesktopViewOptions,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -372,24 +345,25 @@ pub fn context_menu<'a>(
|
||||||
) => {
|
) => {
|
||||||
if selected > 0 {
|
if selected > 0 {
|
||||||
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
|
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
|
||||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
children.push(menu_item(fl!("open"), Action::Open));
|
||||||
}
|
}
|
||||||
if matches!(tab.location, Location::Search(..)) || tab.location.is_recents() {
|
if matches!(tab.location, Location::Search(..)) || tab.location.is_recents() {
|
||||||
children.push(
|
children.push(menu_item(
|
||||||
menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(),
|
fl!("open-item-location"),
|
||||||
);
|
Action::OpenItemLocation,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
children.push(menu_item(fl!("show-details"), Action::Preview).into());
|
children.push(menu_item(fl!("show-details"), Action::Preview));
|
||||||
} else {
|
} else {
|
||||||
if dialog_kind.save() {
|
if dialog_kind.save() {
|
||||||
children.push(menu_item(fl!("new-folder"), Action::NewFolder).into());
|
children.push(menu_item(fl!("new-folder"), Action::NewFolder));
|
||||||
}
|
}
|
||||||
if tab.mode.multiple() {
|
if tab.mode.multiple() {
|
||||||
children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
|
children.push(menu_item(fl!("select-all"), Action::SelectAll));
|
||||||
}
|
}
|
||||||
if !children.is_empty() {
|
if !children.is_empty() {
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
}
|
}
|
||||||
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
|
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
|
||||||
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
|
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
|
||||||
|
|
@ -399,14 +373,14 @@ pub fn context_menu<'a>(
|
||||||
(_, Location::Network(..)) => {
|
(_, Location::Network(..)) => {
|
||||||
if selected > 0 {
|
if selected > 0 {
|
||||||
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
|
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
|
||||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
children.push(menu_item(fl!("open"), Action::Open));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if tab.mode.multiple() {
|
if tab.mode.multiple() {
|
||||||
children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
|
children.push(menu_item(fl!("select-all"), Action::SelectAll));
|
||||||
}
|
}
|
||||||
if !children.is_empty() {
|
if !children.is_empty() {
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
}
|
}
|
||||||
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
|
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
|
||||||
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
|
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
|
||||||
|
|
@ -415,18 +389,20 @@ pub fn context_menu<'a>(
|
||||||
}
|
}
|
||||||
(_, Location::Trash | Location::Search(SearchLocation::Trash, ..)) => {
|
(_, Location::Trash | Location::Search(SearchLocation::Trash, ..)) => {
|
||||||
if tab.mode.multiple() {
|
if tab.mode.multiple() {
|
||||||
children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
|
children.push(menu_item(fl!("select-all"), Action::SelectAll));
|
||||||
}
|
}
|
||||||
if !children.is_empty() {
|
if !children.is_empty() {
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
}
|
}
|
||||||
if selected > 0 {
|
if selected > 0 {
|
||||||
children.push(menu_item(fl!("show-details"), Action::Preview).into());
|
children.push(menu_item(fl!("show-details"), Action::Preview));
|
||||||
children.push(divider::horizontal::light().into());
|
children.push(menu::Item::Divider);
|
||||||
children
|
children.push(menu_item(
|
||||||
.push(menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into());
|
fl!("restore-from-trash"),
|
||||||
children.push(divider::horizontal::light().into());
|
Action::RestoreFromTrash,
|
||||||
children.push(menu_item(fl!("delete-permanently"), Action::Delete).into());
|
));
|
||||||
|
children.push(menu::Item::Divider);
|
||||||
|
children.push(menu_item(fl!("delete-permanently"), Action::Delete));
|
||||||
} else {
|
} else {
|
||||||
// TODO: Nested menu
|
// TODO: Nested menu
|
||||||
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
|
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
|
||||||
|
|
@ -436,26 +412,11 @@ pub fn context_menu<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
container(cosmic::widget::menu::menu_column::MenuColumn::with_children(children))
|
let key_binds: HashMap<KeyBind, TabAction> = key_binds
|
||||||
.padding(1)
|
.iter()
|
||||||
//TODO: move style to libcosmic
|
.map(|(key_bind, action)| (key_bind.clone(), TabAction(*action)))
|
||||||
.style(|theme| {
|
.collect();
|
||||||
let cosmic = theme.cosmic();
|
menu::items(&key_binds, children)
|
||||||
let component = &cosmic.background(theme.transparent);
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dialog_menu(
|
pub fn dialog_menu(
|
||||||
|
|
|
||||||
180
src/tab.rs
180
src/tab.rs
|
|
@ -1742,11 +1742,11 @@ impl fmt::Debug for TaskWrapper {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Action(Action),
|
Action(Action),
|
||||||
|
Surface(cosmic::surface::Action),
|
||||||
AddNetworkDrive,
|
AddNetworkDrive,
|
||||||
AddToSidebar(PathBuf),
|
AddToSidebar(PathBuf),
|
||||||
AutoScroll(Option<f32>),
|
AutoScroll(Option<f32>),
|
||||||
ChangeLocation(String, Location, Option<Vec<PathBuf>>),
|
ChangeLocation(String, Location, Option<Vec<PathBuf>>),
|
||||||
ContextMenu(Option<Point>, Option<window::Id>),
|
|
||||||
Delete(Vec<PathBuf>),
|
Delete(Vec<PathBuf>),
|
||||||
DropFiles(PathBuf, ClipboardPaste),
|
DropFiles(PathBuf, ClipboardPaste),
|
||||||
ClearRecents,
|
ClearRecents,
|
||||||
|
|
@ -1777,7 +1777,8 @@ pub enum Message {
|
||||||
ClickRelease(Option<usize>),
|
ClickRelease(Option<usize>),
|
||||||
Config(TabConfig),
|
Config(TabConfig),
|
||||||
ContextAction(Action),
|
ContextAction(Action),
|
||||||
ContextMenu(Option<Point>, Option<window::Id>),
|
RightClickBackground,
|
||||||
|
Surface(cosmic::surface::Action),
|
||||||
LocationContextMenuPoint(Option<Point>),
|
LocationContextMenuPoint(Option<Point>),
|
||||||
LocationContextMenuIndex(Option<Point>, Option<usize>),
|
LocationContextMenuIndex(Option<Point>, Option<usize>),
|
||||||
LocationMenuAction(LocationMenuAction),
|
LocationMenuAction(LocationMenuAction),
|
||||||
|
|
@ -2807,7 +2808,6 @@ pub struct Tab {
|
||||||
pub location_title: String,
|
pub location_title: String,
|
||||||
pub location_context_menu_point: Option<Point>,
|
pub location_context_menu_point: Option<Point>,
|
||||||
pub location_context_menu_index: Option<usize>,
|
pub location_context_menu_index: Option<usize>,
|
||||||
pub context_menu: Option<Point>,
|
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
pub scroll_opt: Option<AbsoluteOffset>,
|
pub scroll_opt: Option<AbsoluteOffset>,
|
||||||
pub size_opt: Cell<Option<Size>>,
|
pub size_opt: Cell<Option<Size>>,
|
||||||
|
|
@ -2952,7 +2952,6 @@ impl Tab {
|
||||||
location,
|
location,
|
||||||
location_ancestors,
|
location_ancestors,
|
||||||
location_title,
|
location_title,
|
||||||
context_menu: None,
|
|
||||||
location_context_menu_point: None,
|
location_context_menu_point: None,
|
||||||
location_context_menu_index: None,
|
location_context_menu_index: None,
|
||||||
mode: Mode::App,
|
mode: Mode::App,
|
||||||
|
|
@ -3495,7 +3494,6 @@ impl Tab {
|
||||||
self.location = location.normalize();
|
self.location = location.normalize();
|
||||||
self.location_ancestors = self.location.ancestors();
|
self.location_ancestors = self.location.ancestors();
|
||||||
self.location_title = self.location.title();
|
self.location_title = self.location.title();
|
||||||
self.context_menu = None;
|
|
||||||
self.edit_location = None;
|
self.edit_location = None;
|
||||||
self.items_opt = None;
|
self.items_opt = None;
|
||||||
//TODO: remember scroll by location?
|
//TODO: remember scroll by location?
|
||||||
|
|
@ -3540,7 +3538,6 @@ impl Tab {
|
||||||
let mut history_i_opt = None;
|
let mut history_i_opt = None;
|
||||||
let mod_ctrl = modifiers.contains(Modifiers::CTRL) && self.mode.multiple();
|
let mod_ctrl = modifiers.contains(Modifiers::CTRL) && self.mode.multiple();
|
||||||
let mod_shift = modifiers.contains(Modifiers::SHIFT) && self.mode.multiple();
|
let mod_shift = modifiers.contains(Modifiers::SHIFT) && self.mode.multiple();
|
||||||
let last_context_menu = self.context_menu;
|
|
||||||
match message {
|
match message {
|
||||||
Message::AddNetworkDrive => {
|
Message::AddNetworkDrive => {
|
||||||
commands.push(Command::AddNetworkDrive);
|
commands.push(Command::AddNetworkDrive);
|
||||||
|
|
@ -3575,7 +3572,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
if click_i_opt != self.clicked.take() {
|
if click_i_opt != self.clicked.take() {
|
||||||
self.context_menu = None;
|
|
||||||
self.location_context_menu_index = None;
|
self.location_context_menu_index = None;
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
for (i, item) in items.iter_mut().enumerate() {
|
for (i, item) in items.iter_mut().enumerate() {
|
||||||
|
|
@ -3618,7 +3614,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
Message::Click(click_i_opt) => {
|
Message::Click(click_i_opt) => {
|
||||||
self.selected_clicked = false;
|
self.selected_clicked = false;
|
||||||
self.context_menu = None;
|
|
||||||
self.edit_location = None;
|
self.edit_location = None;
|
||||||
self.location_context_menu_index = None;
|
self.location_context_menu_index = None;
|
||||||
if click_i_opt.is_none() {
|
if click_i_opt.is_none() {
|
||||||
|
|
@ -3762,24 +3757,17 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ContextAction(action) => {
|
Message::ContextAction(action) => {
|
||||||
// Close context menu
|
|
||||||
self.context_menu = None;
|
|
||||||
|
|
||||||
commands.push(Command::Action(action));
|
commands.push(Command::Action(action));
|
||||||
}
|
}
|
||||||
Message::RunContextAction(action) => {
|
Message::RunContextAction(action) => {
|
||||||
self.context_menu = None;
|
|
||||||
|
|
||||||
commands.push(Command::RunContextAction(action));
|
commands.push(Command::RunContextAction(action));
|
||||||
}
|
}
|
||||||
Message::ContextMenu(point_opt, _) => {
|
Message::RightClickBackground => {
|
||||||
self.edit_location = None;
|
self.edit_location = None;
|
||||||
self.context_menu = point_opt;
|
|
||||||
self.location_context_menu_index = None;
|
self.location_context_menu_index = None;
|
||||||
|
|
||||||
//TODO: hack for clearing selecting when right clicking empty space
|
//TODO: hack for clearing selecting when right clicking empty space
|
||||||
if self.context_menu.is_some()
|
if self.last_right_click.take().is_none()
|
||||||
&& self.last_right_click.take().is_none()
|
|
||||||
&& let Some(ref mut items) = self.items_opt
|
&& let Some(ref mut items) = self.items_opt
|
||||||
{
|
{
|
||||||
for item in items.iter_mut() {
|
for item in items.iter_mut() {
|
||||||
|
|
@ -3787,12 +3775,13 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::Surface(action) => {
|
||||||
|
commands.push(Command::Surface(action));
|
||||||
|
}
|
||||||
Message::LocationContextMenuPoint(point_opt) => {
|
Message::LocationContextMenuPoint(point_opt) => {
|
||||||
self.context_menu = None;
|
|
||||||
self.location_context_menu_point = point_opt;
|
self.location_context_menu_point = point_opt;
|
||||||
}
|
}
|
||||||
Message::LocationContextMenuIndex(p, index_opt) => {
|
Message::LocationContextMenuIndex(p, index_opt) => {
|
||||||
self.context_menu = None;
|
|
||||||
self.location_context_menu_point = p;
|
self.location_context_menu_point = p;
|
||||||
self.location_context_menu_index = index_opt;
|
self.location_context_menu_index = index_opt;
|
||||||
}
|
}
|
||||||
|
|
@ -3849,7 +3838,6 @@ impl Tab {
|
||||||
Message::Drag(rect_opt) => {
|
Message::Drag(rect_opt) => {
|
||||||
self.watch_drag = false;
|
self.watch_drag = false;
|
||||||
if let Some(rect) = rect_opt {
|
if let Some(rect) = rect_opt {
|
||||||
self.context_menu = None;
|
|
||||||
self.location_context_menu_index = None;
|
self.location_context_menu_index = None;
|
||||||
if self.mode.multiple() {
|
if self.mode.multiple() {
|
||||||
self.select_rect(rect, mod_ctrl, mod_shift);
|
self.select_rect(rect, mod_ctrl, mod_shift);
|
||||||
|
|
@ -4972,16 +4960,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update context menu popup
|
|
||||||
if self.context_menu != last_context_menu {
|
|
||||||
if last_context_menu.is_some() {
|
|
||||||
commands.push(Command::ContextMenu(None, self.window_id));
|
|
||||||
}
|
|
||||||
if let Some(point) = self.context_menu {
|
|
||||||
commands.push(Command::ContextMenu(Some(point), self.window_id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commands
|
commands
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5878,18 +5856,13 @@ impl Tab {
|
||||||
.height(Length::Fixed(item_height as f32))
|
.height(Length::Fixed(item_height as f32))
|
||||||
.width(Length::Fixed(item_width as f32));
|
.width(Length::Fixed(item_width as f32));
|
||||||
for button in buttons {
|
for button in buttons {
|
||||||
if self.context_menu.is_some() {
|
column = column.push(
|
||||||
column = column.push(button);
|
mouse_area::MouseArea::new(button)
|
||||||
} else {
|
.on_right_press_no_capture()
|
||||||
column = column.push(
|
.on_right_press(move |point_opt| {
|
||||||
mouse_area::MouseArea::new(button)
|
Message::RightClick(point_opt, Some(i))
|
||||||
.on_right_press_no_capture()
|
}),
|
||||||
.wayland_on_right_press_window_position()
|
);
|
||||||
.on_right_press(move |point_opt| {
|
|
||||||
Message::RightClick(point_opt, Some(i))
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let column: Element<Message> =
|
let column: Element<Message> =
|
||||||
|
|
@ -6294,39 +6267,33 @@ impl Tab {
|
||||||
.spacing(space_xxs)
|
.spacing(space_xxs)
|
||||||
};
|
};
|
||||||
|
|
||||||
let button = |row| {
|
let button =
|
||||||
let mouse_area = crate::mouse_area::MouseArea::new(
|
|row| {
|
||||||
widget::button::custom(row)
|
let mouse_area = crate::mouse_area::MouseArea::new(
|
||||||
.width(Length::Fill)
|
widget::button::custom(row)
|
||||||
.id(item.button_id.clone())
|
.width(Length::Fill)
|
||||||
.padding([0, space_xxs])
|
.id(item.button_id.clone())
|
||||||
.class(button_style(
|
.padding([0, space_xxs])
|
||||||
item.selected,
|
.class(button_style(
|
||||||
item.highlighted,
|
item.selected,
|
||||||
item.cut,
|
item.highlighted,
|
||||||
true,
|
item.cut,
|
||||||
true,
|
true,
|
||||||
false,
|
true,
|
||||||
)),
|
false,
|
||||||
)
|
)),
|
||||||
.on_press(move |_| Message::Click(Some(i)))
|
)
|
||||||
.on_double_click(move |_| Message::DoubleClick(Some(i)))
|
.on_press(move |_| Message::Click(Some(i)))
|
||||||
.on_release(move |_| Message::ClickRelease(Some(i)))
|
.on_double_click(move |_| Message::DoubleClick(Some(i)))
|
||||||
.on_middle_press(move |_| Message::MiddleClick(i))
|
.on_release(move |_| Message::ClickRelease(Some(i)))
|
||||||
.on_enter(move || Message::HighlightActivate(i))
|
.on_middle_press(move |_| Message::MiddleClick(i))
|
||||||
.on_exit(move || Message::HighlightDeactivate(i));
|
.on_enter(move || Message::HighlightActivate(i))
|
||||||
|
.on_exit(move || Message::HighlightDeactivate(i));
|
||||||
|
|
||||||
if self.context_menu.is_some() {
|
mouse_area.on_right_press_no_capture().on_right_press(
|
||||||
mouse_area
|
move |point_opt| Message::RightClick(point_opt, Some(i)),
|
||||||
} else {
|
)
|
||||||
mouse_area
|
};
|
||||||
.on_right_press_no_capture()
|
|
||||||
.wayland_on_right_press_window_position()
|
|
||||||
.on_right_press(move |point_opt| {
|
|
||||||
Message::RightClick(point_opt, Some(i))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let button_row = button(row.into());
|
let button_row = button(row.into());
|
||||||
let button_row: Element<_> = if item.metadata.is_dir()
|
let button_row: Element<_> = if item.metadata.is_dir()
|
||||||
|
|
@ -6550,51 +6517,47 @@ impl Tab {
|
||||||
.on_back_press(move |_point_opt| Message::GoPrevious)
|
.on_back_press(move |_point_opt| Message::GoPrevious)
|
||||||
.on_forward_press(move |_point_opt| Message::GoNext)
|
.on_forward_press(move |_point_opt| Message::GoNext)
|
||||||
.on_scroll(|delta| respond_to_scroll_direction(delta, modifiers))
|
.on_scroll(|delta| respond_to_scroll_direction(delta, modifiers))
|
||||||
.on_right_press(move |p| {
|
.on_right_press(|_| Message::RightClickBackground);
|
||||||
Message::ContextMenu(
|
|
||||||
if self.context_menu.is_some() { None } else { p },
|
|
||||||
self.window_id,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.wayland_on_right_press_window_position();
|
|
||||||
|
|
||||||
let mut popover = widget::popover(mouse_area);
|
let items_area: Element<'_, Message> = if can_scroll {
|
||||||
if let Some(point) = self.context_menu
|
// FIXME: new responsive widget will remove the state from the scrollable
|
||||||
&& (!cfg!(feature = "wayland") || !crate::is_wayland())
|
// id_container with custom id forces the state to be extracted in a diff
|
||||||
{
|
// pre-processing step
|
||||||
let context_menu = menu::context_menu(
|
widget::id_container(
|
||||||
|
widget::scrollable(mouse_area)
|
||||||
|
.id(self.scrollable_id.clone())
|
||||||
|
.on_scroll(Message::Scroll)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill),
|
||||||
|
widget::Id::new(format!("{}-scrollable", self.scrollable_id)),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
mouse_area.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap the scrollable, not its content, so the popup anchors in window coordinates
|
||||||
|
let mut context_menu = widget::context_menu(
|
||||||
|
items_area,
|
||||||
|
Some(menu::context_menu(
|
||||||
self,
|
self,
|
||||||
key_binds,
|
key_binds,
|
||||||
modifiers,
|
modifiers,
|
||||||
clipboard_paste_available,
|
clipboard_paste_available,
|
||||||
context_actions,
|
context_actions,
|
||||||
);
|
)),
|
||||||
popover = popover
|
)
|
||||||
.popup(context_menu)
|
.item_width(cosmic::widget::menu::ItemWidth::Uniform(360))
|
||||||
.position(widget::popover::Position::Point(point));
|
.on_surface_action(Message::Surface);
|
||||||
|
if let Some(window_id) = self.window_id {
|
||||||
|
context_menu = context_menu.window_id(window_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut tab_column = widget::column::with_capacity(3);
|
let mut tab_column = widget::column::with_capacity(3);
|
||||||
if let Some(location_view) = location_view_opt {
|
if let Some(location_view) = location_view_opt {
|
||||||
tab_column = tab_column.push(location_view);
|
tab_column = tab_column.push(location_view);
|
||||||
}
|
}
|
||||||
if can_scroll {
|
tab_column = tab_column.push(context_menu);
|
||||||
tab_column = tab_column.push(
|
|
||||||
// FIXME: new responsive widget will remove the state from the scrollable
|
|
||||||
// id_container with custom id forces the state to be extracted in a diff
|
|
||||||
// pre-processing step
|
|
||||||
widget::id_container(
|
|
||||||
widget::scrollable(popover)
|
|
||||||
.id(self.scrollable_id.clone())
|
|
||||||
.on_scroll(Message::Scroll)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.height(Length::Fill),
|
|
||||||
widget::Id::new(format!("{}-scrollable", self.scrollable_id)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
tab_column = tab_column.push(popover);
|
|
||||||
}
|
|
||||||
match &self.location {
|
match &self.location {
|
||||||
Location::Trash | Location::Search(SearchLocation::Trash, ..) => {
|
Location::Trash | Location::Search(SearchLocation::Trash, ..) => {
|
||||||
if let Some(items) = self.items_opt()
|
if let Some(items) = self.items_opt()
|
||||||
|
|
@ -7156,8 +7119,7 @@ impl Tab {
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
|
|
||||||
// Acquire semaphore permit
|
// Acquire semaphore permit
|
||||||
let _permit =
|
let _permit = THUMB_SEMAPHORE.acquire().await.unwrap();
|
||||||
THUMB_SEMAPHORE.acquire().await.unwrap();
|
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue