Settings overhaul, part of #321

This commit is contained in:
Jeremy Soller 2024-10-02 14:53:24 -06:00
parent 9a02c0f9f4
commit 60743ed251
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
33 changed files with 170 additions and 503 deletions

View file

@ -181,15 +181,9 @@ impl Action {
Action::TabNew => Message::TabNew,
Action::TabNext => Message::TabNext,
Action::TabPrev => Message::TabPrev,
Action::TabViewGrid => {
Message::TabMessage(entity_opt, tab::Message::View(tab::View::Grid))
}
Action::TabViewList => {
Message::TabMessage(entity_opt, tab::Message::View(tab::View::List))
}
Action::ToggleFoldersFirst => {
Message::TabMessage(entity_opt, tab::Message::ToggleFoldersFirst)
}
Action::TabViewGrid => Message::TabView(entity_opt, tab::View::Grid),
Action::TabViewList => Message::TabView(entity_opt, tab::View::List),
Action::ToggleFoldersFirst => Message::ToggleFoldersFirst,
Action::ToggleShowHidden => {
Message::TabMessage(entity_opt, tab::Message::ToggleShowHidden)
}
@ -198,9 +192,9 @@ impl Action {
}
Action::WindowClose => Message::WindowClose,
Action::WindowNew => Message::WindowNew,
Action::ZoomDefault => Message::TabMessage(entity_opt, tab::Message::ZoomDefault),
Action::ZoomIn => Message::TabMessage(entity_opt, tab::Message::ZoomIn),
Action::ZoomOut => Message::TabMessage(entity_opt, tab::Message::ZoomOut),
Action::ZoomDefault => Message::ZoomDefault(entity_opt),
Action::ZoomIn => Message::ZoomIn(entity_opt),
Action::ZoomOut => Message::ZoomOut(entity_opt),
Action::Recents => Message::Recents,
}
}
@ -312,12 +306,17 @@ pub enum Message {
TabMessage(Option<Entity>, tab::Message),
TabNew,
TabRescan(Entity, Location, Vec<tab::Item>, Option<PathBuf>),
TabView(Option<Entity>, tab::View),
ToggleContextPage(ContextPage),
ToggleFoldersFirst,
Undo(usize),
UndoTrash(widget::ToastId, Arc<[PathBuf]>),
UndoTrashStart(Vec<TrashItem>),
WindowClose,
WindowNew,
ZoomDefault(Option<Entity>),
ZoomIn(Option<Entity>),
ZoomOut(Option<Entity>),
DndHoverLocTimeout(Location),
DndHoverTabTimeout(Entity),
DndEnterNav(Entity),
@ -459,9 +458,6 @@ pub struct App {
config: Config,
mode: Mode,
app_themes: Vec<String>,
default_view: Vec<String>,
sort_by_names: Vec<String>,
sort_direction: Vec<String>,
context_page: ContextPage,
dialog_pages: VecDeque<DialogPage>,
dialog_text_input: widget::Id,
@ -622,7 +618,20 @@ impl App {
fn update_config(&mut self) -> Command<Message> {
self.update_nav_model();
cosmic::app::command::set_theme(self.config.app_theme.theme())
// Tabs are collected first to placate the borrowck
let tabs: Vec<_> = self.tab_model.iter().collect();
// Update main conf and each tab with the new config
let commands: Vec<_> = std::iter::once(cosmic::app::command::set_theme(
self.config.app_theme.theme(),
))
.chain(tabs.into_iter().map(|entity| {
self.update(Message::TabMessage(
Some(entity),
tab::Message::Config(self.config.tab),
))
}))
.collect();
Command::batch(commands)
}
fn activate_nav_model_location(&mut self, location: &Location) {
@ -924,7 +933,7 @@ impl App {
let progress_bar_height = Length::Fixed(4.0);
if !self.pending_operations.is_empty() {
let mut section = widget::settings::view_section(fl!("pending"));
let mut section = widget::settings::section().title(fl!("pending"));
for (_id, (op, progress)) in self.pending_operations.iter().rev() {
section = section.add(widget::column::with_children(vec![
widget::text(op.pending_text()).into(),
@ -937,7 +946,7 @@ impl App {
}
if !self.failed_operations.is_empty() {
let mut section = widget::settings::view_section(fl!("failed"));
let mut section = widget::settings::section().title(fl!("failed"));
for (_id, (op, error)) in self.failed_operations.iter().rev() {
section = section.add(widget::column::with_children(vec![
widget::text(op.pending_text()).into(),
@ -948,7 +957,7 @@ impl App {
}
if !self.complete_operations.is_empty() {
let mut section = widget::settings::view_section(fl!("complete"));
let mut section = widget::settings::section().title(fl!("complete"));
for (_id, op) in self.complete_operations.iter().rev() {
section = section.add(widget::text(op.completed_text()));
}
@ -1003,133 +1012,27 @@ impl App {
fn settings(&self) -> Element<Message> {
// TODO: Should dialog be updated here too?
widget::settings::view_column(vec![
widget::settings::view_section(fl!("appearance"))
.add({
let app_theme_selected = match self.config.app_theme {
AppTheme::Dark => 1,
AppTheme::Light => 2,
AppTheme::System => 0,
};
widget::settings::item::builder(fl!("theme")).control(widget::dropdown(
&self.app_themes,
Some(app_theme_selected),
move |index| {
Message::AppTheme(match index {
1 => AppTheme::Dark,
2 => AppTheme::Light,
_ => AppTheme::System,
})
},
))
})
.add({
let tab_config = self.config.tab.clone();
widget::settings::item::builder(fl!("default-view")).control(widget::dropdown(
&self.default_view,
match tab_config.view {
tab::View::Grid => Some(0),
tab::View::List => Some(1),
},
move |index| {
Message::TabConfig(TabConfig {
view: match index {
0 => tab::View::Grid,
_ => tab::View::List,
},
..tab_config
})
},
))
})
.add({
let tab_config = self.config.tab.clone();
let list: u16 = tab_config.icon_sizes.list.into();
widget::settings::item::builder(fl!("icon-size-list"))
.description(format!("{}%", list))
.control(
widget::slider(50..=500, list, move |list| {
Message::TabConfig(TabConfig {
icon_sizes: IconSizes {
list: NonZeroU16::new(list).unwrap(),
..tab_config.icon_sizes
},
..tab_config
})
})
.step(25u16),
)
})
.add({
let tab_config = self.config.tab.clone();
let grid: u16 = tab_config.icon_sizes.grid.into();
widget::settings::item::builder(fl!("icon-size-grid"))
.description(format!("{}%", grid))
.control(
widget::slider(50..=500, grid, move |grid| {
Message::TabConfig(TabConfig {
icon_sizes: IconSizes {
grid: NonZeroU16::new(grid).unwrap(),
..tab_config.icon_sizes
},
..tab_config
})
})
.step(25u16),
)
})
.add({
let tab_config = self.config.tab;
let sort_by_selected = tab_config.sort_name as _;
widget::settings::item::builder(fl!("sorting-name")).control(widget::dropdown(
&self.sort_by_names,
Some(sort_by_selected),
move |index| {
Message::TabConfig(TabConfig {
sort_name: match index {
0 => HeadingOptions::Name,
1 => HeadingOptions::Modified,
2 => HeadingOptions::Size,
_ => HeadingOptions::Name,
},
..tab_config
})
},
))
})
.add({
let tab_config = self.config.tab;
// Ascending is true. Descending is false
let direction = tab_config.sort_direction.into();
widget::settings::item::builder(fl!("direction")).control(widget::dropdown(
&self.sort_direction,
Some(direction),
move |index| {
Message::TabConfig(TabConfig {
sort_direction: index == 1,
..tab_config
})
},
))
})
.into(),
widget::settings::view_section(fl!("settings-tab"))
.add({
let tab_config = self.config.tab.clone();
widget::settings::item::builder(fl!("settings-show-hidden")).toggler(
tab_config.show_hidden,
move |show_hidden| {
Message::TabConfig(TabConfig {
show_hidden,
..tab_config
})
},
)
})
.into(),
])
widget::settings::view_column(vec![widget::settings::section()
.title(fl!("appearance"))
.add({
let app_theme_selected = match self.config.app_theme {
AppTheme::Dark => 1,
AppTheme::Light => 2,
AppTheme::System => 0,
};
widget::settings::item::builder(fl!("theme")).control(widget::dropdown(
&self.app_themes,
Some(app_theme_selected),
move |index| {
Message::AppTheme(match index {
1 => AppTheme::Dark,
2 => AppTheme::Light,
_ => AppTheme::System,
})
},
))
})
.into()])
.into()
}
}
@ -1183,9 +1086,6 @@ impl Application for App {
config: flags.config,
mode: flags.mode,
app_themes,
default_view: vec![fl!("grid-view"), fl!("list-view")],
sort_by_names: HeadingOptions::names(),
sort_direction: vec![fl!("descending"), fl!("ascending")],
context_page: ContextPage::Settings,
dialog_pages: VecDeque::new(),
dialog_text_input: widget::Id::unique(),
@ -1506,8 +1406,8 @@ impl Application for App {
log::warn!("TODO: retry operation {}", id);
}
DialogPage::NetworkAuth {
mounter_key,
uri,
mounter_key: _,
uri: _,
auth,
auth_tx,
} => {
@ -1520,9 +1420,9 @@ impl Application for App {
);
}
DialogPage::NetworkError {
mounter_key,
mounter_key: _,
uri,
error,
error: _,
} => {
//TODO: re-use mounter_key?
return Command::batch([
@ -2248,23 +2148,15 @@ impl Application for App {
}
Message::TabConfig(config) => {
if config != self.config.tab {
// Tabs are collected first to placate the borrowck
let tabs: Vec<_> = self.tab_model.iter().collect();
// Update main conf and each tab with the new config
let commands: Vec<_> = std::iter::once(self.update_config())
.chain(tabs.into_iter().map(|entity| {
let config = config.clone();
self.update(Message::TabMessage(
Some(entity),
tab::Message::Config(config),
))
}))
.collect();
config_set!(tab, config);
return Command::batch(commands);
return self.update_config();
}
}
Message::ToggleFoldersFirst => {
let mut config = self.config.tab;
config.folders_first = !config.folders_first;
return self.update(Message::TabConfig(config));
}
Message::TabMessage(entity_opt, tab_message) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
@ -2429,6 +2321,15 @@ impl Application for App {
_ => (),
}
}
Message::TabView(entity_opt, view) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
tab.config.view = view;
}
let mut config = self.config.tab;
config.view = view;
return self.update(Message::TabConfig(config));
}
Message::ToggleContextPage(context_page) => {
//TODO: ensure context menus are closed
if self.context_page == context_page {
@ -2439,8 +2340,8 @@ impl Application for App {
self.context_page = context_page;
self.set_context_title(self.context_page.title());
}
Message::Undo(id) => {
// TODO;
Message::Undo(_id) => {
// TODO: undo
}
Message::UndoTrash(id, recently_trashed) => {
self.toasts.remove(id);
@ -2497,6 +2398,65 @@ impl Application for App {
log::error!("failed to get current executable path: {}", err);
}
},
Message::ZoomDefault(entity_opt) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
let mut config = self.config.tab;
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
match tab.config.view {
tab::View::List => config.icon_sizes.list = 100.try_into().unwrap(),
tab::View::Grid => config.icon_sizes.grid = 100.try_into().unwrap(),
}
}
return self.update(Message::TabConfig(config));
}
Message::ZoomIn(entity_opt) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
let zoom_in = |size: &mut NonZeroU16, min: u16, max: u16| {
let mut step = min;
while step <= max {
if size.get() < step {
*size = step.try_into().unwrap();
break;
}
step += 25;
}
if size.get() > step {
*size = step.try_into().unwrap();
}
};
let mut config = self.config.tab;
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
match tab.config.view {
tab::View::List => zoom_in(&mut config.icon_sizes.list, 50, 500),
tab::View::Grid => zoom_in(&mut config.icon_sizes.grid, 50, 500),
}
}
return self.update(Message::TabConfig(config));
}
Message::ZoomOut(entity_opt) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
let zoom_out = |size: &mut NonZeroU16, min: u16, max: u16| {
let mut step = max;
while step >= min {
if size.get() > step {
*size = step.try_into().unwrap();
break;
}
step -= 25;
}
if size.get() < step {
*size = step.try_into().unwrap();
}
};
let mut config = self.config.tab;
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
match tab.config.view {
tab::View::List => zoom_out(&mut config.icon_sizes.list, 50, 500),
tab::View::Grid => zoom_out(&mut config.icon_sizes.grid, 50, 500),
}
}
return self.update(Message::TabConfig(config));
}
Message::DndEnterNav(entity) => {
if let Some(location) = self.nav_model.data::<Location>(entity) {
self.nav_dnd_hover = Some((location.clone(), Instant::now()));
@ -3011,8 +2971,8 @@ impl Application for App {
))
}
DialogPage::NetworkError {
mounter_key,
uri,
mounter_key: _,
uri: _,
error,
} => widget::dialog(fl!("network-drive-error"))
.body(error)
@ -3313,7 +3273,7 @@ impl Application for App {
content
}
fn view_window(&self, id: WindowId) -> Element<Self::Message> {
fn view_window(&self, _id: WindowId) -> Element<Self::Message> {
//TODO: distinct views per window?
self.view_main().map(|message| match message {
app::Message::App(app) => app,