diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index 9fed322..f0b7264 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -139,6 +139,8 @@ open-with = Open with owner = Owner group = Group other = Other +toolbar = Toolbar +parent-directory = Parent directory mixed = Mixed ### Mode 0 none = None diff --git a/i18n/fr/cosmic_files.ftl b/i18n/fr/cosmic_files.ftl index e0fa6f9..c7b0675 100644 --- a/i18n/fr/cosmic_files.ftl +++ b/i18n/fr/cosmic_files.ftl @@ -131,6 +131,8 @@ open-with = Ouvrir avec owner = Propriétaire group = Groupe other = Autre +toolbar = Barre d'outils +parent-directory = Dossier parent ### Mode 0 diff --git a/src/app.rs b/src/app.rs index 5c6a491..cd2058a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -77,7 +77,7 @@ use crate::{ }, config::{ AppTheme, Config, DesktopConfig, Favorite, IconSizes, State, TIME_CONFIG_ID, TabConfig, - TimeConfig, TypeToSearch, + TimeConfig, ToolbarItems, TypeToSearch, }, context_action, dialog::{Dialog, DialogKind, DialogMessage, DialogResult, DialogSettings}, @@ -448,6 +448,8 @@ pub enum Message { SearchInput(String), SetShowDetails(bool), SetShowRecents(bool), + /// Yoda: toggle a single toolbar button visibility. + SetToolbar(ToolbarItems), SetTypeToSearch(TypeToSearch), SystemThemeModeChange, Size(window::Id, Size), @@ -2284,6 +2286,49 @@ impl App { .toggler(self.config.show_recents, Message::SetShowRecents) }) .into(), + // Yoda: configure which quick-action buttons show in the + // toolbar under the tab bar. Each toggle maps to one button; + // layout order inside the toolbar is fixed (file ops → + // clipboard → view). + { + let tb = self.config.toolbar; + widget::settings::section() + .title(fl!("toolbar")) + .add(widget::settings::item::builder(fl!("new-folder")) + .toggler(tb.new_folder, move |v| + Message::SetToolbar(ToolbarItems { new_folder: v, ..tb }))) + .add(widget::settings::item::builder(fl!("new-file")) + .toggler(tb.new_file, move |v| + Message::SetToolbar(ToolbarItems { new_file: v, ..tb }))) + .add(widget::settings::item::builder(fl!("rename")) + .toggler(tb.rename, move |v| + Message::SetToolbar(ToolbarItems { rename: v, ..tb }))) + .add(widget::settings::item::builder(fl!("delete")) + .toggler(tb.delete, move |v| + Message::SetToolbar(ToolbarItems { delete: v, ..tb }))) + .add(widget::settings::item::builder(fl!("cut")) + .toggler(tb.cut, move |v| + Message::SetToolbar(ToolbarItems { cut: v, ..tb }))) + .add(widget::settings::item::builder(fl!("copy")) + .toggler(tb.copy, move |v| + Message::SetToolbar(ToolbarItems { copy: v, ..tb }))) + .add(widget::settings::item::builder(fl!("paste")) + .toggler(tb.paste, move |v| + Message::SetToolbar(ToolbarItems { paste: v, ..tb }))) + .add(widget::settings::item::builder(fl!("reload-folder")) + .toggler(tb.reload, move |v| + Message::SetToolbar(ToolbarItems { reload: v, ..tb }))) + .add(widget::settings::item::builder(fl!("show-hidden-files")) + .toggler(tb.toggle_show_hidden, move |v| + Message::SetToolbar(ToolbarItems { toggle_show_hidden: v, ..tb }))) + .add(widget::settings::item::builder(fl!("open-in-terminal")) + .toggler(tb.open_terminal, move |v| + Message::SetToolbar(ToolbarItems { open_terminal: v, ..tb }))) + .add(widget::settings::item::builder(fl!("parent-directory")) + .toggler(tb.location_up, move |v| + Message::SetToolbar(ToolbarItems { location_up: v, ..tb }))) + .into() + }, ]) .into() } @@ -4377,6 +4422,10 @@ impl Application for App { config_set!(show_recents, show_recents); return self.update_config(); } + Message::SetToolbar(toolbar) => { + config_set!(toolbar, toolbar); + return self.update_config(); + } Message::SetTypeToSearch(type_to_search) => { config_set!(type_to_search, type_to_search); return self.update_config(); @@ -6522,13 +6571,18 @@ impl Application for App { } // Yoda: Dolphin-style quick actions toolbar under the headerbar. - // Minimal 6-button set: New folder · Rename · Delete · Cut · Copy · Paste. - // Actions dispatch through Action::message so the keybinding path and - // toolbar path share the same code. + // Items are rendered from self.config.toolbar (ToolbarItems). Order + // is fixed (file ops / clipboard / view toggles); visibility per + // item is configurable from the Settings page. + // Dispatch goes through Action::message so keybindings and toolbar + // share exactly the same code path. { let clipboard_has = self.clipboard_has_content(); + let tb = self.config.toolbar; + let mut buttons: Vec> = Vec::new(); + let tb_btn = - |icon_name: &'static str, label: String, msg: Message, enabled: bool| { + |icon_name: &'static str, label: String, msg: Message, enabled: bool| -> Element<_> { let btn = widget::button::icon( widget::icon::from_name(icon_name).size(16), ); @@ -6538,29 +6592,91 @@ impl Application for App { widget::text::body(label), widget::tooltip::Position::Bottom, ) + .into() }; - let toolbar = widget::row::with_children(vec![ - tb_btn("folder-new-symbolic", fl!("new-folder"), - Action::NewFolder.message(None), true).into(), - tb_btn("pencil-symbolic", fl!("rename"), - Action::Rename.message(None), true).into(), - tb_btn("edit-delete-symbolic", fl!("delete"), - Action::Delete.message(None), true).into(), - widget::divider::vertical::light().height(16).into(), - tb_btn("edit-cut-symbolic", fl!("cut"), - Action::Cut.message(None), true).into(), - tb_btn("edit-copy-symbolic", fl!("copy"), - Action::Copy.message(None), true).into(), - tb_btn("edit-paste-symbolic", fl!("paste"), - Action::Paste.message(None), clipboard_has).into(), - ]) - .spacing(space_xxs) - .align_y(Alignment::Center); - tab_column = tab_column.push( - widget::container(toolbar) - .width(Length::Fill) - .padding([space_xxs, space_s]), - ); + let divider = || -> Element<_> { + widget::divider::vertical::light().height(16).into() + }; + + // Group 1: location + let mut added_any = false; + if tb.location_up { + buttons.push(tb_btn("go-up-symbolic", fl!("parent-directory"), + Action::LocationUp.message(None), true)); + added_any = true; + } + if tb.reload { + buttons.push(tb_btn("view-refresh-symbolic", fl!("reload-folder"), + Action::Reload.message(None), true)); + added_any = true; + } + + // Group 2: create / edit + let mut group_started = false; + for (enabled, icon, label, msg) in [ + (tb.new_folder, "folder-new-symbolic", fl!("new-folder"), + Action::NewFolder.message(None)), + (tb.new_file, "document-new-symbolic", fl!("new-file"), + Action::NewFile.message(None)), + (tb.rename, "pencil-symbolic", fl!("rename"), + Action::Rename.message(None)), + (tb.delete, "edit-delete-symbolic", fl!("delete"), + Action::Delete.message(None)), + ] { + if enabled { + if !group_started && added_any { buttons.push(divider()); } + buttons.push(tb_btn(icon, label, msg, true)); + group_started = true; + added_any = true; + } + } + + // Group 3: clipboard + let mut group_started = false; + for (enabled, icon, label, msg, avail) in [ + (tb.cut, "edit-cut-symbolic", fl!("cut"), + Action::Cut.message(None), true), + (tb.copy, "edit-copy-symbolic", fl!("copy"), + Action::Copy.message(None), true), + (tb.paste, "edit-paste-symbolic", fl!("paste"), + Action::Paste.message(None), clipboard_has), + ] { + if enabled { + if !group_started && added_any { buttons.push(divider()); } + buttons.push(tb_btn(icon, label, msg, avail)); + group_started = true; + added_any = true; + } + } + + // Group 4: view toggles + misc + let mut group_started = false; + for (enabled, icon, label, msg) in [ + (tb.toggle_show_hidden, "view-reveal-symbolic", + fl!("show-hidden-files"), + Action::ToggleShowHidden.message(None)), + (tb.open_terminal, "utilities-terminal-symbolic", + fl!("open-in-terminal"), + Action::OpenTerminal.message(None)), + ] { + if enabled { + if !group_started && added_any { buttons.push(divider()); } + buttons.push(tb_btn(icon, label, msg, true)); + group_started = true; + added_any = true; + } + } + + if added_any { + let toolbar = widget::row::with_children(buttons) + .spacing(space_xxs) + .align_y(Alignment::Center); + tab_column = tab_column.push( + widget::container(toolbar) + .width(Length::Fill) + .padding([space_xxs, space_s]), + ); + } } let entity = self.tab_model.active(); diff --git a/src/config.rs b/src/config.rs index 0ce9c22..b773d93 100644 --- a/src/config.rs +++ b/src/config.rs @@ -172,6 +172,10 @@ pub struct Config { pub show_details: bool, pub show_recents: bool, pub tab: TabConfig, + /// Yoda: Dolphin-style quick actions toolbar under the tab bar. + /// Each bool toggles one button; order in the UI is fixed (logical + /// grouping file-ops then clipboard then view toggles). + pub toolbar: ToolbarItems, pub type_to_search: TypeToSearch, } @@ -236,11 +240,48 @@ impl Default for Config { show_details: false, show_recents: true, tab: TabConfig::default(), + toolbar: ToolbarItems::default(), type_to_search: TypeToSearch::Recursive, } } } +/// Yoda: visibility toggles for each quick-action toolbar button. +/// Default = the original "minimal 6" set (new_folder, rename, delete, +/// cut, copy, paste). Other items default to false so users opt in. +#[derive(Clone, Copy, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct ToolbarItems { + pub new_folder: bool, + pub new_file: bool, + pub rename: bool, + pub delete: bool, + pub cut: bool, + pub copy: bool, + pub paste: bool, + pub reload: bool, + pub toggle_show_hidden: bool, + pub open_terminal: bool, + pub location_up: bool, +} + +impl Default for ToolbarItems { + fn default() -> Self { + Self { + new_folder: true, + new_file: false, + rename: true, + delete: true, + cut: true, + copy: true, + paste: true, + reload: false, + toggle_show_hidden: false, + open_terminal: false, + location_up: false, + } + } +} + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, CosmicConfigEntry, Deserialize, Serialize)] #[serde(default)] pub struct DesktopConfig {