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:
parent
3167a9936c
commit
1f613860e4
4 changed files with 101 additions and 45 deletions
11
src/app.rs
11
src/app.rs
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 _
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
75
src/tab.rs
75
src/tab.rs
|
|
@ -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());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue