Add context menu for breadcrumbs
* Open in new tab * Open in new window
This commit is contained in:
parent
0971f6f20f
commit
7d3ae07067
4 changed files with 159 additions and 24 deletions
11
src/app.rs
11
src/app.rs
|
|
@ -1740,6 +1740,17 @@ impl Application for App {
|
||||||
tab::Command::OpenInNewTab(path) => {
|
tab::Command::OpenInNewTab(path) => {
|
||||||
commands.push(self.open_tab(Location::Path(path.clone())));
|
commands.push(self.open_tab(Location::Path(path.clone())));
|
||||||
}
|
}
|
||||||
|
tab::Command::OpenInNewWindow(path) => match env::current_exe() {
|
||||||
|
Ok(exe) => match process::Command::new(&exe).arg(path).spawn() {
|
||||||
|
Ok(_child) => {}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("failed to execute {:?}: {}", exe, err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("failed to get current executable path: {}", err);
|
||||||
|
}
|
||||||
|
},
|
||||||
tab::Command::Scroll(id, offset) => {
|
tab::Command::Scroll(id, offset) => {
|
||||||
commands.push(scrollable::scroll_to(id, offset));
|
commands.push(scrollable::scroll_to(id, offset));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -641,6 +641,9 @@ impl Application for App {
|
||||||
tab::Command::OpenInNewTab(_path) => {
|
tab::Command::OpenInNewTab(_path) => {
|
||||||
log::warn!("OpenInNewTab not supported in dialog");
|
log::warn!("OpenInNewTab not supported in dialog");
|
||||||
}
|
}
|
||||||
|
tab::Command::OpenInNewWindow(_path) => {
|
||||||
|
log::warn!("OpenInNewWindow not supported in dialog");
|
||||||
|
}
|
||||||
tab::Command::Scroll(id, offset) => {
|
tab::Command::Scroll(id, offset) => {
|
||||||
commands.push(scrollable::scroll_to(id, offset));
|
commands.push(scrollable::scroll_to(id, offset));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
48
src/menu.rs
48
src/menu.rs
|
|
@ -11,6 +11,7 @@ use cosmic::{
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::tab::LocationMenuAction;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{Action, Message},
|
app::{Action, Message},
|
||||||
config::TabConfig,
|
config::TabConfig,
|
||||||
|
|
@ -94,7 +95,9 @@ pub fn context_menu<'a>(
|
||||||
match tab.location {
|
match tab.location {
|
||||||
Location::Path(_) | Location::Search(_, _) => {
|
Location::Path(_) | Location::Search(_, _) => {
|
||||||
if selected > 0 {
|
if selected > 0 {
|
||||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
|
||||||
|
children.push(menu_item(fl!("open"), Action::Open).into());
|
||||||
|
}
|
||||||
if selected == 1 {
|
if selected == 1 {
|
||||||
children.push(menu_item(fl!("open-with"), Action::OpenWith).into());
|
children.push(menu_item(fl!("open-with"), Action::OpenWith).into());
|
||||||
if selected_dir == 1 {
|
if selected_dir == 1 {
|
||||||
|
|
@ -102,8 +105,12 @@ pub fn context_menu<'a>(
|
||||||
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
|
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab).into());
|
// All selected items are directories
|
||||||
children.push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into());
|
if selected == selected_dir {
|
||||||
|
children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab).into());
|
||||||
|
children
|
||||||
|
.push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into());
|
||||||
|
}
|
||||||
children.push(horizontal_rule(1).into());
|
children.push(horizontal_rule(1).into());
|
||||||
children.push(menu_item(fl!("rename"), Action::Rename).into());
|
children.push(menu_item(fl!("rename"), Action::Rename).into());
|
||||||
children.push(menu_item(fl!("cut"), Action::Cut).into());
|
children.push(menu_item(fl!("cut"), Action::Cut).into());
|
||||||
|
|
@ -255,3 +262,38 @@ pub fn menu_bar<'a>(
|
||||||
.spacing(4.0)
|
.spacing(4.0)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Message> {
|
||||||
|
let children = vec![
|
||||||
|
menu_button!(widget::text(fl!("open-in-new-tab")))
|
||||||
|
.on_press(tab::Message::LocationMenuAction(
|
||||||
|
LocationMenuAction::OpenInNewTab(ancestor_index),
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
|
menu_button!(widget::text(fl!("open-in-new-window")))
|
||||||
|
.on_press(tab::Message::LocationMenuAction(
|
||||||
|
LocationMenuAction::OpenInNewWindow(ancestor_index),
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
widget::container(widget::column::with_children(children))
|
||||||
|
.padding(1)
|
||||||
|
.style(theme::Container::custom(|theme| {
|
||||||
|
let cosmic = theme.cosmic();
|
||||||
|
let component = &cosmic.background.component;
|
||||||
|
widget::container::Appearance {
|
||||||
|
icon_color: Some(component.on.into()),
|
||||||
|
text_color: Some(component.on.into()),
|
||||||
|
background: Some(Background::Color(component.base.into())),
|
||||||
|
border: Border {
|
||||||
|
radius: 8.0.into(),
|
||||||
|
width: 1.0,
|
||||||
|
color: component.divider.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.width(Length::Fixed(240.0))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
|
||||||
121
src/tab.rs
121
src/tab.rs
|
|
@ -1,6 +1,7 @@
|
||||||
use cosmic::iced::clipboard::dnd::DndAction;
|
use cosmic::iced::clipboard::dnd::DndAction;
|
||||||
use cosmic::iced::Border;
|
use cosmic::iced::Border;
|
||||||
use cosmic::iced_core::widget::tree;
|
use cosmic::iced_core::widget::tree;
|
||||||
|
use cosmic::widget::menu::action::MenuAction;
|
||||||
use cosmic::widget::menu::key_bind::KeyBind;
|
use cosmic::widget::menu::key_bind::KeyBind;
|
||||||
use cosmic::widget::{vertical_space, Id, Widget};
|
use cosmic::widget::{vertical_space, Id, Widget};
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
|
|
@ -538,6 +539,7 @@ pub enum Command {
|
||||||
FocusTextInput(widget::Id),
|
FocusTextInput(widget::Id),
|
||||||
OpenFile(PathBuf),
|
OpenFile(PathBuf),
|
||||||
OpenInNewTab(PathBuf),
|
OpenInNewTab(PathBuf),
|
||||||
|
OpenInNewWindow(PathBuf),
|
||||||
Scroll(widget::Id, AbsoluteOffset),
|
Scroll(widget::Id, AbsoluteOffset),
|
||||||
DropFiles(PathBuf, ClipboardPaste),
|
DropFiles(PathBuf, ClipboardPaste),
|
||||||
Timeout(Duration, Message),
|
Timeout(Duration, Message),
|
||||||
|
|
@ -553,8 +555,11 @@ pub enum Message {
|
||||||
Config(TabConfig),
|
Config(TabConfig),
|
||||||
ContextAction(Action),
|
ContextAction(Action),
|
||||||
ContextMenu(Option<Point>),
|
ContextMenu(Option<Point>),
|
||||||
|
LocationContextMenu(Option<(Point, usize)>),
|
||||||
|
LocationMenuAction(LocationMenuAction),
|
||||||
Drag(Option<Rectangle>),
|
Drag(Option<Rectangle>),
|
||||||
EditLocation(Option<Location>),
|
EditLocation(Option<Location>),
|
||||||
|
OpenInNewTab(PathBuf),
|
||||||
EmptyTrash,
|
EmptyTrash,
|
||||||
GoNext,
|
GoNext,
|
||||||
GoPrevious,
|
GoPrevious,
|
||||||
|
|
@ -583,6 +588,20 @@ pub enum Message {
|
||||||
ZoomOut,
|
ZoomOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum LocationMenuAction {
|
||||||
|
OpenInNewTab(usize),
|
||||||
|
OpenInNewWindow(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuAction for LocationMenuAction {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn message(&self) -> Self::Message {
|
||||||
|
Message::LocationMenuAction(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ItemMetadata {
|
pub enum ItemMetadata {
|
||||||
Path {
|
Path {
|
||||||
|
|
@ -805,6 +824,7 @@ impl HeadingOptions {
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
//TODO: make more items private
|
//TODO: make more items private
|
||||||
pub location: Location,
|
pub location: Location,
|
||||||
|
pub location_context_menu: Option<(Point, usize)>,
|
||||||
pub context_menu: Option<Point>,
|
pub context_menu: Option<Point>,
|
||||||
pub dialog: Option<DialogKind>,
|
pub dialog: Option<DialogKind>,
|
||||||
pub scroll_opt: Option<AbsoluteOffset>,
|
pub scroll_opt: Option<AbsoluteOffset>,
|
||||||
|
|
@ -830,6 +850,7 @@ impl Tab {
|
||||||
Self {
|
Self {
|
||||||
location,
|
location,
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
|
location_context_menu: None,
|
||||||
dialog: None,
|
dialog: None,
|
||||||
scroll_opt: None,
|
scroll_opt: None,
|
||||||
size_opt: Cell::new(None),
|
size_opt: Cell::new(None),
|
||||||
|
|
@ -1131,6 +1152,7 @@ impl Tab {
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
self.context_menu = None;
|
self.context_menu = None;
|
||||||
|
self.location_context_menu = 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() {
|
||||||
if mod_ctrl {
|
if mod_ctrl {
|
||||||
|
|
@ -1174,6 +1196,7 @@ 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.context_menu = None;
|
||||||
|
self.location_context_menu = None;
|
||||||
if click_i_opt.is_none() {
|
if click_i_opt.is_none() {
|
||||||
self.clicked = click_i_opt;
|
self.clicked = click_i_opt;
|
||||||
}
|
}
|
||||||
|
|
@ -1249,9 +1272,37 @@ impl Tab {
|
||||||
Message::ContextMenu(point_opt) => {
|
Message::ContextMenu(point_opt) => {
|
||||||
self.context_menu = point_opt;
|
self.context_menu = point_opt;
|
||||||
}
|
}
|
||||||
|
Message::LocationContextMenu(point_path_opt) => {
|
||||||
|
self.location_context_menu = point_path_opt;
|
||||||
|
}
|
||||||
|
Message::LocationMenuAction(action) => {
|
||||||
|
self.location_context_menu = None;
|
||||||
|
let path_for_index = |ancestor_index| {
|
||||||
|
match self.location {
|
||||||
|
Location::Path(ref path) => Some(path),
|
||||||
|
Location::Search(ref path, _) => Some(path),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
.and_then(|path| path.ancestors().nth(ancestor_index))
|
||||||
|
.map(|path| path.to_path_buf())
|
||||||
|
};
|
||||||
|
match action {
|
||||||
|
LocationMenuAction::OpenInNewTab(ancestor_index) => {
|
||||||
|
if let Some(path) = path_for_index(ancestor_index) {
|
||||||
|
commands.push(Command::OpenInNewTab(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LocationMenuAction::OpenInNewWindow(ancestor_index) => {
|
||||||
|
if let Some(path) = path_for_index(ancestor_index) {
|
||||||
|
commands.push(Command::OpenInNewWindow(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::Drag(rect_opt) => match rect_opt {
|
Message::Drag(rect_opt) => match rect_opt {
|
||||||
Some(rect) => {
|
Some(rect) => {
|
||||||
self.context_menu = None;
|
self.context_menu = None;
|
||||||
|
self.location_context_menu = None;
|
||||||
self.select_rect(rect, mod_ctrl, mod_shift);
|
self.select_rect(rect, mod_ctrl, mod_shift);
|
||||||
if self.select_focus.take().is_some() {
|
if self.select_focus.take().is_some() {
|
||||||
// Unfocus currently focused button
|
// Unfocus currently focused button
|
||||||
|
|
@ -1266,6 +1317,9 @@ impl Tab {
|
||||||
}
|
}
|
||||||
self.edit_location = edit_location;
|
self.edit_location = edit_location;
|
||||||
}
|
}
|
||||||
|
Message::OpenInNewTab(path) => {
|
||||||
|
commands.push(Command::OpenInNewTab(path));
|
||||||
|
}
|
||||||
Message::EmptyTrash => {
|
Message::EmptyTrash => {
|
||||||
commands.push(Command::EmptyTrash);
|
commands.push(Command::EmptyTrash);
|
||||||
}
|
}
|
||||||
|
|
@ -1788,12 +1842,15 @@ impl Tab {
|
||||||
//TODO: allow editing other locations
|
//TODO: allow editing other locations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Location::Path(_) = &self.location {
|
} else if let Location::Path(path) = &self.location {
|
||||||
row = row.push(
|
row = row.push(
|
||||||
widget::button(widget::icon::from_name("edit-symbolic").size(16))
|
crate::mouse_area::MouseArea::new(
|
||||||
.on_press(Message::EditLocation(Some(self.location.clone())))
|
widget::button(widget::icon::from_name("edit-symbolic").size(16))
|
||||||
.padding(space_xxs)
|
.padding(space_xxs)
|
||||||
.style(theme::Button::Icon),
|
.style(theme::Button::Icon),
|
||||||
|
)
|
||||||
|
.on_press(move |_| Message::EditLocation(Some(self.location.clone())))
|
||||||
|
.on_middle_press(move |_| Message::OpenInNewTab(path.clone())),
|
||||||
);
|
);
|
||||||
} else if let Location::Search(_, term) = &self.location {
|
} else if let Location::Search(_, term) = &self.location {
|
||||||
row = row.push(
|
row = row.push(
|
||||||
|
|
@ -1815,8 +1872,7 @@ impl Tab {
|
||||||
match &self.location {
|
match &self.location {
|
||||||
Location::Path(path) | Location::Search(path, ..) => {
|
Location::Path(path) | Location::Search(path, ..) => {
|
||||||
let home_dir = crate::home_dir();
|
let home_dir = crate::home_dir();
|
||||||
for ancestor in path.ancestors() {
|
for (index, ancestor) in path.ancestors().enumerate() {
|
||||||
let ancestor = ancestor.to_path_buf();
|
|
||||||
let mut found_home = false;
|
let mut found_home = false;
|
||||||
let mut row = widget::row::with_capacity(2)
|
let mut row = widget::row::with_capacity(2)
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
|
|
@ -1826,8 +1882,11 @@ impl Tab {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
if ancestor == home_dir {
|
if ancestor == home_dir {
|
||||||
row = row.push(
|
row = row.push(
|
||||||
widget::icon::icon(folder_icon_symbolic(&ancestor, 16))
|
widget::icon::icon(folder_icon_symbolic(
|
||||||
.size(16),
|
&ancestor.to_path_buf(),
|
||||||
|
16,
|
||||||
|
))
|
||||||
|
.size(16),
|
||||||
);
|
);
|
||||||
found_home = true;
|
found_home = true;
|
||||||
fl!("home")
|
fl!("home")
|
||||||
|
|
@ -1857,19 +1916,31 @@ impl Tab {
|
||||||
row = row.push(widget::text(name));
|
row = row.push(widget::text(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
children.push(
|
let mut mouse_area = crate::mouse_area::MouseArea::new(
|
||||||
widget::button(row)
|
widget::button(row)
|
||||||
.padding(space_xxxs)
|
.padding(space_xxxs)
|
||||||
.on_press(Message::Location(match &self.location {
|
.style(theme::Button::Link),
|
||||||
Location::Path(_) => Location::Path(ancestor),
|
)
|
||||||
Location::Search(_, term) => {
|
.on_press(move |_| {
|
||||||
Location::Search(ancestor, term.clone())
|
Message::Location(match &self.location {
|
||||||
}
|
Location::Path(_) => Location::Path(ancestor.to_path_buf()),
|
||||||
other => other.clone(),
|
Location::Search(_, term) => {
|
||||||
}))
|
Location::Search(ancestor.to_path_buf(), term.clone())
|
||||||
.style(theme::Button::Link)
|
}
|
||||||
.into(),
|
other => other.clone(),
|
||||||
);
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if self.location_context_menu.is_some() {
|
||||||
|
mouse_area = mouse_area
|
||||||
|
.on_right_press(move |_point_opt| Message::LocationContextMenu(None))
|
||||||
|
} else {
|
||||||
|
mouse_area = mouse_area.on_right_press(move |point_opt| {
|
||||||
|
Message::LocationContextMenu(point_opt.map(|point| (point, index)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
children.push(mouse_area.into());
|
||||||
|
|
||||||
if found_home {
|
if found_home {
|
||||||
break;
|
break;
|
||||||
|
|
@ -1897,7 +1968,15 @@ impl Tab {
|
||||||
for child in children {
|
for child in children {
|
||||||
row = row.push(child);
|
row = row.push(child);
|
||||||
}
|
}
|
||||||
row.into()
|
|
||||||
|
let mut popover = widget::popover(row);
|
||||||
|
if let Some((point, ancestor_index)) = self.location_context_menu {
|
||||||
|
popover = popover
|
||||||
|
.popup(menu::location_context_menu(ancestor_index))
|
||||||
|
.position(widget::popover::Position::Point(point))
|
||||||
|
}
|
||||||
|
|
||||||
|
popover.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn empty_view(&self, has_hidden: bool) -> Element<Message> {
|
pub fn empty_view(&self, has_hidden: bool) -> Element<Message> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue