diff --git a/flake.nix b/flake.nix index fe3523d..c36841e 100644 --- a/flake.nix +++ b/flake.nix @@ -71,7 +71,9 @@ ]; in rec { - devShell = pkgs.mkShell { + devShell = pkgs.mkShell.override { + stdenv = pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv; + } { nativeBuildInputs = nbi; buildInputs = bi; LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${ diff --git a/src/core/slide.rs b/src/core/slide.rs index 533b32d..bcd67a1 100644 --- a/src/core/slide.rs +++ b/src/core/slide.rs @@ -1,11 +1,13 @@ +use cosmic::dialog::ashpd::url::Url; use crisp::types::{Keyword, Symbol, Value}; +use iced_video_player::Video; use miette::{miette, Result}; use serde::{Deserialize, Serialize}; use std::{ fmt::Display, path::{Path, PathBuf}, }; -use tracing::{debug, error}; +use tracing::error; use super::songs::Song; @@ -49,6 +51,20 @@ pub struct Background { pub kind: BackgroundKind, } +impl TryFrom for Video { + type Error = ParseError; + + fn try_from( + value: Background, + ) -> std::result::Result { + Video::new( + &Url::from_file_path(value.path) + .map_err(|_| ParseError::BackgroundNotVideo)?, + ) + .map_err(|_| ParseError::BackgroundNotVideo) + } +} + impl TryFrom for Background { type Error = ParseError; fn try_from(value: String) -> Result { @@ -137,6 +153,7 @@ pub enum ParseError { NonBackgroundFile, DoesNotExist, CannotCanonicalize, + BackgroundNotVideo, } impl std::error::Error for ParseError {} @@ -154,6 +171,9 @@ impl Display for ParseError { Self::CannotCanonicalize => { "Could not canonicalize this file" } + Self::BackgroundNotVideo => { + "This background isn't a video" + } }; write!(f, "Error: {message}") } diff --git a/src/core/songs.rs b/src/core/songs.rs index 38fbe7d..46441db 100644 --- a/src/core/songs.rs +++ b/src/core/songs.rs @@ -533,7 +533,8 @@ impl Song { } lyric_list.push(lyric.to_string()); } else { - error!("NOT WORKING!"); + // error!("NOT WORKING!"); + () }; } // for lyric in lyric_list.iter() { diff --git a/src/main.rs b/src/main.rs index 5df1c51..d978efe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use clap::{command, Parser}; use core::model::{get_db, LibraryKind}; use core::service_items::{ServiceItem, ServiceItemModel}; use core::slide::*; +use core::songs::Song; use cosmic::app::context_drawer::ContextDrawer; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::clipboard::dnd::DndAction; @@ -28,6 +29,7 @@ use crisp::types::Value; use lisp::parse_lisp; use miette::{miette, Result}; use sqlx::{SqliteConnection, SqlitePool}; +use std::collections::BTreeMap; use std::fs::read_to_string; use std::path::PathBuf; use tracing::{debug, level_filters::LevelFilter}; @@ -97,6 +99,7 @@ struct App { file: PathBuf, presenter: Presenter, windows: Vec, + service: BTreeMap>, slides: Vec, current_slide: Slide, presentation_open: bool, @@ -191,6 +194,7 @@ impl cosmic::Application for App { presenter, core, nav_model, + service: BTreeMap::new(), file: PathBuf::default(), windows, slides, @@ -417,86 +421,26 @@ impl cosmic::Application for App { self.process_key_press(key, modifiers) } Message::SongEditor(message) => { - debug!(?message); - let library_task = if let Some(mut song) = - self.song_editor.song.clone() - { - match message { - song_editor::Message::ChangeFont( - ref font, - ) => { - song.font = Some(font.to_string()); - self.song_editor.song = - Some(song.clone()); - } - song_editor::Message::ChangeFontSize( - font_size, - ) => { - song.font_size = Some(font_size as i32); - self.song_editor.song = - Some(song.clone()); - } - song_editor::Message::ChangeTitle( - ref title, - ) => { - song.title = title.to_string(); - self.song_editor.song = - Some(song.clone()); - } - song_editor::Message::ChangeVerseOrder( - ref vo, - ) => { - let verse_order = vo - .split(" ") - .into_iter() - .map(|s| s.to_owned()) - .collect(); - song.verse_order = Some(verse_order); - self.song_editor.song = - Some(song.clone()); - } - song_editor::Message::ChangeLyrics( - ref action, - ) => { - self.song_editor - .lyrics - .perform(action.clone()); - let lyrics = - self.song_editor.lyrics.text(); - song.lyrics = Some(lyrics.to_string()); - self.song_editor.song = - Some(song.clone()); - } - song_editor::Message::ChangeAuthor( - ref author, - ) => { - song.author = Some(author.to_string()); - self.song_editor.song = - Some(song.clone()); - } - song_editor::Message::Edit(_) => todo!(), - _ => (), - }; - - if let Some(library) = &mut self.library { - let task = library.update( - library::Message::UpdateSong(song), - ); - task.map(|_m| { - cosmic::app::Message::App(Message::None) + // debug!(?message); + match self.song_editor.update(message) { + song_editor::Action::Task(task) => { + task.map(|m| { + cosmic::app::Message::App( + Message::SongEditor(m), + ) }) - } else { - Task::none() } - } else { - Task::none() - }; - let song_editor_task = - self.song_editor.update(message).map(|m| { - debug!(?m); - cosmic::app::Message::App(Message::None) - }); - Task::batch(vec![song_editor_task, library_task]) + song_editor::Action::UpdateSong(song) => { + if let Some(library) = &mut self.library { + self.update(Message::Library( + library::Message::UpdateSong(song), + )) + } else { + Task::none() + } + } + song_editor::Action::None => Task::none(), + } } Message::Present(message) => { // debug!(?message); @@ -506,12 +450,12 @@ impl cosmic::Application for App { } } self.presenter.update(message).map(|x| { - debug!(?x); + // debug!(?x); cosmic::app::Message::App(Message::None) }) } - Message::Library(message) => { +<<<<<<< HEAD // debug!(?message); let (mut kind, mut index): (LibraryKind, i32) = (LibraryKind::Song, 0); @@ -531,24 +475,47 @@ impl cosmic::Application for App { () } }; +======= + let mut song = Song::default(); +>>>>>>> 93b021e (fixed lots of bugs by return Action enums in song_editor and library) if let Some(library) = &mut self.library { - if opened_item { - if let Some(song) = library.get_song(index) { - self.editor_mode = Some(EditorMode::Song); - let _ = self.song_editor.update( - song_editor::Message::ChangeSong( - song.clone(), - ), + match library.update(message) { + library::Action::OpenItem(None) => { + return Task::none(); + } + library::Action::Task(task) => { + return task.map(|message| { + cosmic::app::Message::App( + Message::Library(message), + ) + }); + } + library::Action::None => return Task::none(), + library::Action::OpenItem(Some(( + kind, + index, + ))) => { + debug!( + "Should get song at index: {:?}", + index + ); + let Some(lib_song) = + library.get_song(index) + else { + return Task::none(); + }; + self.editor_mode = Some(kind.into()); + song = lib_song.to_owned(); + debug!( + "Should change songs to: {:?}", + song ); } } - library.update(message).map(|x| { - debug!(?x); - cosmic::app::Message::App(Message::None) - }) - } else { - Task::none() } + self.update(Message::SongEditor( + song_editor::Message::ChangeSong(song), + )) } Message::File(file) => { self.file = file; diff --git a/src/ui/library.rs b/src/ui/library.rs index 41edb99..e2aaed7 100644 --- a/src/ui/library.rs +++ b/src/ui/library.rs @@ -11,8 +11,10 @@ use cosmic::{ Element, Task, }; use miette::{miette, IntoDiagnostic, Result}; -use sqlx::{SqliteConnection, SqlitePool}; -use tracing::{debug, error}; +use sqlx::{ + pool::PoolConnection, Sqlite, SqliteConnection, SqlitePool, +}; +use tracing::{debug, error, warn}; use crate::core::{ content::Content, @@ -39,6 +41,12 @@ pub(crate) struct Library { db: SqlitePool, } +pub(crate) enum Action { + OpenItem(Option<(LibraryKind, i32)>), + Task(Task), + None, +} + #[derive(Clone, Debug)] pub(crate) enum Message { AddItem, @@ -85,157 +93,160 @@ impl<'a> Library { self.song_library.get_item(index) } - pub fn update(&'a mut self, message: Message) -> Task { + pub fn update(&'a mut self, message: Message) -> Action { match message { - Message::AddItem => Task::none(), - Message::None => Task::none(), - Message::RemoveItem => Task::none(), + Message::AddItem => (), + Message::None => (), + Message::RemoveItem => (), Message::OpenItem(item) => { debug!(?item); self.editing_item = item; - Task::none() + return Action::OpenItem(item); } Message::HoverLibrary(library_kind) => { self.library_hovered = library_kind; - Task::none() } Message::OpenLibrary(library_kind) => { self.library_open = library_kind; - Task::none() } Message::HoverItem(item) => { self.hovered_item = item; - Task::none() } Message::SelectItem(item) => { self.selected_item = item; - Task::none() } Message::UpdateSong(song) => { let Some((kind, index)) = self.editing_item else { error!("Not editing an item"); - return Task::none(); + return Action::None; }; if kind != LibraryKind::Song { error!("Not editing a song item"); - return Task::none(); + return Action::None; }; match self .song_library .update_item(song.clone(), index) { - Ok(_) => Task::future(self.db.acquire()) - .and_then(move |conn| { - Task::perform( - update_song_in_db(song.clone(), conn) - .map(|r| match r { - Ok(_) => {} - Err(e) => { - error!(?e); - } - }), - |_| Message::SongChanged, - ) - }), + Ok(_) => { + return Action::Task( + Task::future(self.db.acquire()).and_then( + move |conn| update_in_db(&song, conn), + ), + ) + } Err(_) => todo!(), } } Message::SongChanged => { // self.song_library.update_item(song, index); debug!("song changed"); - Task::none() } Message::UpdateImage(image) => { let Some((kind, index)) = self.editing_item else { error!("Not editing an item"); - return Task::none(); + return Action::None; }; if kind != LibraryKind::Image { error!("Not editing a image item"); - return Task::none(); + return Action::None; }; match self .image_library .update_item(image.clone(), index) { - Ok(_) => Task::future(self.db.acquire()) - .and_then(move |conn| { - Task::perform( - update_image_in_db( - image.clone(), - conn, - ), - |_| Message::ImageChanged, - ) - }), + Ok(_) => { + return Action::Task( + Task::future(self.db.acquire()).and_then( + move |conn| { + Task::perform( + update_image_in_db( + image.clone(), + conn, + ), + |_| Message::ImageChanged, + ) + }, + ), + ) + } Err(_) => todo!(), } } - Message::ImageChanged => todo!(), + Message::ImageChanged => (), Message::UpdateVideo(video) => { let Some((kind, index)) = self.editing_item else { error!("Not editing an item"); - return Task::none(); + return Action::None; }; if kind != LibraryKind::Video { error!("Not editing a video item"); - return Task::none(); + return Action::None; }; match self .video_library .update_item(video.clone(), index) { - Ok(_) => Task::future(self.db.acquire()) - .and_then(move |conn| { - Task::perform( - update_video_in_db( - video.clone(), - conn, - ), - |_| Message::VideoChanged, - ) - }), + Ok(_) => { + return Action::Task( + Task::future(self.db.acquire()).and_then( + move |conn| { + Task::perform( + update_video_in_db( + video.clone(), + conn, + ), + |_| Message::VideoChanged, + ) + }, + ), + ) + } Err(_) => todo!(), } } - Message::VideoChanged => todo!(), + Message::VideoChanged => (), Message::UpdatePresentation(presentation) => { let Some((kind, index)) = self.editing_item else { error!("Not editing an item"); - return Task::none(); + return Action::None; }; if kind != LibraryKind::Presentation { error!("Not editing a presentation item"); - return Task::none(); + return Action::None; }; match self .presentation_library .update_item(presentation.clone(), index) { - Ok(_) => Task::future(self.db.acquire()) - .and_then(move |conn| { - Task::perform( - update_presentation_in_db( - presentation.clone(), - conn, - ), - |_| Message::PresentationChanged, - ) - }), + Ok(_) => return Action::Task( + Task::future(self.db.acquire()).and_then( + move |conn| { + Task::perform( + update_presentation_in_db( + presentation.clone(), + conn, + ), + |_| Message::PresentationChanged, + ) + }, + ), + ), Err(_) => todo!(), } } - Message::PresentationChanged => todo!(), - Message::Error(_) => todo!(), - } + Message::PresentationChanged => (), + Message::Error(_) => (), + }; + Action::None } pub fn view(&self) -> Element { @@ -508,6 +519,30 @@ impl<'a> Library { // } } +fn update_in_db( + song: &Song, + conn: PoolConnection, +) -> Task { + let song_title = song.title.clone(); + warn!("Should have updated song: {:?}", song_title); + Task::perform( + update_song_in_db(song.to_owned(), conn).map( + move |r| match r { + Ok(_) => { + warn!( + "Should have updated song: {:?}", + song_title + ); + } + Err(e) => { + error!(?e); + } + }, + ), + |_| Message::SongChanged, + ) +} + async fn add_db() -> Result { let mut data = dirs::data_local_dir().unwrap(); data.push("lumina"); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index e6e0c84..e14da85 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,5 @@ +use crate::core::model::LibraryKind; + pub mod double_ended_slider; pub mod library; pub mod presenter; @@ -11,3 +13,14 @@ pub enum EditorMode { Presentation, Slide, } + +impl From for EditorMode { + fn from(value: LibraryKind) -> Self { + match value { + LibraryKind::Song => Self::Song, + LibraryKind::Video => Self::Video, + LibraryKind::Image => Self::Image, + LibraryKind::Presentation => Self::Presentation, + } + } +} diff --git a/src/ui/song_editor.rs b/src/ui/song_editor.rs index 6fa7cd6..d9c5958 100644 --- a/src/ui/song_editor.rs +++ b/src/ui/song_editor.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{io, path::PathBuf}; use cosmic::{ iced::{ @@ -17,11 +17,11 @@ use cosmic::{ }; use dirs::font_dir; use iced_video_player::Video; -use tracing::debug; +use tracing::{debug, error}; use crate::{ core::{service_items::ServiceTrait, songs::Song}, - Background, + Background, BackgroundKind, }; use super::presenter::slide_view; @@ -45,6 +45,12 @@ pub struct SongEditor { ccli: String, } +pub enum Action { + Task(Task), + UpdateSong(Song), + None, +} + #[derive(Debug, Clone)] pub enum Message { ChangeSong(Song), @@ -54,7 +60,8 @@ pub enum Message { ChangeTitle(String), ChangeVerseOrder(String), ChangeLyrics(text_editor::Action), - ChangeBackground, + ChangeBackground(Result), + PickBackground, Edit(bool), None, ChangeAuthor(String), @@ -144,7 +151,7 @@ impl SongEditor { ccli: "8".to_owned(), } } - pub fn update(&mut self, message: Message) -> Task { + pub fn update(&mut self, message: Message) -> Action { match message { Message::ChangeSong(song) => { self.song = Some(song.clone()); @@ -178,12 +185,6 @@ impl SongEditor { text_editor::Content::with_text(&lyrics) }; self.background = song.background; - - Task::none() - } - Message::UpdateSong(song) => { - self.song = Some(song); - Task::none() } Message::ChangeFont(font) => { self.font = font.clone(); @@ -200,20 +201,24 @@ impl SongEditor { style, }; self.current_font = font; - Task::none() + // return self.update_song(song); } Message::ChangeFontSize(size) => { if let Some(size) = self.font_sizes.get(size) { if let Ok(size) = size.parse() { debug!(font_size = size); self.font_size = size; + // return self.update_song(song); } } - Task::none() } Message::ChangeTitle(title) => { self.title = title.clone(); - Task::none() + if let Some(song) = &mut self.song { + song.title = title; + let song = song.to_owned(); + return self.update_song(song); + } } Message::ChangeVerseOrder(verse_order) => { self.verse_order = verse_order.clone(); @@ -224,9 +229,7 @@ impl SongEditor { .map(|s| s.to_owned()) .collect(); song.verse_order = Some(verse_order); - self.update(Message::UpdateSong(song)) - } else { - Task::none() + return self.update_song(song); } } Message::ChangeLyrics(action) => { @@ -236,61 +239,50 @@ impl SongEditor { if let Some(mut song) = self.song.clone() { song.lyrics = Some(lyrics); - self.update(Message::UpdateSong(song)) - } else { - Task::none() + return self.update_song(song); } } Message::Edit(edit) => { debug!(edit); self.editing = edit; - Task::none() } - Message::None => Task::none(), Message::ChangeAuthor(author) => { debug!(author); self.author = author.clone(); if let Some(mut song) = self.song.clone() { song.author = Some(author); - self.update(Message::UpdateSong(song)) - } else { - Task::none() + return self.update_song(song); } } - Message::ChangeBackground => { - let background = rfd::FileDialog::new() - .pick_file() - .and_then(|f| { - Background::try_from(f) - .map_or(None, |f| Some(f)) - }); - - debug!(?background); + Message::ChangeBackground(Ok(path)) => { + debug!(?path); if let Some(mut song) = self.song.clone() { - song.background = background.clone(); - self.update(Message::UpdateSong(song)) - } else { - Task::none() + let background = + Background::try_from(path.clone()).ok(); + if let Some(background) = background { + if background.kind == BackgroundKind::Video { + let video = + Video::try_from(background).ok(); + debug!(?video); + self.video = video; + } + } + song.background = path.try_into().ok(); + return self.update_song(song); } - - // todo!() - - // if let Some(mut song) = self.song.clone() { - // Task::future( - // rfd::AsyncFileDialog::new().pick_file(), - // ) - // .and_then(move |f| { - // song.background = f - // .path() - // .try_into() - // .map_or(None, |b| Some(b)); - // Task::none() - // }) - // } else { - // Task::none() - // } } + Message::ChangeBackground(Err(error)) => { + error!(?error); + } + Message::PickBackground => { + return Action::Task(Task::perform( + pick_background(), + Message::ChangeBackground, + )) + } + _ => (), } + Action::None } pub fn view(&self) -> Element { @@ -411,7 +403,7 @@ order", ) .label("Background") .tooltip("Select an image or video background") - .on_press(Message::ChangeBackground) + .on_press(Message::PickBackground) .padding(10); row![ @@ -426,6 +418,11 @@ order", pub fn editing(&self) -> bool { self.editing } + + fn update_song(&mut self, song: Song) -> Action { + self.song = Some(song.clone()); + Action::UpdateSong(song) + } } impl Default for SongEditor { @@ -433,3 +430,18 @@ impl Default for SongEditor { Self::new() } } + +async fn pick_background() -> Result { + rfd::AsyncFileDialog::new() + .set_title("Choose a background...") + .pick_file() + .await + .ok_or(SongError::DialogClosed) + .map(|file| file.path().to_owned()) +} + +#[derive(Debug, Clone)] +pub enum SongError { + DialogClosed, + IOError(io::ErrorKind), +} diff --git a/todo.org b/todo.org index c42102d..71ad008 100644 --- a/todo.org +++ b/todo.org @@ -1,6 +1,8 @@ #+TITLE: The Task list for Lumina +* TODO Change return type of all components to an Action enum instead of the Task type + * TODO Need to fix tests now that the basic app is working * TODO Build library to see all available songs, images, videos, presentations, and slides