Add icon sizes to config

Icon sizes are stored as a zoom percentage. This may need to be updated
by a programmer with better ideas, but it seems a bit nicer than storing
the size in pixels and presenting that to the user.
This commit is contained in:
Josh Megnauth 2024-02-18 02:44:54 -05:00 committed by Jeremy Soller
parent 3167a9936c
commit 1f613860e4
4 changed files with 101 additions and 45 deletions

View file

@ -219,9 +219,12 @@ impl App {
entity: segmented_button::Entity,
location: Location,
) -> Command<Message> {
let icon_sizes = self.config.tab.icon_sizes;
Command::perform(
async move {
match tokio::task::spawn_blocking(move || location.scan()).await {
match tokio::task::spawn_blocking(move || location.scan(icon_sizes))
.await
{
Ok(items) => message::app(Message::TabRescan(entity, items)),
Err(err) => {
log::warn!("failed to rescan: {}", err);
@ -357,7 +360,7 @@ impl App {
if let Some(ref items) = tab.items_opt {
for item in items.iter() {
if item.selected {
children.push(item.property_view(&self.core));
children.push(item.property_view(&self.core, tab.config.icon_sizes));
}
}
}
@ -1123,7 +1126,7 @@ pub(crate) mod test_utils {
use log::{debug, trace};
use tempfile::{tempdir, TempDir};
use crate::{config::TabConfig, tab::Item};
use crate::{config::{TabConfig, IconSizes}, tab::Item};
use super::*;
@ -1274,7 +1277,7 @@ pub(crate) mod test_utils {
// New tab with items
let location = Location::Path(path.to_owned());
let items = location.scan();
let items = location.scan(IconSizes::default());
let mut tab = Tab::new(location, TabConfig::default());
tab.items_opt = Some(items);

View file

@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::num::NonZeroU16;
use cosmic::{
cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry},
theme,
@ -8,6 +10,13 @@ use serde::{Deserialize, Serialize};
pub const CONFIG_VERSION: u64 = 1;
// Default icon sizes
const ICON_SIZE_DIALOG: u16 = 16;
const ICON_SIZE_LIST: u16 = 32;
const ICON_SIZE_GRID: u16 = 64;
// TODO: 5 is an arbitrary number. Maybe there's a better icon size max
const ICON_SCALE_MAX: u16 = 5;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum AppTheme {
Dark,
@ -51,14 +60,52 @@ pub struct TabConfig {
pub show_hidden: bool,
// TODO: Other possible options
// pub sort_by: fn(&PathBuf, &PathBuf) -> Ordering,
// Icon handle sizes
// icon_size_dialog: u16,
// icon_size_list: u16,
// icon_size_grid: u16,
// Icon handle zoom percents
pub icon_sizes: IconSizes,
}
impl Default for TabConfig {
fn default() -> Self {
Self { show_hidden: false }
Self {
show_hidden: false,
icon_sizes: IconSizes::default(),
}
}
}
macro_rules! percent {
($perc:expr, $pixel:ident) => {
(($perc.get() as f32 * $pixel as f32) / 100.).clamp(1., ($pixel * ICON_SCALE_MAX) as _)
};
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, CosmicConfigEntry, Deserialize, Serialize)]
pub struct IconSizes {
pub dialog: NonZeroU16,
pub list: NonZeroU16,
pub grid: NonZeroU16,
}
impl Default for IconSizes {
fn default() -> Self {
Self {
dialog: 100.try_into().unwrap(),
list: 100.try_into().unwrap(),
grid: 100.try_into().unwrap(),
}
}
}
impl IconSizes {
pub fn dialog(&self) -> u16 {
percent!(self.dialog, ICON_SIZE_DIALOG) as _
}
pub fn list(&self) -> u16 {
percent!(self.list, ICON_SIZE_LIST) as _
}
pub fn grid(&self) -> u16 {
percent!(self.grid, ICON_SIZE_GRID) as _
}
}

View file

@ -209,9 +209,10 @@ struct App {
impl App {
fn rescan_tab(&self) -> Command<Message> {
let location = self.tab.location.clone();
let icon_sizes = self.tab.config.icon_sizes;
Command::perform(
async move {
match tokio::task::spawn_blocking(move || location.scan()).await {
match tokio::task::spawn_blocking(move || location.scan(icon_sizes)).await {
Ok(items) => message::app(Message::TabRescan(items)),
Err(err) => {
log::warn!("failed to rescan: {}", err);

View file

@ -23,13 +23,14 @@ use std::{
time::{Duration, Instant},
};
use crate::{config::TabConfig, dialog::DialogKind, fl, mime_icon::mime_icon};
use crate::{
config::{IconSizes, TabConfig},
dialog::DialogKind,
fl,
mime_icon::mime_icon,
};
const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
//TODO: configurable
const ICON_SIZE_DIALOG: u16 = 16;
const ICON_SIZE_LIST: u16 = 32;
const ICON_SIZE_GRID: u16 = 64;
static SPECIAL_DIRS: Lazy<HashMap<PathBuf, &'static str>> = Lazy::new(|| {
let mut special_dirs = HashMap::new();
if let Some(dir) = dirs::document_dir() {
@ -187,7 +188,7 @@ fn open_command(path: &PathBuf) -> process::Command {
command
}
pub fn scan_path(tab_path: &PathBuf) -> Vec<Item> {
pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
let mut items = Vec::new();
match fs::read_dir(tab_path) {
Ok(entries) => {
@ -228,19 +229,18 @@ pub fn scan_path(tab_path: &PathBuf) -> Vec<Item> {
let path = entry.path();
//TODO: configurable size
let (icon_handle_dialog, icon_handle_grid, icon_handle_list) = if metadata.is_dir()
{
(
folder_icon(&path, ICON_SIZE_DIALOG),
folder_icon(&path, ICON_SIZE_GRID),
folder_icon(&path, ICON_SIZE_LIST),
folder_icon(&path, sizes.dialog()),
folder_icon(&path, sizes.grid()),
folder_icon(&path, sizes.list()),
)
} else {
(
mime_icon(&path, ICON_SIZE_DIALOG),
mime_icon(&path, ICON_SIZE_GRID),
mime_icon(&path, ICON_SIZE_LIST),
mime_icon(&path, sizes.dialog()),
mime_icon(&path, sizes.grid()),
mime_icon(&path, sizes.list()),
)
};
@ -307,7 +307,7 @@ pub fn scan_trash() -> Vec<Item> {
not(target_os = "android")
)
))]
pub fn scan_trash() -> Vec<Item> {
pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
let mut items: Vec<Item> = Vec::new();
match trash::os_limited::list() {
Ok(entries) => {
@ -323,17 +323,16 @@ pub fn scan_trash() -> Vec<Item> {
let path = entry.original_path();
let name = entry.name.clone();
//TODO: configurable size
let (icon_handle_dialog, icon_handle_grid, icon_handle_list) = match metadata.size {
trash::TrashItemSize::Entries(_) => (
folder_icon(&path, ICON_SIZE_DIALOG),
folder_icon(&path, ICON_SIZE_GRID),
folder_icon(&path, ICON_SIZE_LIST),
folder_icon(&path, sizes.dialog()),
folder_icon(&path, sizes.grid()),
folder_icon(&path, sizes.list()),
),
trash::TrashItemSize::Bytes(_) => (
mime_icon(&path, ICON_SIZE_DIALOG),
mime_icon(&path, ICON_SIZE_GRID),
mime_icon(&path, ICON_SIZE_LIST),
mime_icon(&path, sizes.dialog()),
mime_icon(&path, sizes.grid()),
mime_icon(&path, sizes.list()),
),
};
@ -369,10 +368,10 @@ pub enum Location {
}
impl Location {
pub fn scan(&self) -> Vec<Item> {
pub fn scan(&self, sizes: IconSizes) -> Vec<Item> {
match self {
Self::Path(path) => scan_path(path),
Self::Trash => scan_trash(),
Self::Path(path) => scan_path(path, sizes),
Self::Trash => scan_trash(sizes),
}
}
}
@ -429,11 +428,11 @@ pub struct Item {
}
impl Item {
pub fn property_view(&self, core: &Core) -> Element<crate::app::Message> {
pub fn property_view(&self, core: &Core, sizes: IconSizes) -> Element<crate::app::Message> {
let mut section = widget::settings::view_section("");
section = section.add(widget::settings::item::item_row(vec![
widget::icon::icon(self.icon_handle_list.clone())
.size(ICON_SIZE_LIST)
.size(sizes.list())
.into(),
widget::text(self.name.clone()).into(),
]));
@ -869,7 +868,10 @@ impl Tab {
//TODO: get from config
let item_width = Length::Fixed(96.0);
let item_height = Length::Fixed(116.0);
let TabConfig { show_hidden } = self.config;
let TabConfig {
show_hidden,
icon_sizes,
} = self.config;
let mut children: Vec<Element<_>> = Vec::new();
if let Some(ref items) = self.items_opt {
@ -884,7 +886,7 @@ impl Tab {
let button = widget::button(
widget::column::with_children(vec![
widget::icon::icon(item.icon_handle_grid.clone())
.size(ICON_SIZE_GRID)
.size(icon_sizes.grid())
.into(),
widget::text(item.name.clone()).into(),
])
@ -951,7 +953,10 @@ impl Tab {
if let Some(ref items) = self.items_opt {
let mut count = 0;
let mut hidden = 0;
let TabConfig { show_hidden } = self.config;
let TabConfig {
show_hidden,
icon_sizes,
} = self.config;
for (i, item) in items.iter().enumerate() {
if !show_hidden && item.hidden {
hidden += 1;
@ -998,11 +1003,11 @@ impl Tab {
widget::row::with_children(vec![
if self.dialog.is_some() {
widget::icon::icon(item.icon_handle_dialog.clone())
.size(ICON_SIZE_DIALOG)
.size(icon_sizes.dialog())
.into()
} else {
widget::icon::icon(item.icon_handle_list.clone())
.size(ICON_SIZE_LIST)
.size(icon_sizes.list())
.into()
},
widget::text(item.name.clone()).width(Length::Fill).into(),
@ -1071,7 +1076,7 @@ mod tests {
read_dir_sorted, simple_fs, sort_files, tab_click_new, NAME_LEN, NUM_DIRS, NUM_FILES,
NUM_HIDDEN, NUM_NESTED,
},
config::TabConfig,
config::{IconSizes, TabConfig},
};
// Boilerplate for tab tests. Checks if simulated clicks selected items.
@ -1153,7 +1158,7 @@ mod tests {
let entries = read_dir_sorted(path)?;
debug!("Calling scan_path(\"{}\")", path.display());
let actual = scan_path(&path.to_owned());
let actual = scan_path(&path.to_owned(), IconSizes::default());
// scan_path shouldn't skip any entries
assert_eq!(entries.len(), actual.len());
@ -1177,7 +1182,7 @@ mod tests {
assert!(!invalid_path.exists());
debug!("Calling scan_path(\"{}\")", invalid_path.display());
let actual = scan_path(&invalid_path);
let actual = scan_path(&invalid_path, IconSizes::default());
assert!(actual.is_empty());
@ -1190,7 +1195,7 @@ mod tests {
let path = fs.path();
debug!("Calling scan_path(\"{}\")", path.display());
let actual = scan_path(&path.to_owned());
let actual = scan_path(&path.to_owned(), IconSizes::default());
assert_eq!(0, path.read_dir()?.count());
assert!(actual.is_empty());