diff --git a/src/app.rs b/src/app.rs index 094982c..6957242 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1740,6 +1740,17 @@ impl Application for App { tab::Command::OpenInNewTab(path) => { 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) => { commands.push(scrollable::scroll_to(id, offset)); } diff --git a/src/dialog.rs b/src/dialog.rs index a1a2348..c1204a3 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -641,6 +641,9 @@ impl Application for App { tab::Command::OpenInNewTab(_path) => { log::warn!("OpenInNewTab not supported in dialog"); } + tab::Command::OpenInNewWindow(_path) => { + log::warn!("OpenInNewWindow not supported in dialog"); + } tab::Command::Scroll(id, offset) => { commands.push(scrollable::scroll_to(id, offset)); } diff --git a/src/menu.rs b/src/menu.rs index 8d081ff..33ed6af 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -11,6 +11,7 @@ use cosmic::{ }; use std::collections::HashMap; +use crate::tab::LocationMenuAction; use crate::{ app::{Action, Message}, config::TabConfig, @@ -94,7 +95,9 @@ pub fn context_menu<'a>( match tab.location { Location::Path(_) | Location::Search(_, _) => { 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 { children.push(menu_item(fl!("open-with"), Action::OpenWith).into()); if selected_dir == 1 { @@ -102,8 +105,12 @@ pub fn context_menu<'a>( .push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into()); } } - children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab).into()); - children.push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into()); + // All selected items are directories + 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(menu_item(fl!("rename"), Action::Rename).into()); children.push(menu_item(fl!("cut"), Action::Cut).into()); @@ -255,3 +262,38 @@ pub fn menu_bar<'a>( .spacing(4.0) .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() +} diff --git a/src/tab.rs b/src/tab.rs index 7da78d3..2d8b4f3 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1,6 +1,7 @@ use cosmic::iced::clipboard::dnd::DndAction; use cosmic::iced::Border; use cosmic::iced_core::widget::tree; +use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::{vertical_space, Id, Widget}; use cosmic::{ @@ -538,6 +539,7 @@ pub enum Command { FocusTextInput(widget::Id), OpenFile(PathBuf), OpenInNewTab(PathBuf), + OpenInNewWindow(PathBuf), Scroll(widget::Id, AbsoluteOffset), DropFiles(PathBuf, ClipboardPaste), Timeout(Duration, Message), @@ -553,8 +555,11 @@ pub enum Message { Config(TabConfig), ContextAction(Action), ContextMenu(Option), + LocationContextMenu(Option<(Point, usize)>), + LocationMenuAction(LocationMenuAction), Drag(Option), EditLocation(Option), + OpenInNewTab(PathBuf), EmptyTrash, GoNext, GoPrevious, @@ -583,6 +588,20 @@ pub enum Message { 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)] pub enum ItemMetadata { Path { @@ -805,6 +824,7 @@ impl HeadingOptions { pub struct Tab { //TODO: make more items private pub location: Location, + pub location_context_menu: Option<(Point, usize)>, pub context_menu: Option, pub dialog: Option, pub scroll_opt: Option, @@ -830,6 +850,7 @@ impl Tab { Self { location, context_menu: None, + location_context_menu: None, dialog: None, scroll_opt: None, size_opt: Cell::new(None), @@ -1131,6 +1152,7 @@ impl Tab { return commands; } self.context_menu = None; + self.location_context_menu = None; if let Some(ref mut items) = self.items_opt { for (i, item) in items.iter_mut().enumerate() { if mod_ctrl { @@ -1174,6 +1196,7 @@ impl Tab { Message::Click(click_i_opt) => { self.selected_clicked = false; self.context_menu = None; + self.location_context_menu = None; if click_i_opt.is_none() { self.clicked = click_i_opt; } @@ -1249,9 +1272,37 @@ impl Tab { Message::ContextMenu(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 { Some(rect) => { self.context_menu = None; + self.location_context_menu = None; self.select_rect(rect, mod_ctrl, mod_shift); if self.select_focus.take().is_some() { // Unfocus currently focused button @@ -1266,6 +1317,9 @@ impl Tab { } self.edit_location = edit_location; } + Message::OpenInNewTab(path) => { + commands.push(Command::OpenInNewTab(path)); + } Message::EmptyTrash => { commands.push(Command::EmptyTrash); } @@ -1788,12 +1842,15 @@ impl Tab { //TODO: allow editing other locations } } - } else if let Location::Path(_) = &self.location { + } else if let Location::Path(path) = &self.location { row = row.push( - widget::button(widget::icon::from_name("edit-symbolic").size(16)) - .on_press(Message::EditLocation(Some(self.location.clone()))) - .padding(space_xxs) - .style(theme::Button::Icon), + crate::mouse_area::MouseArea::new( + widget::button(widget::icon::from_name("edit-symbolic").size(16)) + .padding(space_xxs) + .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 { row = row.push( @@ -1815,8 +1872,7 @@ impl Tab { match &self.location { Location::Path(path) | Location::Search(path, ..) => { let home_dir = crate::home_dir(); - for ancestor in path.ancestors() { - let ancestor = ancestor.to_path_buf(); + for (index, ancestor) in path.ancestors().enumerate() { let mut found_home = false; let mut row = widget::row::with_capacity(2) .align_items(Alignment::Center) @@ -1826,8 +1882,11 @@ impl Tab { Some(name) => { if ancestor == home_dir { row = row.push( - widget::icon::icon(folder_icon_symbolic(&ancestor, 16)) - .size(16), + widget::icon::icon(folder_icon_symbolic( + &ancestor.to_path_buf(), + 16, + )) + .size(16), ); found_home = true; fl!("home") @@ -1857,19 +1916,31 @@ impl Tab { row = row.push(widget::text(name)); } - children.push( + let mut mouse_area = crate::mouse_area::MouseArea::new( widget::button(row) .padding(space_xxxs) - .on_press(Message::Location(match &self.location { - Location::Path(_) => Location::Path(ancestor), - Location::Search(_, term) => { - Location::Search(ancestor, term.clone()) - } - other => other.clone(), - })) - .style(theme::Button::Link) - .into(), - ); + .style(theme::Button::Link), + ) + .on_press(move |_| { + Message::Location(match &self.location { + Location::Path(_) => Location::Path(ancestor.to_path_buf()), + Location::Search(_, term) => { + Location::Search(ancestor.to_path_buf(), term.clone()) + } + 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 { break; @@ -1897,7 +1968,15 @@ impl Tab { for child in children { 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 {