Add recent media, part of #53
This commit is contained in:
parent
fd1df4f098
commit
0169cccfa2
6 changed files with 144 additions and 9 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1070,6 +1070,7 @@ dependencies = [
|
|||
name = "cosmic-player"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"env_logger",
|
||||
"gstreamer-tag",
|
||||
"i18n-embed",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
dirs = "5"
|
||||
gstreamer-tag = "0.23"
|
||||
image = "0.24.9"
|
||||
lazy_static = "1"
|
||||
|
|
|
|||
|
|
@ -22,4 +22,6 @@ file = File
|
|||
open-media = Open media...
|
||||
open-recent-media = Open recent media
|
||||
close-file = Close file
|
||||
open-media-folder = Open media folder...
|
||||
open-recent-media-folder = Open recent media folder
|
||||
quit = Quit
|
||||
|
|
@ -5,6 +5,7 @@ use cosmic::{
|
|||
theme,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub const CONFIG_VERSION: u64 = 1;
|
||||
|
||||
|
|
@ -38,3 +39,18 @@ impl Default for Config {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct ConfigState {
|
||||
pub recent_files: VecDeque<url::Url>,
|
||||
pub recent_folders: VecDeque<url::Url>,
|
||||
}
|
||||
|
||||
impl Default for ConfigState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
recent_files: VecDeque::new(),
|
||||
recent_folders: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
84
src/main.rs
84
src/main.rs
|
|
@ -30,7 +30,7 @@ use std::{
|
|||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
config::{Config, CONFIG_VERSION},
|
||||
config::{Config, ConfigState, CONFIG_VERSION},
|
||||
key_bind::{key_binds, KeyBind},
|
||||
};
|
||||
|
||||
|
|
@ -85,6 +85,23 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
};
|
||||
|
||||
let (config_state_handler, config_state) =
|
||||
match cosmic_config::Config::new_state(App::APP_ID, CONFIG_VERSION) {
|
||||
Ok(config_state_handler) => {
|
||||
let config_state = ConfigState::get_entry(&config_state_handler).unwrap_or_else(
|
||||
|(errs, config_state)| {
|
||||
log::info!("errors loading config_state: {:?}", errs);
|
||||
config_state
|
||||
},
|
||||
);
|
||||
(Some(config_state_handler), config_state)
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to create config_state handler: {}", err);
|
||||
(None, ConfigState::default())
|
||||
}
|
||||
};
|
||||
|
||||
let mut settings = Settings::default();
|
||||
settings = settings.theme(config.app_theme.theme());
|
||||
settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0));
|
||||
|
|
@ -112,6 +129,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let flags = Flags {
|
||||
config_handler,
|
||||
config,
|
||||
config_state_handler,
|
||||
config_state,
|
||||
url_opt,
|
||||
};
|
||||
cosmic::app::run::<App>(settings, flags)?;
|
||||
|
|
@ -123,6 +142,9 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
pub enum Action {
|
||||
FileClose,
|
||||
FileOpen,
|
||||
FileOpenRecent(usize),
|
||||
FolderOpen,
|
||||
FolderOpenRecent(usize),
|
||||
Fullscreen,
|
||||
PlayPause,
|
||||
SeekBackward,
|
||||
|
|
@ -137,6 +159,9 @@ impl MenuAction for Action {
|
|||
match self {
|
||||
Self::FileClose => Message::FileClose,
|
||||
Self::FileOpen => Message::FileOpen,
|
||||
Self::FileOpenRecent(index) => Message::FileOpenRecent(*index),
|
||||
Self::FolderOpen => Message::FolderOpen,
|
||||
Self::FolderOpenRecent(index) => Message::FolderOpenRecent(*index),
|
||||
Self::Fullscreen => Message::Fullscreen,
|
||||
Self::PlayPause => Message::PlayPause,
|
||||
Self::SeekBackward => Message::SeekRelative(-10.0),
|
||||
|
|
@ -150,6 +175,8 @@ impl MenuAction for Action {
|
|||
pub struct Flags {
|
||||
config_handler: Option<cosmic_config::Config>,
|
||||
config: Config,
|
||||
config_state_handler: Option<cosmic_config::Config>,
|
||||
config_state: ConfigState,
|
||||
url_opt: Option<url::Url>,
|
||||
}
|
||||
|
||||
|
|
@ -191,10 +218,14 @@ pub enum MprisEvent {
|
|||
pub enum Message {
|
||||
None,
|
||||
Config(Config),
|
||||
ConfigState(ConfigState),
|
||||
DropdownToggle(DropdownKind),
|
||||
FileClose,
|
||||
FileLoad(url::Url),
|
||||
FileOpen,
|
||||
FileOpenRecent(usize),
|
||||
FolderOpen,
|
||||
FolderOpenRecent(usize),
|
||||
Fullscreen,
|
||||
Key(Modifiers, Key),
|
||||
AudioCode(usize),
|
||||
|
|
@ -272,12 +303,18 @@ impl App {
|
|||
}
|
||||
|
||||
let url = match &self.flags.url_opt {
|
||||
Some(some) => some,
|
||||
Some(some) => some.clone(),
|
||||
None => return Command::none(),
|
||||
};
|
||||
|
||||
log::info!("Loading {}", url);
|
||||
|
||||
// Add to recent files, ensuring only one entry
|
||||
self.flags.config_state.recent_files.retain(|x| x != &url);
|
||||
self.flags.config_state.recent_files.push_front(url.clone());
|
||||
self.flags.config_state.recent_files.truncate(10);
|
||||
self.save_config_state();
|
||||
|
||||
//TODO: this code came from iced_video_player::Video::new and has been modified to stop the pipeline on error
|
||||
//TODO: remove unwraps and enable playback of files with only audio.
|
||||
let video = {
|
||||
|
|
@ -387,6 +424,14 @@ impl App {
|
|||
self.update_title()
|
||||
}
|
||||
|
||||
fn save_config_state(&mut self) {
|
||||
if let Some(ref config_state_handler) = self.flags.config_state_handler {
|
||||
if let Err(err) = self.flags.config_state.write_entry(config_state_handler) {
|
||||
log::error!("failed to save config_state: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_controls(&mut self, in_use: bool) {
|
||||
if in_use
|
||||
|| !self
|
||||
|
|
@ -593,6 +638,12 @@ impl Application for App {
|
|||
return self.update_config();
|
||||
}
|
||||
}
|
||||
Message::ConfigState(config_state) => {
|
||||
if config_state != self.flags.config_state {
|
||||
log::info!("update config state");
|
||||
self.flags.config_state = config_state;
|
||||
}
|
||||
}
|
||||
Message::DropdownToggle(menu_kind) => {
|
||||
if self.dropdown_opt.take() != Some(menu_kind) {
|
||||
self.dropdown_opt = Some(menu_kind);
|
||||
|
|
@ -625,6 +676,15 @@ impl Application for App {
|
|||
|x| x,
|
||||
);
|
||||
}
|
||||
Message::FileOpenRecent(index) => {
|
||||
if let Some(url) = self.flags.config_state.recent_files.get(index) {
|
||||
self.flags.url_opt = Some(url.clone());
|
||||
return self.load();
|
||||
}
|
||||
}
|
||||
Message::FolderOpen | Message::FolderOpenRecent(..) => {
|
||||
log::error!("TODO: {:?}", message);
|
||||
}
|
||||
Message::Fullscreen => {
|
||||
//TODO: cleanest way to close dropdowns
|
||||
self.dropdown_opt = None;
|
||||
|
|
@ -818,7 +878,11 @@ impl Application for App {
|
|||
}
|
||||
|
||||
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
||||
vec![menu::menu_bar(&self.flags.config, &self.key_binds)]
|
||||
vec![menu::menu_bar(
|
||||
&self.flags.config,
|
||||
&self.flags.config_state,
|
||||
&self.key_binds,
|
||||
)]
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
|
|
@ -1094,6 +1158,7 @@ impl Application for App {
|
|||
|
||||
fn subscription(&self) -> Subscription<Self::Message> {
|
||||
struct ConfigSubscription;
|
||||
struct ConfigStateSubscription;
|
||||
struct ThemeSubscription;
|
||||
|
||||
let mut subscriptions = vec![
|
||||
|
|
@ -1113,7 +1178,18 @@ impl Application for App {
|
|||
if !update.errors.is_empty() {
|
||||
log::debug!("errors loading config: {:?}", update.errors);
|
||||
}
|
||||
Message::SystemThemeModeChange(update.config)
|
||||
Message::Config(update.config)
|
||||
}),
|
||||
cosmic_config::config_state_subscription(
|
||||
TypeId::of::<ConfigStateSubscription>(),
|
||||
Self::APP_ID.into(),
|
||||
CONFIG_VERSION,
|
||||
)
|
||||
.map(|update| {
|
||||
if !update.errors.is_empty() {
|
||||
log::debug!("errors loading config state: {:?}", update.errors);
|
||||
}
|
||||
Message::ConfigState(update.config)
|
||||
}),
|
||||
cosmic_config::config_subscription::<_, cosmic_theme::ThemeMode>(
|
||||
TypeId::of::<ThemeSubscription>(),
|
||||
|
|
|
|||
49
src/menu.rs
49
src/menu.rs
|
|
@ -7,10 +7,43 @@ use cosmic::{
|
|||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{fl, Action, Config, Message};
|
||||
use crate::{fl, Action, Config, ConfigState, Message};
|
||||
|
||||
pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
|
||||
let mut recent_items = Vec::new();
|
||||
pub fn menu_bar<'a>(
|
||||
config: &Config,
|
||||
config_state: &ConfigState,
|
||||
key_binds: &HashMap<KeyBind, Action>,
|
||||
) -> Element<'a, Message> {
|
||||
let home_dir_opt = dirs::home_dir();
|
||||
let format_path = |url: &url::Url| -> String {
|
||||
match url.to_file_path() {
|
||||
Ok(path) => {
|
||||
if let Some(home_dir) = &home_dir_opt {
|
||||
if let Ok(part) = path.strip_prefix(home_dir) {
|
||||
return format!("~/{}", part.display());
|
||||
}
|
||||
}
|
||||
path.display().to_string()
|
||||
}
|
||||
Err(()) => url.to_string(),
|
||||
}
|
||||
};
|
||||
|
||||
let mut recent_files = Vec::with_capacity(config_state.recent_files.len());
|
||||
for (i, path) in config_state.recent_files.iter().enumerate() {
|
||||
recent_files.push(menu::Item::Button(
|
||||
format_path(path),
|
||||
Action::FileOpenRecent(i),
|
||||
));
|
||||
}
|
||||
|
||||
let mut recent_folders = Vec::with_capacity(config_state.recent_folders.len());
|
||||
for (i, path) in config_state.recent_folders.iter().enumerate() {
|
||||
recent_folders.push(menu::Item::Button(
|
||||
format_path(path),
|
||||
Action::FolderOpenRecent(i),
|
||||
));
|
||||
}
|
||||
|
||||
MenuBar::new(vec![menu::Tree::with_children(
|
||||
menu::root(fl!("file")),
|
||||
|
|
@ -18,15 +51,21 @@ pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> El
|
|||
key_binds,
|
||||
vec![
|
||||
menu::Item::Button(fl!("open-media"), Action::FileOpen),
|
||||
menu::Item::Folder(fl!("open-recent-media"), recent_items),
|
||||
menu::Item::Folder(fl!("open-recent-media"), recent_files),
|
||||
menu::Item::Button(fl!("close-file"), Action::FileClose),
|
||||
menu::Item::Divider,
|
||||
/*TODO: folders
|
||||
menu::Item::Button(fl!("open-media-folder"), Action::FolderOpen),
|
||||
menu::Item::Folder(fl!("open-recent-media-folder"), recent_folders),
|
||||
menu::Item::Folder(fl!("close-media-folder"), close_folders),
|
||||
menu::Item::Divider,
|
||||
*/
|
||||
menu::Item::Button(fl!("quit"), Action::WindowClose),
|
||||
],
|
||||
),
|
||||
)])
|
||||
.item_height(ItemHeight::Dynamic(40))
|
||||
.item_width(ItemWidth::Uniform(240))
|
||||
.item_width(ItemWidth::Uniform(320))
|
||||
.spacing(theme::active().cosmic().spacing.space_xxxs.into())
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue