From 841816e8d1987a867ff551853e12d6291eea4c39 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 24 Jan 2025 14:05:50 -0700 Subject: [PATCH] Add tree view, fixes #52, fixes #53 --- Cargo.lock | 47 ++++-- Cargo.toml | 8 +- i18n/en/cosmic_player.ftl | 2 + src/config.rs | 6 +- src/localize.rs | 64 +++++-- src/main.rs | 346 +++++++++++++++++++++++++++++++++++++- src/menu.rs | 44 +++-- src/project.rs | 99 +++++++++++ 8 files changed, 557 insertions(+), 59 deletions(-) create mode 100644 src/project.rs diff --git a/Cargo.lock b/Cargo.lock index 72eca37..5bfd4bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1076,6 +1076,8 @@ dependencies = [ "i18n-embed", "i18n-embed-fl", "iced_video_player", + "icu_collator", + "icu_provider", "image", "lazy_static", "libcosmic", @@ -2484,9 +2486,9 @@ dependencies = [ [[package]] name = "i18n-embed" -version = "0.13.9" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92a86226a7a16632de6723449ee5fe70bac5af718bc642ee9ca2f0f6e14fa1fa" +checksum = "94205d95764f5bb9db9ea98fa77f89653365ca748e27161f5bbea2ffd50e459c" dependencies = [ "arc-swap", "fluent", @@ -2506,9 +2508,9 @@ dependencies = [ [[package]] name = "i18n-embed-fl" -version = "0.6.7" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26a3d3569737dfaac7fc1c4078e6af07471c3060b8e570bcd83cdd5f4685395" +checksum = "9fc1f8715195dffc4caddcf1cf3128da15fe5d8a137606ea8856c9300047d5a2" dependencies = [ "dashmap", "find-crate", @@ -2797,6 +2799,31 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "icu_collator" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d370371887d31d56f361c3eaa15743e54f13bc677059c9191c77e099ed6966b2" +dependencies = [ + "displaydoc", + "icu_collator_data", + "icu_collections", + "icu_locid_transform", + "icu_normalizer", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_collator_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee3f88741364b7d6269cce6827a3e6a8a2cf408a78f766c9224ab479d5e4ae5" + [[package]] name = "icu_collections" version = "1.5.0" @@ -4442,9 +4469,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rust-embed" -version = "6.8.1" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -4453,9 +4480,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "6.8.1" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" dependencies = [ "proc-macro2", "quote", @@ -4466,9 +4493,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "7.8.1" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" dependencies = [ "sha2", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 7b89bcd..34f95d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,11 @@ tempfile = "3" tokio = "1" url = "2" # Internationalization -i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] } -i18n-embed-fl = "0.6" -rust-embed = "6" +icu_collator = "1.5" +icu_provider = { version = "1.5", features = ["sync"] } +i18n-embed = { version = "0.14", features = ["fluent-system", "desktop-requester"] } +i18n-embed-fl = "0.7" +rust-embed = "8" # Logging env_logger = "0.10" log = "0.4" diff --git a/i18n/en/cosmic_player.ftl b/i18n/en/cosmic_player.ftl index 932ec08..9961387 100644 --- a/i18n/en/cosmic_player.ftl +++ b/i18n/en/cosmic_player.ftl @@ -1,6 +1,7 @@ audio = Audio no-video-or-audio-file-open = No video or audio file open open-file = Open file +open-folder = Open folder subtitles = Subtitles # Context Pages @@ -24,4 +25,5 @@ open-recent-media = Open recent media close-file = Close file open-media-folder = Open media folder... open-recent-media-folder = Open recent media folder +close-media-folder = Close media folder quit = Quit \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 233cf48..dfb895a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,7 +5,7 @@ use cosmic::{ theme, }; use serde::{Deserialize, Serialize}; -use std::collections::VecDeque; +use std::{collections::VecDeque, path::PathBuf}; pub const CONFIG_VERSION: u64 = 1; @@ -43,14 +43,14 @@ impl Default for Config { #[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct ConfigState { pub recent_files: VecDeque, - pub recent_folders: VecDeque, + pub recent_projects: VecDeque, } impl Default for ConfigState { fn default() -> Self { Self { recent_files: VecDeque::new(), - recent_folders: VecDeque::new(), + recent_projects: VecDeque::new(), } } } diff --git a/src/localize.rs b/src/localize.rs index f5c6fb9..70272fb 100644 --- a/src/localize.rs +++ b/src/localize.rs @@ -1,17 +1,37 @@ // SPDX-License-Identifier: GPL-3.0-only +use std::str::FromStr; +use std::sync::OnceLock; + use i18n_embed::{ fluent::{fluent_language_loader, FluentLanguageLoader}, DefaultLocalizer, LanguageLoader, Localizer, }; +use icu_collator::{Collator, CollatorOptions, Numeric}; +use icu_provider::DataLocale; use rust_embed::RustEmbed; #[derive(RustEmbed)] #[folder = "i18n/"] struct Localizations; -lazy_static::lazy_static! { - pub static ref LANGUAGE_LOADER: FluentLanguageLoader = { +pub static LANGUAGE_LOADER: OnceLock = OnceLock::new(); +pub static LANGUAGE_SORTER: OnceLock = OnceLock::new(); + +#[macro_export] +macro_rules! fl { + ($message_id:literal) => {{ + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER.get().unwrap(), $message_id) + }}; + + ($message_id:literal, $($args:expr),*) => {{ + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER.get().unwrap(), $message_id, $($args), *) + }}; +} + +// Get the `Localizer` to be used for localizing this library. +pub fn localizer() -> Box { + LANGUAGE_LOADER.get_or_init(|| { let loader: FluentLanguageLoader = fluent_language_loader!(); loader @@ -19,23 +39,12 @@ lazy_static::lazy_static! { .expect("Error while loading fallback language"); loader - }; -} + }); -#[macro_export] -macro_rules! fl { - ($message_id:literal) => {{ - i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id) - }}; - - ($message_id:literal, $($args:expr),*) => {{ - i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *) - }}; -} - -// Get the `Localizer` to be used for localizing this library. -pub fn localizer() -> Box { - Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations)) + Box::from(DefaultLocalizer::new( + LANGUAGE_LOADER.get().unwrap(), + &Localizations, + )) } pub fn localize() { @@ -46,3 +55,22 @@ pub fn localize() { eprintln!("Error while loading language for App List {}", error); } } + +pub fn sorter() -> &'static Collator { + LANGUAGE_SORTER.get_or_init(|| { + let mut options = CollatorOptions::new(); + options.numeric = Some(Numeric::On); + let localizer = localizer(); + let language_loader = localizer.language_loader(); + + DataLocale::from_str(&language_loader.current_language().to_string()) + .or_else(|_| DataLocale::from_str(&language_loader.fallback_language().to_string())) + .ok() + .and_then(|locale| Collator::try_new(&locale, options).ok()) + .or_else(|| { + let locale = DataLocale::from_str("en-US").expect("en-US is a valid BCP-47 tag"); + Collator::try_new(&locale, options).ok() + }) + .expect("Creating a collator from the system's current language, the fallback language, or American English should succeed") + }) +} diff --git a/src/main.rs b/src/main.rs index 6a66dff..a6bc650 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,8 @@ use cosmic::{ subscription::Subscription, window, Alignment, Background, Border, Color, ContentFit, Length, Limits, }, - theme, - widget::{self, menu::action::MenuAction, Slider}, + iced_style, theme, + widget::{self, menu::action::MenuAction, nav_bar, segmented_button, Slider}, Application, ApplicationExt, Element, }; use iced_video_player::{ @@ -24,7 +24,9 @@ use std::{ any::TypeId, collections::HashMap, ffi::{CStr, CString}, - fs, process, thread, + fs, + path::{Path, PathBuf}, + process, thread, time::{Duration, Instant}, }; use tokio::sync::mpsc; @@ -32,6 +34,7 @@ use tokio::sync::mpsc; use crate::{ config::{Config, ConfigState, CONFIG_VERSION}, key_bind::{key_binds, KeyBind}, + project::ProjectNode, }; mod config; @@ -40,6 +43,7 @@ mod localize; mod menu; #[cfg(feature = "mpris-server")] mod mpris; +mod project; static CONTROLS_TIMEOUT: Duration = Duration::new(2, 0); @@ -143,6 +147,7 @@ pub enum Action { FileClose, FileOpen, FileOpenRecent(usize), + FolderClose(usize), FolderOpen, FolderOpenRecent(usize), Fullscreen, @@ -160,6 +165,7 @@ impl MenuAction for Action { Self::FileClose => Message::FileClose, Self::FileOpen => Message::FileOpen, Self::FileOpenRecent(index) => Message::FileOpenRecent(*index), + Self::FolderClose(index) => Message::FolderClose(*index), Self::FolderOpen => Message::FolderOpen, Self::FolderOpenRecent(index) => Message::FolderOpenRecent(*index), Self::Fullscreen => Message::Fullscreen, @@ -224,6 +230,8 @@ pub enum Message { FileLoad(url::Url), FileOpen, FileOpenRecent(usize), + FolderClose(usize), + FolderLoad(PathBuf), FolderOpen, FolderOpenRecent(usize), Fullscreen, @@ -259,6 +267,8 @@ pub struct App { fullscreen: bool, key_binds: HashMap, mpris_opt: Option<(MprisMeta, MprisState, mpsc::UnboundedSender)>, + nav_model: segmented_button::SingleSelectModel, + projects: Vec<(String, PathBuf)>, video_opt: Option