From 1aba09a4d028ace585ecceb9f6ce5af73b49afb2 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 10 Jan 2024 11:39:04 -0700 Subject: [PATCH] Add menu --- i18n/en/cosmic_files.ftl | 23 +++++++++- src/main.rs | 91 +++++++++++++++++----------------------- src/menu.rs | 82 +++++++++++++++++++++++++++++++++++- 3 files changed, 140 insertions(+), 56 deletions(-) diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index aa40180..4aea73f 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -20,8 +20,27 @@ light = Light # Context menu new-file = New file new-folder = New folder +move-to-trash = Move to trash +restore-from-trash = Restore from trash + +# Menu + +## File +file = File +new-tab = New tab +new-window = New window +close-tab = Close tab +quit = Quit + +## Edit +edit = Edit +cut = Cut copy = Copy paste = Paste select-all = Select all -move-to-trash = Move to trash -restore-from-trash = Restore from trash + +## View +view = View +grid-view = Grid view +list-view = List view +menu-settings = Settings... diff --git a/src/main.rs b/src/main.rs index 01d452d..b383f63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -106,6 +106,7 @@ pub struct Flags { #[derive(Clone, Copy, Debug)] pub enum Action { Copy, + Cut, MoveToTrash, NewFile, NewFolder, @@ -121,6 +122,7 @@ impl Action { pub fn message(self, entity: segmented_button::Entity) -> Message { match self { Action::Copy => Message::Copy(Some(entity)), + Action::Cut => Message::Cut(Some(entity)), Action::MoveToTrash => Message::MoveToTrash(Some(entity)), Action::NewFile => Message::NewFile(Some(entity)), Action::NewFolder => Message::NewFolder(Some(entity)), @@ -141,6 +143,7 @@ pub enum Message { AppTheme(AppTheme), Config(Config), Copy(Option), + Cut(Option), KeyModifiers(Modifiers), MoveToTrash(Option), NewFile(Option), @@ -150,13 +153,15 @@ pub enum Message { SelectAll(Option), SystemThemeModeChange(cosmic_theme::ThemeMode), TabActivate(segmented_button::Entity), - TabClose(segmented_button::Entity), + TabClose(Option), TabContextAction(segmented_button::Entity, Action), TabContextMenu(segmented_button::Entity, Option), - TabMessage(segmented_button::Entity, tab::Message), + TabMessage(Option, tab::Message), TabNew, TabRescan(segmented_button::Entity, Vec), ToggleContextPage(ContextPage), + WindowClose, + WindowNew, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -403,10 +408,7 @@ impl Application for App { let location_opt = self.nav_model.data::(entity).clone(); if let Some(location) = location_opt { - let message = Message::TabMessage( - self.tab_model.active(), - tab::Message::Location(location.clone()), - ); + let message = Message::TabMessage(None, tab::Message::Location(location.clone())); return self.update(message); } @@ -434,6 +436,9 @@ impl Application for App { Message::Copy(entity_opt) => { log::warn!("TODO: COPY"); } + Message::Cut(entity_opt) => { + log::warn!("TODO: CUT"); + } Message::KeyModifiers(modifiers) => { self.modifiers = modifiers; } @@ -474,7 +479,9 @@ impl Application for App { self.tab_model.activate(entity); return self.update_title(); } - Message::TabClose(entity) => { + Message::TabClose(entity_opt) => { + let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); + // Activate closest item if let Some(position) = self.tab_model.position(entity) { if position > 0 { @@ -518,7 +525,9 @@ impl Application for App { // Disable side context page self.core.window.show_context = false; } - Message::TabMessage(entity, tab_message) => { + Message::TabMessage(entity_opt, tab_message) => { + let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); + let mut update_opt = None; match self.tab_model.data_mut::(entity) { Some(tab) => { @@ -561,6 +570,20 @@ impl Application for App { } self.set_context_title(context_page.title()); } + Message::WindowClose => { + return window::close(window::Id::MAIN); + } + Message::WindowNew => match env::current_exe() { + Ok(exe) => match process::Command::new(&exe).spawn() { + Ok(_child) => {} + Err(err) => { + log::error!("failed to execute {:?}: {}", exe, err); + } + }, + Err(err) => { + log::error!("failed to get current executable path: {}", err); + } + }, } Command::none() @@ -580,16 +603,10 @@ impl Application for App { fn header_start(&self) -> Vec> { let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing; - let active = self.tab_model.active(); - - //TODO: dynamically show items vec![row![ - widget::button(widget::icon::from_name("list-add-symbolic").size(16).icon()) - .on_press(Message::TabNew) - .padding(space_xxs) - .style(style::Button::Icon), + menu::menu_bar(), widget::button(widget::icon::from_name("go-up-symbolic").size(16).icon()) - .on_press(Message::TabMessage(active, tab::Message::Parent)) + .on_press(Message::TabMessage(None, tab::Message::Parent)) .padding(space_xxs) .style(style::Button::Icon), ] @@ -597,38 +614,6 @@ impl Application for App { .into()] } - fn header_end(&self) -> Vec> { - let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing; - - let active = self.tab_model.active(); - let current_view = if let Some(tab) = self.tab_model.data::(active) { - tab.view - } else { - tab::View::Grid - }; - let (view, view_icon) = match current_view { - tab::View::Grid => (tab::View::List, "view-list-symbolic"), - tab::View::List => (tab::View::Grid, "view-grid-symbolic"), - }; - - vec![row![ - widget::button(widget::icon::from_name(view_icon).size(16).icon()) - .on_press(Message::TabMessage(active, tab::Message::View(view))) - .padding(space_xxs) - .style(style::Button::Icon), - widget::button( - widget::icon::from_name("preferences-system-symbolic") - .size(16) - .icon() - ) - .on_press(Message::ToggleContextPage(ContextPage::Settings)) - .padding(space_xxs) - .style(style::Button::Icon) - ] - .align_items(Alignment::Center) - .into()] - } - /// Creates a view after each update. fn view(&self) -> Element { let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing; @@ -642,7 +627,7 @@ impl Application for App { .button_height(32) .button_spacing(space_xxs) .on_activate(Message::TabActivate) - .on_close(Message::TabClose), + .on_close(|entity| Message::TabClose(Some(entity))), ) .style(style::Container::Background) .width(Length::Fill), @@ -654,9 +639,11 @@ impl Application for App { Some(tab) => { let mut mouse_area = mouse_area::MouseArea::new( tab.view(self.core()) - .map(move |message| Message::TabMessage(entity, message)), + .map(move |message| Message::TabMessage(Some(entity), message)), ) - .on_press(move |_point_opt| Message::TabMessage(entity, tab::Message::Click(None))); + .on_press(move |_point_opt| { + Message::TabMessage(Some(entity), tab::Message::Click(None)) + }); if tab.context_menu.is_some() { mouse_area = mouse_area .on_right_press(move |_point_opt| Message::TabContextMenu(entity, None)); @@ -720,7 +707,7 @@ impl Application for App { modifiers, }) => { if modifiers == Modifiers::CTRL { - Some(Message::Copy(None)) + Some(Message::Cut(None)) } else { None } diff --git a/src/menu.rs b/src/menu.rs index fc6a057..7362223 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -4,11 +4,15 @@ use cosmic::{ //TODO: export iced::widget::horizontal_rule in cosmic::widget iced::{widget::horizontal_rule, Alignment, Background, Length}, theme, - widget::{self, segmented_button}, + widget::{ + self, + menu::{ItemHeight, ItemWidth, MenuBar, MenuTree}, + segmented_button, + }, Element, }; -use crate::{fl, Action, Location, Message, Tab}; +use crate::{fl, tab, Action, ContextPage, Location, Message, Tab}; macro_rules! menu_button { ($($x:expr),+ $(,)?) => ( @@ -42,6 +46,7 @@ pub fn context_menu<'a>(entity: segmented_button::Entity, tab: &Tab) -> Element< children.push(menu_action(fl!("new-folder"), Action::NewFolder).into()); children.push(horizontal_rule(1).into()); if selected > 0 { + children.push(menu_action(fl!("cut"), Action::Cut).into()); children.push(menu_action(fl!("copy"), Action::Copy).into()); children.push(menu_action(fl!("paste"), Action::Paste).into()); } @@ -81,3 +86,76 @@ pub fn context_menu<'a>(entity: segmented_button::Entity, tab: &Tab) -> Element< .width(Length::Fixed(240.0)) .into() } + +pub fn menu_bar<'a>() -> Element<'a, Message> { + //TODO: port to libcosmic + let menu_root = |label| { + widget::button(widget::text(label)) + .padding([4, 12]) + .style(theme::Button::MenuRoot) + }; + + let find_key = |message: &Message| -> String { + //TODO: hotkey config + String::new() + }; + + let menu_item = |label, message| { + let key = find_key(&message); + MenuTree::new( + menu_button!( + widget::text(label), + widget::horizontal_space(Length::Fill), + widget::text(key) + ) + .on_press(message), + ) + }; + + MenuBar::new(vec![ + MenuTree::with_children( + menu_root(fl!("file")), + vec![ + menu_item(fl!("new-tab"), Message::TabNew), + menu_item(fl!("new-window"), Message::WindowNew), + menu_item(fl!("new-file"), Message::NewFile(None)), + menu_item(fl!("new-folder"), Message::NewFolder(None)), + MenuTree::new(horizontal_rule(1)), + menu_item(fl!("close-tab"), Message::TabClose(None)), + MenuTree::new(horizontal_rule(1)), + menu_item(fl!("quit"), Message::WindowClose), + ], + ), + MenuTree::with_children( + menu_root(fl!("edit")), + vec![ + menu_item(fl!("cut"), Message::Cut(None)), + menu_item(fl!("copy"), Message::Copy(None)), + menu_item(fl!("paste"), Message::Paste(None)), + menu_item(fl!("select-all"), Message::SelectAll(None)), + ], + ), + MenuTree::with_children( + menu_root(fl!("view")), + vec![ + menu_item( + fl!("grid-view"), + Message::TabMessage(None, tab::Message::View(tab::View::Grid)), + ), + menu_item( + fl!("list-view"), + Message::TabMessage(None, tab::Message::View(tab::View::List)), + ), + MenuTree::new(horizontal_rule(1)), + menu_item( + fl!("menu-settings"), + Message::ToggleContextPage(ContextPage::Settings), + ), + ], + ), + ]) + .item_height(ItemHeight::Dynamic(40)) + .item_width(ItemWidth::Uniform(240)) + .spacing(4.0) + .into() +}