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,
|
entity: segmented_button::Entity,
|
||||||
location: Location,
|
location: Location,
|
||||||
) -> Command<Message> {
|
) -> Command<Message> {
|
||||||
|
let icon_sizes = self.config.tab.icon_sizes;
|
||||||
Command::perform(
|
Command::perform(
|
||||||
async move {
|
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)),
|
Ok(items) => message::app(Message::TabRescan(entity, items)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to rescan: {}", err);
|
log::warn!("failed to rescan: {}", err);
|
||||||
|
|
@ -357,7 +360,7 @@ impl App {
|
||||||
if let Some(ref items) = tab.items_opt {
|
if let Some(ref items) = tab.items_opt {
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
if item.selected {
|
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 log::{debug, trace};
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
use crate::{config::TabConfig, tab::Item};
|
use crate::{config::{TabConfig, IconSizes}, tab::Item};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
@ -1274,7 +1277,7 @@ pub(crate) mod test_utils {
|
||||||
|
|
||||||
// New tab with items
|
// New tab with items
|
||||||
let location = Location::Path(path.to_owned());
|
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());
|
let mut tab = Tab::new(location, TabConfig::default());
|
||||||
tab.items_opt = Some(items);
|
tab.items_opt = Some(items);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
use std::num::NonZeroU16;
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry},
|
cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry},
|
||||||
theme,
|
theme,
|
||||||
|
|
@ -8,6 +10,13 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub const CONFIG_VERSION: u64 = 1;
|
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)]
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
pub enum AppTheme {
|
pub enum AppTheme {
|
||||||
Dark,
|
Dark,
|
||||||
|
|
@ -51,14 +60,52 @@ pub struct TabConfig {
|
||||||
pub show_hidden: bool,
|
pub show_hidden: bool,
|
||||||
// TODO: Other possible options
|
// TODO: Other possible options
|
||||||
// pub sort_by: fn(&PathBuf, &PathBuf) -> Ordering,
|
// pub sort_by: fn(&PathBuf, &PathBuf) -> Ordering,
|
||||||
// Icon handle sizes
|
// Icon handle zoom percents
|
||||||
// icon_size_dialog: u16,
|
pub icon_sizes: IconSizes,
|
||||||
// icon_size_list: u16,
|
|
||||||
// icon_size_grid: u16,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TabConfig {
|
impl Default for TabConfig {
|
||||||
fn default() -> Self {
|
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 {
|
impl App {
|
||||||
fn rescan_tab(&self) -> Command<Message> {
|
fn rescan_tab(&self) -> Command<Message> {
|
||||||
let location = self.tab.location.clone();
|
let location = self.tab.location.clone();
|
||||||
|
let icon_sizes = self.tab.config.icon_sizes;
|
||||||
Command::perform(
|
Command::perform(
|
||||||
async move {
|
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)),
|
Ok(items) => message::app(Message::TabRescan(items)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to rescan: {}", err);
|
log::warn!("failed to rescan: {}", err);
|
||||||
|
|
|
||||||
75
src/tab.rs
75
src/tab.rs
|
|
@ -23,13 +23,14 @@ use std::{
|
||||||
time::{Duration, Instant},
|
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);
|
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(|| {
|
static SPECIAL_DIRS: Lazy<HashMap<PathBuf, &'static str>> = Lazy::new(|| {
|
||||||
let mut special_dirs = HashMap::new();
|
let mut special_dirs = HashMap::new();
|
||||||
if let Some(dir) = dirs::document_dir() {
|
if let Some(dir) = dirs::document_dir() {
|
||||||
|
|
@ -187,7 +188,7 @@ fn open_command(path: &PathBuf) -> process::Command {
|
||||||
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();
|
let mut items = Vec::new();
|
||||||
match fs::read_dir(tab_path) {
|
match fs::read_dir(tab_path) {
|
||||||
Ok(entries) => {
|
Ok(entries) => {
|
||||||
|
|
@ -228,19 +229,18 @@ pub fn scan_path(tab_path: &PathBuf) -> Vec<Item> {
|
||||||
|
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
//TODO: configurable size
|
|
||||||
let (icon_handle_dialog, icon_handle_grid, icon_handle_list) = if metadata.is_dir()
|
let (icon_handle_dialog, icon_handle_grid, icon_handle_list) = if metadata.is_dir()
|
||||||
{
|
{
|
||||||
(
|
(
|
||||||
folder_icon(&path, ICON_SIZE_DIALOG),
|
folder_icon(&path, sizes.dialog()),
|
||||||
folder_icon(&path, ICON_SIZE_GRID),
|
folder_icon(&path, sizes.grid()),
|
||||||
folder_icon(&path, ICON_SIZE_LIST),
|
folder_icon(&path, sizes.list()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
mime_icon(&path, ICON_SIZE_DIALOG),
|
mime_icon(&path, sizes.dialog()),
|
||||||
mime_icon(&path, ICON_SIZE_GRID),
|
mime_icon(&path, sizes.grid()),
|
||||||
mime_icon(&path, ICON_SIZE_LIST),
|
mime_icon(&path, sizes.list()),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -307,7 +307,7 @@ pub fn scan_trash() -> Vec<Item> {
|
||||||
not(target_os = "android")
|
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();
|
let mut items: Vec<Item> = Vec::new();
|
||||||
match trash::os_limited::list() {
|
match trash::os_limited::list() {
|
||||||
Ok(entries) => {
|
Ok(entries) => {
|
||||||
|
|
@ -323,17 +323,16 @@ pub fn scan_trash() -> Vec<Item> {
|
||||||
let path = entry.original_path();
|
let path = entry.original_path();
|
||||||
let name = entry.name.clone();
|
let name = entry.name.clone();
|
||||||
|
|
||||||
//TODO: configurable size
|
|
||||||
let (icon_handle_dialog, icon_handle_grid, icon_handle_list) = match metadata.size {
|
let (icon_handle_dialog, icon_handle_grid, icon_handle_list) = match metadata.size {
|
||||||
trash::TrashItemSize::Entries(_) => (
|
trash::TrashItemSize::Entries(_) => (
|
||||||
folder_icon(&path, ICON_SIZE_DIALOG),
|
folder_icon(&path, sizes.dialog()),
|
||||||
folder_icon(&path, ICON_SIZE_GRID),
|
folder_icon(&path, sizes.grid()),
|
||||||
folder_icon(&path, ICON_SIZE_LIST),
|
folder_icon(&path, sizes.list()),
|
||||||
),
|
),
|
||||||
trash::TrashItemSize::Bytes(_) => (
|
trash::TrashItemSize::Bytes(_) => (
|
||||||
mime_icon(&path, ICON_SIZE_DIALOG),
|
mime_icon(&path, sizes.dialog()),
|
||||||
mime_icon(&path, ICON_SIZE_GRID),
|
mime_icon(&path, sizes.grid()),
|
||||||
mime_icon(&path, ICON_SIZE_LIST),
|
mime_icon(&path, sizes.list()),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -369,10 +368,10 @@ pub enum Location {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Location {
|
impl Location {
|
||||||
pub fn scan(&self) -> Vec<Item> {
|
pub fn scan(&self, sizes: IconSizes) -> Vec<Item> {
|
||||||
match self {
|
match self {
|
||||||
Self::Path(path) => scan_path(path),
|
Self::Path(path) => scan_path(path, sizes),
|
||||||
Self::Trash => scan_trash(),
|
Self::Trash => scan_trash(sizes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -429,11 +428,11 @@ pub struct Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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("");
|
let mut section = widget::settings::view_section("");
|
||||||
section = section.add(widget::settings::item::item_row(vec![
|
section = section.add(widget::settings::item::item_row(vec![
|
||||||
widget::icon::icon(self.icon_handle_list.clone())
|
widget::icon::icon(self.icon_handle_list.clone())
|
||||||
.size(ICON_SIZE_LIST)
|
.size(sizes.list())
|
||||||
.into(),
|
.into(),
|
||||||
widget::text(self.name.clone()).into(),
|
widget::text(self.name.clone()).into(),
|
||||||
]));
|
]));
|
||||||
|
|
@ -869,7 +868,10 @@ impl Tab {
|
||||||
//TODO: get from config
|
//TODO: get from config
|
||||||
let item_width = Length::Fixed(96.0);
|
let item_width = Length::Fixed(96.0);
|
||||||
let item_height = Length::Fixed(116.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();
|
let mut children: Vec<Element<_>> = Vec::new();
|
||||||
if let Some(ref items) = self.items_opt {
|
if let Some(ref items) = self.items_opt {
|
||||||
|
|
@ -884,7 +886,7 @@ impl Tab {
|
||||||
let button = widget::button(
|
let button = widget::button(
|
||||||
widget::column::with_children(vec![
|
widget::column::with_children(vec![
|
||||||
widget::icon::icon(item.icon_handle_grid.clone())
|
widget::icon::icon(item.icon_handle_grid.clone())
|
||||||
.size(ICON_SIZE_GRID)
|
.size(icon_sizes.grid())
|
||||||
.into(),
|
.into(),
|
||||||
widget::text(item.name.clone()).into(),
|
widget::text(item.name.clone()).into(),
|
||||||
])
|
])
|
||||||
|
|
@ -951,7 +953,10 @@ impl Tab {
|
||||||
if let Some(ref items) = self.items_opt {
|
if let Some(ref items) = self.items_opt {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let mut hidden = 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() {
|
for (i, item) in items.iter().enumerate() {
|
||||||
if !show_hidden && item.hidden {
|
if !show_hidden && item.hidden {
|
||||||
hidden += 1;
|
hidden += 1;
|
||||||
|
|
@ -998,11 +1003,11 @@ impl Tab {
|
||||||
widget::row::with_children(vec![
|
widget::row::with_children(vec![
|
||||||
if self.dialog.is_some() {
|
if self.dialog.is_some() {
|
||||||
widget::icon::icon(item.icon_handle_dialog.clone())
|
widget::icon::icon(item.icon_handle_dialog.clone())
|
||||||
.size(ICON_SIZE_DIALOG)
|
.size(icon_sizes.dialog())
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
widget::icon::icon(item.icon_handle_list.clone())
|
widget::icon::icon(item.icon_handle_list.clone())
|
||||||
.size(ICON_SIZE_LIST)
|
.size(icon_sizes.list())
|
||||||
.into()
|
.into()
|
||||||
},
|
},
|
||||||
widget::text(item.name.clone()).width(Length::Fill).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,
|
read_dir_sorted, simple_fs, sort_files, tab_click_new, NAME_LEN, NUM_DIRS, NUM_FILES,
|
||||||
NUM_HIDDEN, NUM_NESTED,
|
NUM_HIDDEN, NUM_NESTED,
|
||||||
},
|
},
|
||||||
config::TabConfig,
|
config::{IconSizes, TabConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Boilerplate for tab tests. Checks if simulated clicks selected items.
|
// Boilerplate for tab tests. Checks if simulated clicks selected items.
|
||||||
|
|
@ -1153,7 +1158,7 @@ mod tests {
|
||||||
let entries = read_dir_sorted(path)?;
|
let entries = read_dir_sorted(path)?;
|
||||||
|
|
||||||
debug!("Calling scan_path(\"{}\")", path.display());
|
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
|
// scan_path shouldn't skip any entries
|
||||||
assert_eq!(entries.len(), actual.len());
|
assert_eq!(entries.len(), actual.len());
|
||||||
|
|
@ -1177,7 +1182,7 @@ mod tests {
|
||||||
assert!(!invalid_path.exists());
|
assert!(!invalid_path.exists());
|
||||||
|
|
||||||
debug!("Calling scan_path(\"{}\")", invalid_path.display());
|
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());
|
assert!(actual.is_empty());
|
||||||
|
|
||||||
|
|
@ -1190,7 +1195,7 @@ mod tests {
|
||||||
let path = fs.path();
|
let path = fs.path();
|
||||||
|
|
||||||
debug!("Calling scan_path(\"{}\")", path.display());
|
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_eq!(0, path.read_dir()?.count());
|
||||||
assert!(actual.is_empty());
|
assert!(actual.is_empty());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue