yoda: phase 2 — customizable toolbar (settings toggles per button)
Phase 1 shipped a fixed 6-button toolbar. Phase 2 moves visibility to the config so users pick which buttons appear. Config (config.rs): - new ToolbarItems struct (CosmicConfigEntry) with one bool per button - Config.toolbar: ToolbarItems, default = 'minimal 6' set from phase 1 (new_folder, rename, delete, cut, copy, paste) + 5 extras off (new_file, reload, toggle_show_hidden, open_terminal, location_up) Rendering (view()): - iterate through self.config.toolbar fields in fixed logical order (location → create/edit → clipboard → view toggles) - dividers inserted only between non-empty groups - whole toolbar hidden if every button is off (no empty container) Settings page (settings()): - new 'Toolbar' section with one toggler per button, wired through Message::SetToolbar(ToolbarItems) which persists via config_set! i18n (en + fr): - added 'toolbar' + 'parent-directory' strings - reused existing new-folder / new-file / rename / delete / cut / copy / paste / reload-folder / show-hidden-files / open-in-terminal All actions dispatch through Action::message so keybindings and toolbar share one code path.
This commit is contained in:
parent
8b51af1632
commit
33a5c8ff99
4 changed files with 188 additions and 27 deletions
|
|
@ -139,6 +139,8 @@ open-with = Open with
|
||||||
owner = Owner
|
owner = Owner
|
||||||
group = Group
|
group = Group
|
||||||
other = Other
|
other = Other
|
||||||
|
toolbar = Toolbar
|
||||||
|
parent-directory = Parent directory
|
||||||
mixed = Mixed
|
mixed = Mixed
|
||||||
### Mode 0
|
### Mode 0
|
||||||
none = None
|
none = None
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,8 @@ open-with = Ouvrir avec
|
||||||
owner = Propriétaire
|
owner = Propriétaire
|
||||||
group = Groupe
|
group = Groupe
|
||||||
other = Autre
|
other = Autre
|
||||||
|
toolbar = Barre d'outils
|
||||||
|
parent-directory = Dossier parent
|
||||||
|
|
||||||
### Mode 0
|
### Mode 0
|
||||||
|
|
||||||
|
|
|
||||||
170
src/app.rs
170
src/app.rs
|
|
@ -77,7 +77,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
config::{
|
config::{
|
||||||
AppTheme, Config, DesktopConfig, Favorite, IconSizes, State, TIME_CONFIG_ID, TabConfig,
|
AppTheme, Config, DesktopConfig, Favorite, IconSizes, State, TIME_CONFIG_ID, TabConfig,
|
||||||
TimeConfig, TypeToSearch,
|
TimeConfig, ToolbarItems, TypeToSearch,
|
||||||
},
|
},
|
||||||
context_action,
|
context_action,
|
||||||
dialog::{Dialog, DialogKind, DialogMessage, DialogResult, DialogSettings},
|
dialog::{Dialog, DialogKind, DialogMessage, DialogResult, DialogSettings},
|
||||||
|
|
@ -448,6 +448,8 @@ pub enum Message {
|
||||||
SearchInput(String),
|
SearchInput(String),
|
||||||
SetShowDetails(bool),
|
SetShowDetails(bool),
|
||||||
SetShowRecents(bool),
|
SetShowRecents(bool),
|
||||||
|
/// Yoda: toggle a single toolbar button visibility.
|
||||||
|
SetToolbar(ToolbarItems),
|
||||||
SetTypeToSearch(TypeToSearch),
|
SetTypeToSearch(TypeToSearch),
|
||||||
SystemThemeModeChange,
|
SystemThemeModeChange,
|
||||||
Size(window::Id, Size),
|
Size(window::Id, Size),
|
||||||
|
|
@ -2284,6 +2286,49 @@ impl App {
|
||||||
.toggler(self.config.show_recents, Message::SetShowRecents)
|
.toggler(self.config.show_recents, Message::SetShowRecents)
|
||||||
})
|
})
|
||||||
.into(),
|
.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()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -4377,6 +4422,10 @@ impl Application for App {
|
||||||
config_set!(show_recents, show_recents);
|
config_set!(show_recents, show_recents);
|
||||||
return self.update_config();
|
return self.update_config();
|
||||||
}
|
}
|
||||||
|
Message::SetToolbar(toolbar) => {
|
||||||
|
config_set!(toolbar, toolbar);
|
||||||
|
return self.update_config();
|
||||||
|
}
|
||||||
Message::SetTypeToSearch(type_to_search) => {
|
Message::SetTypeToSearch(type_to_search) => {
|
||||||
config_set!(type_to_search, type_to_search);
|
config_set!(type_to_search, type_to_search);
|
||||||
return self.update_config();
|
return self.update_config();
|
||||||
|
|
@ -6522,13 +6571,18 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yoda: Dolphin-style quick actions toolbar under the headerbar.
|
// Yoda: Dolphin-style quick actions toolbar under the headerbar.
|
||||||
// Minimal 6-button set: New folder · Rename · Delete · Cut · Copy · Paste.
|
// Items are rendered from self.config.toolbar (ToolbarItems). Order
|
||||||
// Actions dispatch through Action::message so the keybinding path and
|
// is fixed (file ops / clipboard / view toggles); visibility per
|
||||||
// toolbar path share the same code.
|
// 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 clipboard_has = self.clipboard_has_content();
|
||||||
|
let tb = self.config.toolbar;
|
||||||
|
let mut buttons: Vec<Element<_>> = Vec::new();
|
||||||
|
|
||||||
let tb_btn =
|
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(
|
let btn = widget::button::icon(
|
||||||
widget::icon::from_name(icon_name).size(16),
|
widget::icon::from_name(icon_name).size(16),
|
||||||
);
|
);
|
||||||
|
|
@ -6538,29 +6592,91 @@ impl Application for App {
|
||||||
widget::text::body(label),
|
widget::text::body(label),
|
||||||
widget::tooltip::Position::Bottom,
|
widget::tooltip::Position::Bottom,
|
||||||
)
|
)
|
||||||
|
.into()
|
||||||
};
|
};
|
||||||
let toolbar = widget::row::with_children(vec![
|
let divider = || -> Element<_> {
|
||||||
tb_btn("folder-new-symbolic", fl!("new-folder"),
|
widget::divider::vertical::light().height(16).into()
|
||||||
Action::NewFolder.message(None), true).into(),
|
};
|
||||||
tb_btn("pencil-symbolic", fl!("rename"),
|
|
||||||
Action::Rename.message(None), true).into(),
|
// Group 1: location
|
||||||
tb_btn("edit-delete-symbolic", fl!("delete"),
|
let mut added_any = false;
|
||||||
Action::Delete.message(None), true).into(),
|
if tb.location_up {
|
||||||
widget::divider::vertical::light().height(16).into(),
|
buttons.push(tb_btn("go-up-symbolic", fl!("parent-directory"),
|
||||||
tb_btn("edit-cut-symbolic", fl!("cut"),
|
Action::LocationUp.message(None), true));
|
||||||
Action::Cut.message(None), true).into(),
|
added_any = true;
|
||||||
tb_btn("edit-copy-symbolic", fl!("copy"),
|
}
|
||||||
Action::Copy.message(None), true).into(),
|
if tb.reload {
|
||||||
tb_btn("edit-paste-symbolic", fl!("paste"),
|
buttons.push(tb_btn("view-refresh-symbolic", fl!("reload-folder"),
|
||||||
Action::Paste.message(None), clipboard_has).into(),
|
Action::Reload.message(None), true));
|
||||||
])
|
added_any = true;
|
||||||
.spacing(space_xxs)
|
}
|
||||||
.align_y(Alignment::Center);
|
|
||||||
tab_column = tab_column.push(
|
// Group 2: create / edit
|
||||||
widget::container(toolbar)
|
let mut group_started = false;
|
||||||
.width(Length::Fill)
|
for (enabled, icon, label, msg) in [
|
||||||
.padding([space_xxs, space_s]),
|
(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();
|
let entity = self.tab_model.active();
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,10 @@ pub struct Config {
|
||||||
pub show_details: bool,
|
pub show_details: bool,
|
||||||
pub show_recents: bool,
|
pub show_recents: bool,
|
||||||
pub tab: TabConfig,
|
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,
|
pub type_to_search: TypeToSearch,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,11 +240,48 @@ impl Default for Config {
|
||||||
show_details: false,
|
show_details: false,
|
||||||
show_recents: true,
|
show_recents: true,
|
||||||
tab: TabConfig::default(),
|
tab: TabConfig::default(),
|
||||||
|
toolbar: ToolbarItems::default(),
|
||||||
type_to_search: TypeToSearch::Recursive,
|
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)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, CosmicConfigEntry, Deserialize, Serialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct DesktopConfig {
|
pub struct DesktopConfig {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue