Compare commits

...

3 commits

Author SHA1 Message Date
645411b59c more things, but mostly starting to add library management
Some checks are pending
/ test (push) Waiting to run
2025-09-15 14:42:07 -05:00
3fe77c93e2 perf, pedantic, nursery, and unwrap_used clippy fixes 2025-09-15 11:19:27 -05:00
a186d3bec4 more cargo clippy fixes 2025-09-15 11:18:05 -05:00
16 changed files with 471 additions and 400 deletions

View file

@ -8,16 +8,18 @@
fenix.url = "github:nix-community/fenix"; fenix.url = "github:nix-community/fenix";
}; };
outputs = inputs: with inputs; outputs =
flake-utils.lib.eachDefaultSystem inputs:
(system: with inputs;
flake-utils.lib.eachDefaultSystem (
system:
let let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
overlays = [fenix.overlays.default]; overlays = [ fenix.overlays.default ];
# overlays = [cargo2nix.overlays.default]; # overlays = [cargo2nix.overlays.default];
}; };
naersk' = pkgs.callPackage naersk {}; naersk' = pkgs.callPackage naersk { };
nbi = with pkgs; [ nbi = with pkgs; [
# Rust tools # Rust tools
alejandra alejandra
@ -52,6 +54,7 @@
vulkan-tools vulkan-tools
libGL libGL
cargo-flamegraph cargo-flamegraph
bacon
fontconfig fontconfig
glib glib
@ -74,11 +77,14 @@
sqlx-cli sqlx-cli
cargo-watch cargo-watch
]; ];
in rec in
rec {
devShell =
pkgs.mkShell.override
{ {
devShell = pkgs.mkShell.override {
# stdenv = pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv; # stdenv = pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv;
} { }
{
nativeBuildInputs = nbi; nativeBuildInputs = nbi;
buildInputs = bi; buildInputs = bi;
LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${ LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${

View file

@ -17,7 +17,7 @@ use std::path::PathBuf;
use tracing::error; use tracing::error;
#[derive( #[derive(
Clone, Debug, Default, PartialEq, Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize,
)] )]
pub struct Image { pub struct Image {
pub id: i32, pub id: i32,
@ -52,8 +52,9 @@ impl Content for Image {
if self.path.exists() { if self.path.exists() {
self.path self.path
.file_name() .file_name()
.map(|f| f.to_string_lossy().to_string()) .map_or("Missing image".into(), |f| {
.unwrap_or("Missing image".into()) f.to_string_lossy().to_string()
})
} else { } else {
"Missing image".into() "Missing image".into()
} }
@ -85,7 +86,7 @@ impl From<&Value> for Image {
let path = let path =
p.to_str().unwrap_or_default().to_string(); p.to_str().unwrap_or_default().to_string();
let title = let title =
path.rsplit_once("/").unwrap_or_default().1; path.rsplit_once('/').unwrap_or_default().1;
title.to_string() title.to_string()
}); });
Self { Self {
@ -154,14 +155,16 @@ impl Model<Image> {
.await; .await;
match result { match result {
Ok(v) => { Ok(v) => {
for image in v.into_iter() { for image in v {
let _ = self.add_item(image); let _ = self.add_item(image);
} }
} }
Err(e) => { Err(e) => {
error!("There was an error in converting images: {e}") error!(
"There was an error in converting images: {e}"
);
}
} }
};
} }
} }
@ -172,7 +175,7 @@ pub async fn update_image_in_db(
let path = image let path = image
.path .path
.to_str() .to_str()
.map(|s| s.to_string()) .map(std::string::ToString::to_string)
.unwrap_or_default(); .unwrap_or_default();
query!( query!(
r#"UPDATE images SET title = $2, file_path = $3 WHERE id = $1"#, r#"UPDATE images SET title = $2, file_path = $3 WHERE id = $1"#,

View file

@ -21,11 +21,11 @@ pub enum ServiceItemKind {
impl std::fmt::Display for ServiceItemKind { impl std::fmt::Display for ServiceItemKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let s = match self { let s = match self {
Self::Song(s) => "song".to_owned(), Self::Song(_) => "song".to_owned(),
Self::Image(i) => "image".to_owned(), Self::Image(_) => "image".to_owned(),
Self::Video(v) => "video".to_owned(), Self::Video(_) => "video".to_owned(),
Self::Presentation(p) => "html".to_owned(), Self::Presentation(_) => "html".to_owned(),
Self::Content(s) => "content".to_owned(), Self::Content(_) => "content".to_owned(),
}; };
write!(f, "{s}") write!(f, "{s}")
} }
@ -50,7 +50,7 @@ impl std::fmt::Display for ServiceItemKind {
// } // }
impl From<ServiceItemKind> for String { impl From<ServiceItemKind> for String {
fn from(val: ServiceItemKind) -> String { fn from(val: ServiceItemKind) -> Self {
match val { match val {
ServiceItemKind::Song(_) => "song".to_owned(), ServiceItemKind::Song(_) => "song".to_owned(),
ServiceItemKind::Video(_) => "video".to_owned(), ServiceItemKind::Video(_) => "video".to_owned(),
@ -76,7 +76,9 @@ impl Display for ParseError {
f: &mut std::fmt::Formatter<'_>, f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result { ) -> std::fmt::Result {
let message = match self { let message = match self {
Self::UnknownType => "The type does not exist. It needs to be one of 'song', 'video', 'image', 'presentation', or 'content'", Self::UnknownType => {
"The type does not exist. It needs to be one of 'song', 'video', 'image', 'presentation', or 'content'"
}
}; };
write!(f, "Error: {message}") write!(f, "Error: {message}")
} }

View file

@ -1,7 +1,6 @@
use std::mem::replace; use std::mem::replace;
use cosmic::iced::Executor; use miette::{Result, miette};
use miette::{miette, Result};
use sqlx::{Connection, SqliteConnection}; use sqlx::{Connection, SqliteConnection};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -10,7 +9,7 @@ pub struct Model<T> {
pub kind: LibraryKind, pub kind: LibraryKind,
} }
#[derive(Debug, Clone, PartialEq, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum LibraryKind { pub enum LibraryKind {
Song, Song,
Video, Video,
@ -46,6 +45,7 @@ impl<T> Model<T> {
Ok(()) Ok(())
} }
#[must_use]
pub fn get_item(&self, index: i32) -> Option<&T> { pub fn get_item(&self, index: i32) -> Option<&T> {
self.items.get(index as usize) self.items.get(index as usize)
} }

View file

@ -75,8 +75,9 @@ impl Content for Presentation {
if self.path.exists() { if self.path.exists() {
self.path self.path
.file_name() .file_name()
.map(|f| f.to_string_lossy().to_string()) .map_or("Missing presentation".into(), |f| {
.unwrap_or("Missing presentation".into()) f.to_string_lossy().to_string()
})
} else { } else {
"Missing presentation".into() "Missing presentation".into()
} }
@ -190,14 +191,16 @@ impl ServiceTrait for Presentation {
} }
impl Presentation { impl Presentation {
#[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
title: "".to_string(), title: String::new(),
..Default::default() ..Default::default()
} }
} }
pub fn get_kind(&self) -> &PresKind { #[must_use]
pub const fn get_kind(&self) -> &PresKind {
&self.kind &self.kind
} }
} }
@ -240,7 +243,7 @@ impl Model<Presentation> {
.await; .await;
match result { match result {
Ok(v) => { Ok(v) => {
for presentation in v.into_iter() { for presentation in v {
let _ = self.add_item(Presentation { let _ = self.add_item(Presentation {
id: presentation.id, id: presentation.id,
title: presentation.title, title: presentation.title,
@ -267,7 +270,7 @@ pub async fn update_presentation_in_db(
let path = presentation let path = presentation
.path .path
.to_str() .to_str()
.map(|s| s.to_string()) .map(std::string::ToString::to_string)
.unwrap_or_default(); .unwrap_or_default();
let html = presentation.kind == PresKind::Html; let html = presentation.kind == PresKind::Html;
query!( query!(

View file

@ -79,13 +79,13 @@ impl AsMimeTypes for ServiceItem {
impl From<&ServiceItem> for Value { impl From<&ServiceItem> for Value {
fn from(value: &ServiceItem) -> Self { fn from(value: &ServiceItem) -> Self {
match &value.kind { match &value.kind {
ServiceItemKind::Song(song) => Value::from(song), ServiceItemKind::Song(song) => Self::from(song),
ServiceItemKind::Video(video) => Value::from(video), ServiceItemKind::Video(video) => Self::from(video),
ServiceItemKind::Image(image) => Value::from(image), ServiceItemKind::Image(image) => Self::from(image),
ServiceItemKind::Presentation(presentation) => { ServiceItemKind::Presentation(presentation) => {
Value::from(presentation) Self::from(presentation)
} }
ServiceItemKind::Content(slide) => Value::from(slide), ServiceItemKind::Content(slide) => Self::from(slide),
} }
} }
} }
@ -172,8 +172,8 @@ impl From<&Value> for ServiceItem {
} else if let Some(background) = } else if let Some(background) =
list.get(background_pos) list.get(background_pos)
{ {
match background { if let Value::List(item) = background {
Value::List(item) => match &item[0] { match &item[0] {
Value::Symbol(Symbol(s)) Value::Symbol(Symbol(s))
if s == "image" => if s == "image" =>
{ {
@ -196,30 +196,29 @@ impl From<&Value> for ServiceItem {
)) ))
} }
_ => todo!(), _ => todo!(),
}, }
_ => { } else {
error!( error!(
"There is no background here: {:?}", "There is no background here: {:?}",
background background
); );
ServiceItem::default() Self::default()
}
} }
} else { } else {
error!( error!(
"There is no background here: {:?}", "There is no background here: {:?}",
background_pos background_pos
); );
ServiceItem::default() Self::default()
} }
} }
Value::Symbol(Symbol(s)) if s == "song" => { Value::Symbol(Symbol(s)) if s == "song" => {
let song = lisp_to_song(list.clone()); let song = lisp_to_song(list.clone());
Self::from(&song) Self::from(&song)
} }
_ => ServiceItem::default(), _ => Self::default(),
}, },
_ => ServiceItem::default(), _ => Self::default(),
} }
} }
} }
@ -258,7 +257,7 @@ impl From<&Song> for ServiceItem {
kind: ServiceItemKind::Song(song.clone()), kind: ServiceItemKind::Song(song.clone()),
database_id: song.id, database_id: song.id,
title: song.title.clone(), title: song.title.clone(),
slides: slides, slides,
..Default::default() ..Default::default()
} }
} else { } else {
@ -279,7 +278,7 @@ impl From<&Video> for ServiceItem {
kind: ServiceItemKind::Video(video.clone()), kind: ServiceItemKind::Video(video.clone()),
database_id: video.id, database_id: video.id,
title: video.title.clone(), title: video.title.clone(),
slides: slides, slides,
..Default::default() ..Default::default()
} }
} else { } else {
@ -300,7 +299,7 @@ impl From<&Image> for ServiceItem {
kind: ServiceItemKind::Image(image.clone()), kind: ServiceItemKind::Image(image.clone()),
database_id: image.id, database_id: image.id,
title: image.title.clone(), title: image.title.clone(),
slides: slides, slides,
..Default::default() ..Default::default()
} }
} else { } else {
@ -323,7 +322,7 @@ impl From<&Presentation> for ServiceItem {
), ),
database_id: presentation.id, database_id: presentation.id,
title: presentation.title.clone(), title: presentation.title.clone(),
slides: slides, slides,
..Default::default() ..Default::default()
}, },
Err(e) => { Err(e) => {

View file

@ -107,9 +107,9 @@ impl TryFrom<&Background> for Video {
fn try_from( fn try_from(
value: &Background, value: &Background,
) -> std::result::Result<Self, Self::Error> { ) -> std::result::Result<Self, Self::Error> {
Video::new( Self::new(
&url::Url::from_file_path(value.path.clone()) &url::Url::from_file_path(value.path.clone())
.map_err(|_| ParseError::BackgroundNotVideo)?, .map_err(|()| ParseError::BackgroundNotVideo)?,
) )
.map_err(|_| ParseError::BackgroundNotVideo) .map_err(|_| ParseError::BackgroundNotVideo)
} }
@ -121,9 +121,9 @@ impl TryFrom<Background> for Video {
fn try_from( fn try_from(
value: Background, value: Background,
) -> std::result::Result<Self, Self::Error> { ) -> std::result::Result<Self, Self::Error> {
Video::new( Self::new(
&url::Url::from_file_path(value.path) &url::Url::from_file_path(value.path)
.map_err(|_| ParseError::BackgroundNotVideo)?, .map_err(|()| ParseError::BackgroundNotVideo)?,
) )
.map_err(|_| ParseError::BackgroundNotVideo) .map_err(|_| ParseError::BackgroundNotVideo)
} }
@ -132,7 +132,7 @@ impl TryFrom<Background> for Video {
impl TryFrom<String> for Background { impl TryFrom<String> for Background {
type Error = ParseError; type Error = ParseError;
fn try_from(value: String) -> Result<Self, Self::Error> { fn try_from(value: String) -> Result<Self, Self::Error> {
Background::try_from(value.as_str()) Self::try_from(value.as_str())
} }
} }
@ -147,7 +147,7 @@ impl TryFrom<PathBuf> for Background {
.to_str() .to_str()
.unwrap() .unwrap()
.to_string(); .to_string();
let path = path.replace("~", &home); let path = path.replace('~', &home);
PathBuf::from(path) PathBuf::from(path)
} else { } else {
path path
@ -192,10 +192,10 @@ impl TryFrom<&str> for Background {
type Error = ParseError; type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(value: &str) -> Result<Self, Self::Error> {
let value = value.trim_start_matches("file://"); let value = value.trim_start_matches("file://");
if value.starts_with("~") { if value.starts_with('~') {
if let Some(home) = dirs::home_dir() { if let Some(home) = dirs::home_dir() {
if let Some(home) = home.to_str() { if let Some(home) = home.to_str() {
let value = value.replace("~", home); let value = value.replace('~', home);
Self::try_from(PathBuf::from(value)) Self::try_from(PathBuf::from(value))
} else { } else {
Self::try_from(PathBuf::from(value)) Self::try_from(PathBuf::from(value))
@ -252,9 +252,9 @@ impl Display for ParseError {
impl From<String> for BackgroundKind { impl From<String> for BackgroundKind {
fn from(value: String) -> Self { fn from(value: String) -> Self {
if value == "image" { if value == "image" {
BackgroundKind::Image Self::Image
} else { } else {
BackgroundKind::Video Self::Video
} }
} }
} }
@ -281,7 +281,7 @@ impl Slide {
self self
} }
pub fn set_font_size(mut self, font_size: i32) -> Self { pub const fn set_font_size(mut self, font_size: i32) -> Self {
self.font_size = font_size; self.font_size = font_size;
self self
} }
@ -291,12 +291,12 @@ impl Slide {
self self
} }
pub fn set_pdf_index(mut self, pdf_index: u32) -> Self { pub const fn set_pdf_index(mut self, pdf_index: u32) -> Self {
self.pdf_index = pdf_index; self.pdf_index = pdf_index;
self self
} }
pub fn background(&self) -> &Background { pub const fn background(&self) -> &Background {
&self.background &self.background
} }
@ -304,11 +304,11 @@ impl Slide {
self.text.clone() self.text.clone()
} }
pub fn text_alignment(&self) -> TextAlignment { pub const fn text_alignment(&self) -> TextAlignment {
self.text_alignment self.text_alignment
} }
pub fn font_size(&self) -> i32 { pub const fn font_size(&self) -> i32 {
self.font_size self.font_size
} }
@ -316,7 +316,7 @@ impl Slide {
self.font.clone() self.font.clone()
} }
pub fn video_loop(&self) -> bool { pub const fn video_loop(&self) -> bool {
self.video_loop self.video_loop
} }
@ -328,13 +328,13 @@ impl Slide {
self.pdf_page.clone() self.pdf_page.clone()
} }
pub fn pdf_index(&self) -> u32 { pub const fn pdf_index(&self) -> u32 {
self.pdf_index self.pdf_index
} }
pub fn song_slides(song: &Song) -> Result<Vec<Self>> { pub fn song_slides(song: &Song) -> Result<Vec<Self>> {
let lyrics = song.get_lyrics()?; let lyrics = song.get_lyrics()?;
let slides: Vec<Slide> = lyrics let slides: Vec<Self> = lyrics
.iter() .iter()
.map(|l| { .map(|l| {
let song = song.clone(); let song = song.clone();
@ -358,7 +358,7 @@ impl Slide {
Ok(slides) Ok(slides)
} }
pub(crate) fn set_index(&mut self, index: i32) { pub(crate) const fn set_index(&mut self, index: i32) {
self.id = index; self.id = index;
} }
@ -381,7 +381,7 @@ impl From<&Value> for Slide {
fn from(value: &Value) -> Self { fn from(value: &Value) -> Self {
match value { match value {
Value::List(list) => lisp_to_slide(list), Value::List(list) => lisp_to_slide(list),
_ => Slide::default(), _ => Self::default(),
} }
} }
} }
@ -404,7 +404,7 @@ fn lisp_to_slide(lisp: &Vec<Value>) -> Slide {
slide = slide.background(lisp_to_background(background)); slide = slide.background(lisp_to_background(background));
} else { } else {
slide = slide.background(Background::default()); slide = slide.background(Background::default());
}; }
let text_position = lisp.iter().position(|v| match v { let text_position = lisp.iter().position(|v| match v {
Value::List(vec) => { Value::List(vec) => {
@ -691,9 +691,7 @@ impl SlideBuilder {
impl Image { impl Image {
fn new() -> Self { fn new() -> Self {
Self { Default::default()
..Default::default()
}
} }
} }

View file

@ -1,16 +1,15 @@
use std::{collections::HashMap, option::Option, path::PathBuf}; use std::{collections::HashMap, option::Option, path::PathBuf};
use cosmic::iced::Executor;
use crisp::types::{Keyword, Symbol, Value}; use crisp::types::{Keyword, Symbol, Value};
use miette::{miette, IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result, miette};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{ use sqlx::{
pool::PoolConnection, query, sqlite::SqliteRow, FromRow, Row, FromRow, Row, Sqlite, SqliteConnection, SqlitePool,
Sqlite, SqliteConnection, SqlitePool, pool::PoolConnection, query, sqlite::SqliteRow,
}; };
use tracing::error; use tracing::error;
use crate::{core::slide, Slide, SlideBuilder}; use crate::{Slide, SlideBuilder, core::slide};
use super::{ use super::{
content::Content, content::Content,
@ -128,7 +127,9 @@ impl FromRow<'_, SqliteRow> for Song {
})), })),
verse_order: Some({ verse_order: Some({
let str: &str = row.try_get(0)?; let str: &str = row.try_get(0)?;
str.split(' ').map(|s| s.to_string()).collect() str.split(' ')
.map(std::string::ToString::to_string)
.collect()
}), }),
background: { background: {
let string: String = row.try_get(7)?; let string: String = row.try_get(7)?;
@ -250,9 +251,7 @@ pub fn lisp_to_song(list: Vec<Value>) -> Song {
.position(|v| v == &Value::Keyword(Keyword::from("title"))) .position(|v| v == &Value::Keyword(Keyword::from("title")))
{ {
let pos = key_pos + 1; let pos = key_pos + 1;
list.get(pos) list.get(pos).map_or(String::from("song"), String::from)
.map(String::from)
.unwrap_or(String::from("song"))
} else { } else {
String::from("song") String::from("song")
}; };
@ -319,30 +318,30 @@ pub fn lisp_to_song(list: Vec<Value>) -> Song {
let lyric = String::from(&lyric[1]); let lyric = String::from(&lyric[1]);
let verse_title = match lyric_verse.as_str() { let verse_title = match lyric_verse.as_str() {
"i1" => r#"\n\nIntro 1\n"#, "i1" => r"\n\nIntro 1\n",
"i2" => r#"\n\nIntro 1\n"#, "i2" => r"\n\nIntro 1\n",
"v1" => r#"\n\nVerse 1\n"#, "v1" => r"\n\nVerse 1\n",
"v2" => r#"\n\nVerse 2\n"#, "v2" => r"\n\nVerse 2\n",
"v3" => r#"\n\nVerse 3\n"#, "v3" => r"\n\nVerse 3\n",
"v4" => r#"\n\nVerse 4\n"#, "v4" => r"\n\nVerse 4\n",
"v5" => r#"\n\nVerse 5\n"#, "v5" => r"\n\nVerse 5\n",
"c1" => r#"\n\nChorus 1\n"#, "c1" => r"\n\nChorus 1\n",
"c2" => r#"\n\nChorus 2\n"#, "c2" => r"\n\nChorus 2\n",
"c3" => r#"\n\nChorus 3\n"#, "c3" => r"\n\nChorus 3\n",
"c4" => r#"\n\nChorus 4\n"#, "c4" => r"\n\nChorus 4\n",
"b1" => r#"\n\nBridge 1\n"#, "b1" => r"\n\nBridge 1\n",
"b2" => r#"\n\nBridge 2\n"#, "b2" => r"\n\nBridge 2\n",
"e1" => r#"\n\nEnding 1\n"#, "e1" => r"\n\nEnding 1\n",
"e2" => r#"\n\nEnding 2\n"#, "e2" => r"\n\nEnding 2\n",
"o1" => r#"\n\nOther 1\n"#, "o1" => r"\n\nOther 1\n",
"o2" => r#"\n\nOther 2\n"#, "o2" => r"\n\nOther 2\n",
_ => "", _ => "",
}; };
let lyric = format!("{verse_title}{lyric}"); let lyric = format!("{verse_title}{lyric}");
let lyric = lyric.replace( let lyric = lyric.replace(
"\\n", r#" "\\n", r"
"#, ",
); );
lyrics.push(lyric); lyrics.push(lyric);
} }
@ -392,15 +391,15 @@ impl Model<Song> {
let result = query(r#"SELECT verse_order as "verse_order!", font_size as "font_size!: i32", background_type as "background_type!", horizontal_text_alignment as "horizontal_text_alignment!", vertical_text_alignment as "vertical_text_alignment!", title as "title!", font as "font!", background as "background!", lyrics as "lyrics!", ccli as "ccli!", author as "author!", audio as "audio!", id as "id: i32" from songs"#).fetch_all(db).await; let result = query(r#"SELECT verse_order as "verse_order!", font_size as "font_size!: i32", background_type as "background_type!", horizontal_text_alignment as "horizontal_text_alignment!", vertical_text_alignment as "vertical_text_alignment!", title as "title!", font as "font!", background as "background!", lyrics as "lyrics!", ccli as "ccli!", author as "author!", audio as "audio!", id as "id: i32" from songs"#).fetch_all(db).await;
match result { match result {
Ok(s) => { Ok(s) => {
for song in s.into_iter() { for song in s {
match Song::from_row(&song) { match Song::from_row(&song) {
Ok(song) => { Ok(song) => {
let _ = self.add_item(song); let _ = self.add_item(song);
} }
Err(e) => { Err(e) => {
error!("Could not convert song: {e}") error!("Could not convert song: {e}");
}
} }
};
} }
} }
Err(e) => { Err(e) => {
@ -425,7 +424,7 @@ pub async fn update_song_in_db(
}) })
.collect::<String>() .collect::<String>()
} else { } else {
String::from("") String::new()
} }
}; };
@ -488,13 +487,13 @@ impl Song {
let verse_order = self.verse_order.clone(); let verse_order = self.verse_order.clone();
let mut lyric_map = HashMap::new(); let mut lyric_map = HashMap::new();
let mut verse_title = String::from(""); let mut verse_title = String::new();
let mut lyric = String::from(""); let mut lyric = String::new();
for (i, line) in raw_lyrics.split('\n').enumerate() { for (i, line) in raw_lyrics.split('\n').enumerate() {
if VERSE_KEYWORDS.contains(&line) { if VERSE_KEYWORDS.contains(&line) {
if i != 0 { if i != 0 {
lyric_map.insert(verse_title, lyric); lyric_map.insert(verse_title, lyric);
lyric = String::from(""); lyric = String::new();
verse_title = line.to_string(); verse_title = line.to_string();
} else { } else {
verse_title = line.to_string(); verse_title = line.to_string();
@ -535,7 +534,7 @@ impl Song {
lyric_list.push(lyric.to_string()); lyric_list.push(lyric.to_string());
} else { } else {
// error!("NOT WORKING!"); // error!("NOT WORKING!");
}; }
} }
// for lyric in lyric_list.iter() { // for lyric in lyric_list.iter() {
// debug!(lyric = ?lyric) // debug!(lyric = ?lyric)
@ -626,7 +625,27 @@ You saved my soul"
let lyrics = song.get_lyrics(); let lyrics = song.get_lyrics();
match lyrics { match lyrics {
Ok(lyrics) => { Ok(lyrics) => {
assert_eq!(vec!["From the Day\nI Am They", "When You found me,\nI was so blind\nMy sin was before me,\nI was swallowed by pride", "But out of the darkness,\nYou brought me to Your light\nYou showed me new mercy\nAnd opened up my eyes", "From the day\nYou saved my soul\n'Til the very moment\nWhen I come home", "I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul", "Where brilliant light\nIs all around\nAnd endless joy\nIs the only sound", "Oh, rest my heart\nForever now\nOh, in Your arms\nI'll always be found", "From the day\nYou saved my soul\n'Til the very moment\nWhen I come home", "I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul", "My love is Yours\nMy heart is Yours\nMy life is Yours\nForever", "My love is Yours\nMy heart is Yours\nMy life is Yours\nForever", "From the day\nYou saved my soul\n'Til the very moment\nWhen I come home", "I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul", "From the day\nYou saved my soul\n'Til the very moment\nWhen I come home", "I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul", "Oh Oh Oh\nFrom the day\nYou saved my soul\n"], lyrics); assert_eq!(
vec![
"From the Day\nI Am They",
"When You found me,\nI was so blind\nMy sin was before me,\nI was swallowed by pride",
"But out of the darkness,\nYou brought me to Your light\nYou showed me new mercy\nAnd opened up my eyes",
"From the day\nYou saved my soul\n'Til the very moment\nWhen I come home",
"I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul",
"Where brilliant light\nIs all around\nAnd endless joy\nIs the only sound",
"Oh, rest my heart\nForever now\nOh, in Your arms\nI'll always be found",
"From the day\nYou saved my soul\n'Til the very moment\nWhen I come home",
"I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul",
"My love is Yours\nMy heart is Yours\nMy life is Yours\nForever",
"My love is Yours\nMy heart is Yours\nMy life is Yours\nForever",
"From the day\nYou saved my soul\n'Til the very moment\nWhen I come home",
"I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul",
"From the day\nYou saved my soul\n'Til the very moment\nWhen I come home",
"I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul",
"Oh Oh Oh\nFrom the day\nYou saved my soul\n"
],
lyrics
);
} }
Err(e) => { Err(e) => {
assert!(false, "{:?}", e) assert!(false, "{:?}", e)

View file

@ -11,7 +11,9 @@ pub fn bg_from_video(
video: &Path, video: &Path,
screenshot: &Path, screenshot: &Path,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
if !screenshot.exists() { if screenshot.exists() {
debug!("Screenshot already exists");
} else {
let output_duration = Command::new("ffprobe") let output_duration = Command::new("ffprobe")
.args(["-i", &video.to_string_lossy()]) .args(["-i", &video.to_string_lossy()])
.output() .output()
@ -26,9 +28,9 @@ pub fn bg_from_video(
let mut duration = log.split_off(duration_index + 10); let mut duration = log.split_off(duration_index + 10);
duration.truncate(11); duration.truncate(11);
// debug!("rust-duration-is: {duration}"); // debug!("rust-duration-is: {duration}");
let mut hours = String::from(""); let mut hours = String::new();
let mut minutes = String::from(""); let mut minutes = String::new();
let mut seconds = String::from(""); let mut seconds = String::new();
for (i, c) in duration.chars().enumerate() { for (i, c) in duration.chars().enumerate() {
if i <= 1 { if i <= 1 {
hours.push(c); hours.push(c);
@ -63,8 +65,6 @@ pub fn bg_from_video(
.expect("failed to execute ffmpeg"); .expect("failed to execute ffmpeg");
// io::stdout().write_all(&output.stdout).unwrap(); // io::stdout().write_all(&output.stdout).unwrap();
// io::stderr().write_all(&output.stderr).unwrap(); // io::stderr().write_all(&output.stderr).unwrap();
} else {
debug!("Screenshot already exists");
} }
Ok(()) Ok(())
} }

View file

@ -7,13 +7,12 @@ use super::{
service_items::ServiceTrait, service_items::ServiceTrait,
slide::Slide, slide::Slide,
}; };
use cosmic::iced::Executor;
use crisp::types::{Keyword, Symbol, Value}; use crisp::types::{Keyword, Symbol, Value};
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{ use sqlx::{
pool::PoolConnection, query, query_as, Sqlite, SqliteConnection, Sqlite, SqliteConnection, SqlitePool, pool::PoolConnection,
SqlitePool, query, query_as,
}; };
use std::path::PathBuf; use std::path::PathBuf;
use tracing::error; use tracing::error;
@ -57,8 +56,9 @@ impl Content for Video {
if self.path.exists() { if self.path.exists() {
self.path self.path
.file_name() .file_name()
.map(|f| f.to_string_lossy().to_string()) .map_or("Missing video".into(), |f| {
.unwrap_or("Missing video".into()) f.to_string_lossy().to_string()
})
} else { } else {
"Missing video".into() "Missing video".into()
} }
@ -90,7 +90,7 @@ impl From<&Value> for Video {
let path = let path =
p.to_str().unwrap_or_default().to_string(); p.to_str().unwrap_or_default().to_string();
let title = let title =
path.rsplit_once("/").unwrap_or_default().1; path.rsplit_once('/').unwrap_or_default().1;
title.to_string() title.to_string()
}); });
@ -124,8 +124,7 @@ impl From<&Value> for Video {
}) { }) {
let pos = loop_pos + 1; let pos = loop_pos + 1;
list.get(pos) list.get(pos)
.map(|l| String::from(l) == *"true") .is_some_and(|l| String::from(l) == *"true")
.unwrap_or_default()
} else { } else {
false false
}; };
@ -193,14 +192,16 @@ impl Model<Video> {
let result = query_as!(Video, r#"SELECT title as "title!", file_path as "path!", start_time as "start_time!: f32", end_time as "end_time!: f32", loop as "looping!", id as "id: i32" from videos"#).fetch_all(db).await; let result = query_as!(Video, r#"SELECT title as "title!", file_path as "path!", start_time as "start_time!: f32", end_time as "end_time!: f32", loop as "looping!", id as "id: i32" from videos"#).fetch_all(db).await;
match result { match result {
Ok(v) => { Ok(v) => {
for video in v.into_iter() { for video in v {
let _ = self.add_item(video); let _ = self.add_item(video);
} }
} }
Err(e) => { Err(e) => {
error!("There was an error in converting videos: {e}") error!(
"There was an error in converting videos: {e}"
);
}
} }
};
} }
} }
@ -211,7 +212,7 @@ pub async fn update_video_in_db(
let path = video let path = video
.path .path
.to_str() .to_str()
.map(|s| s.to_string()) .map(std::string::ToString::to_string)
.unwrap_or_default(); .unwrap_or_default();
query!( query!(
r#"UPDATE videos SET title = $2, file_path = $3, start_time = $4, end_time = $5, loop = $6 WHERE id = $1"#, r#"UPDATE videos SET title = $2, file_path = $3, start_time = $4, end_time = $5, loop = $6 WHERE id = $1"#,

View file

@ -1,31 +1,32 @@
use clap::{command, Parser}; use clap::{Parser, command};
use core::service_items::ServiceItem; use core::service_items::ServiceItem;
use core::slide::*; use core::slide::{
Background, BackgroundKind, Slide, SlideBuilder, TextAlignment,
};
use core::songs::Song; use core::songs::Song;
use cosmic::app::context_drawer::ContextDrawer; use cosmic::app::context_drawer::ContextDrawer;
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::alignment::Vertical; use cosmic::iced::alignment::Vertical;
use cosmic::iced::keyboard::{Key, Modifiers}; use cosmic::iced::keyboard::{Key, Modifiers};
use cosmic::iced::window::{Mode, Position}; use cosmic::iced::window::{Mode, Position};
use cosmic::iced::{self, event, window, Length, Point}; use cosmic::iced::{self, Length, Point, event, window};
use cosmic::iced_futures::Subscription; use cosmic::iced_futures::Subscription;
use cosmic::iced_widget::{column, row, stack}; use cosmic::iced_widget::{column, row, stack};
use cosmic::theme; use cosmic::theme;
use cosmic::widget::Container;
use cosmic::widget::dnd_destination::dnd_destination; use cosmic::widget::dnd_destination::dnd_destination;
use cosmic::widget::nav_bar::nav_bar_style; use cosmic::widget::nav_bar::nav_bar_style;
use cosmic::widget::segmented_button::Entity;
use cosmic::widget::text; use cosmic::widget::text;
use cosmic::widget::tooltip::Position as TPosition; use cosmic::widget::tooltip::Position as TPosition;
use cosmic::widget::{ use cosmic::widget::{
button, horizontal_space, mouse_area, nav_bar, search_input, Space, button, horizontal_space, mouse_area, nav_bar,
tooltip, vertical_space, Space, search_input, tooltip, vertical_space,
}; };
use cosmic::widget::{icon, slider}; use cosmic::widget::{icon, slider};
use cosmic::{executor, Application, ApplicationExt, Element}; use cosmic::{Application, ApplicationExt, Element, executor};
use cosmic::{widget::Container, Theme};
use crisp::types::Value; use crisp::types::Value;
use lisp::parse_lisp; use lisp::parse_lisp;
use miette::{miette, Result}; use miette::{Result, miette};
use rayon::prelude::*; use rayon::prelude::*;
use resvg::usvg::fontdb; use resvg::usvg::fontdb;
use std::fs::read_to_string; use std::fs::read_to_string;
@ -34,10 +35,10 @@ use std::sync::Arc;
use tracing::{debug, level_filters::LevelFilter}; use tracing::{debug, level_filters::LevelFilter};
use tracing::{error, warn}; use tracing::{error, warn};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use ui::EditorMode;
use ui::library::{self, Library}; use ui::library::{self, Library};
use ui::presenter::{self, Presenter}; use ui::presenter::{self, Presenter};
use ui::song_editor::{self, SongEditor}; use ui::song_editor::{self, SongEditor};
use ui::EditorMode;
use crate::core::kinds::ServiceItemKind; use crate::core::kinds::ServiceItemKind;
use crate::ui::text_svg::{self}; use crate::ui::text_svg::{self};
@ -94,9 +95,9 @@ fn main() -> Result<()> {
.map_err(|e| miette!("Invalid things... {}", e)) .map_err(|e| miette!("Invalid things... {}", e))
} }
fn theme(_state: &App) -> Theme { // fn theme(_state: &App) -> Theme {
Theme::dark() // Theme::dark()
} // }
struct App { struct App {
core: Core, core: Core,
@ -135,9 +136,6 @@ enum Message {
Quit, Quit,
Key(Key, Modifiers), Key(Key, Modifiers),
None, None,
DndLeave(Entity),
DndEnter(Entity, Vec<String>),
DndDrop,
EditorToggle(bool), EditorToggle(bool),
ChangeServiceItem(usize), ChangeServiceItem(usize),
AddServiceItem(usize, ServiceItem), AddServiceItem(usize, ServiceItem),
@ -233,11 +231,11 @@ impl cosmic::Application for App {
// } // }
// nav_model.activate_position(0); // nav_model.activate_position(0);
let mut app = App { let mut app = Self {
presenter, presenter,
core, core,
nav_model, nav_model,
service: items.clone(), service: items,
file: PathBuf::default(), file: PathBuf::default(),
windows, windows,
presentation_open: false, presentation_open: false,
@ -248,7 +246,7 @@ impl cosmic::Application for App {
song_editor, song_editor,
searching: false, searching: false,
search_results: vec![], search_results: vec![],
search_query: "".into(), search_query: String::new(),
search_id: cosmic::widget::Id::unique(), search_id: cosmic::widget::Id::unique(),
current_item: (0, 0), current_item: (0, 0),
library_dragged_item: None, library_dragged_item: None,
@ -259,11 +257,11 @@ impl cosmic::Application for App {
if input.ui { if input.ui {
debug!("main view"); debug!("main view");
batch.push(app.update_title()) batch.push(app.update_title());
} else { } else {
debug!("window view"); debug!("window view");
batch.push(app.show_window()) batch.push(app.show_window());
}; }
batch.push(app.add_library()); batch.push(app.add_library());
// batch.push(app.add_service(items, Arc::clone(&fontdb))); // batch.push(app.add_service(items, Arc::clone(&fontdb)));
@ -403,7 +401,7 @@ impl cosmic::Application for App {
.fold(0, |a, item| a + item.slides.len()); .fold(0, |a, item| a + item.slides.len());
let total_slides_text = let total_slides_text =
format!("Total Slides: {}", total_slides); format!("Total Slides: {total_slides}");
let row = row![ let row = row![
text::body(total_items_text), text::body(total_items_text),
text::body(total_slides_text) text::body(total_slides_text)
@ -635,7 +633,7 @@ impl cosmic::Application for App {
cosmic::Action::App( cosmic::Action::App(
Message::Present(m), Message::Present(m),
) )
})) }));
} }
_ => todo!(), _ => todo!(),
} }
@ -661,7 +659,7 @@ impl cosmic::Application for App {
m, m,
), ),
) )
})) }));
} }
_ => todo!(), _ => todo!(),
} }
@ -694,7 +692,7 @@ impl cosmic::Application for App {
cosmic::Action::App( cosmic::Action::App(
Message::Present(m), Message::Present(m),
) )
})) }));
} }
_ => todo!(), _ => todo!(),
} }
@ -735,7 +733,7 @@ impl cosmic::Application for App {
m, m,
), ),
) )
})) }));
} }
_ => todo!(), _ => todo!(),
} }
@ -816,10 +814,8 @@ impl cosmic::Application for App {
}); });
self.windows.push(id); self.windows.push(id);
_ = self.set_window_title( _ = self
format!("window_{}", count), .set_window_title(format!("window_{count}"), id);
id,
);
spawn_window.map(|id| { spawn_window.map(|id| {
cosmic::Action::App(Message::WindowOpened( cosmic::Action::App(Message::WindowOpened(
@ -873,34 +869,6 @@ impl cosmic::Application for App {
Task::none() Task::none()
} }
Message::Quit => cosmic::iced::exit(), Message::Quit => cosmic::iced::exit(),
Message::DndEnter(entity, data) => {
debug!(?entity);
debug!(?data);
Task::none()
}
Message::DndDrop => {
// debug!(?entity);
// debug!(?action);
// debug!(?service_item);
if let Some(library) = &self.library
&& let Some((lib, item)) = library.dragged_item
{
// match lib {
// core::model::LibraryKind::Song => ,
// core::model::LibraryKind::Video => todo!(),
// core::model::LibraryKind::Image => todo!(),
// core::model::LibraryKind::Presentation => todo!(),
// }
let item = library.get_song(item).unwrap();
let item = ServiceItem::from(item);
self.nav_model
.insert()
.text(item.title.clone())
.data(item);
}
Task::none()
}
Message::AddLibrary(library) => { Message::AddLibrary(library) => {
self.library = Some(library); self.library = Some(library);
Task::none() Task::none()
@ -910,10 +878,6 @@ impl cosmic::Application for App {
Task::none() Task::none()
} }
Message::None => Task::none(), Message::None => Task::none(),
Message::DndLeave(entity) => {
// debug!(?entity);
Task::none()
}
Message::EditorToggle(edit) => { Message::EditorToggle(edit) => {
if edit { if edit {
self.editor_mode = Some(EditorMode::Song); self.editor_mode = Some(EditorMode::Song);
@ -992,7 +956,7 @@ impl cosmic::Application for App {
Task::none() Task::none()
} }
Message::CloseSearch => { Message::CloseSearch => {
self.search_query = "".into(); self.search_query = String::new();
self.search_results = vec![]; self.search_results = vec![];
self.searching = false; self.searching = false;
Task::none() Task::none()
@ -1006,12 +970,12 @@ impl cosmic::Application for App {
song_editor::Message::ChangeSong(song), song_editor::Message::ChangeSong(song),
)) ))
} }
ServiceItemKind::Video(video) => todo!(), ServiceItemKind::Video(_video) => todo!(),
ServiceItemKind::Image(image) => todo!(), ServiceItemKind::Image(_image) => todo!(),
ServiceItemKind::Presentation(presentation) => { ServiceItemKind::Presentation(_presentation) => {
todo!() todo!()
} }
ServiceItemKind::Content(slide) => todo!(), ServiceItemKind::Content(_slide) => todo!(),
} }
} }
} }
@ -1198,7 +1162,7 @@ where
}) })
} }
fn add_library(&mut self) -> Task<Message> { fn add_library(&self) -> Task<Message> {
Task::perform(async move { Library::new().await }, |x| { Task::perform(async move { Library::new().await }, |x| {
cosmic::Action::App(Message::AddLibrary(x)) cosmic::Action::App(Message::AddLibrary(x))
}) })
@ -1220,7 +1184,7 @@ where
} }
fn add_service( fn add_service(
&mut self, &self,
items: Vec<ServiceItem>, items: Vec<ServiceItem>,
fontdb: Arc<fontdb::Database>, fontdb: Arc<fontdb::Database>,
) -> Task<Message> { ) -> Task<Message> {

View file

@ -1,3 +1,5 @@
use std::collections::HashMap;
use cosmic::{ use cosmic::{
iced::{ iced::{
alignment::Vertical, clipboard::dnd::DndAction, alignment::Vertical, clipboard::dnd::DndAction,
@ -7,9 +9,10 @@ use cosmic::{
iced_widget::{column, row as rowm, text as textm}, iced_widget::{column, row as rowm, text as textm},
theme, theme,
widget::{ widget::{
button, container, horizontal_space, icon, mouse_area, button, container, context_menu, horizontal_space, icon,
responsive, row, scrollable, text, text_input, Container, menu::{self, Action as MenuAction},
DndSource, Space, Widget, mouse_area, responsive, row, scrollable, text, text_input,
Container, DndSource, Space,
}, },
Element, Task, Element, Task,
}; };
@ -41,6 +44,29 @@ pub(crate) struct Library {
pub dragged_item: Option<(LibraryKind, i32)>, pub dragged_item: Option<(LibraryKind, i32)>,
editing_item: Option<(LibraryKind, i32)>, editing_item: Option<(LibraryKind, i32)>,
db: SqlitePool, db: SqlitePool,
menu_keys: std::collections::HashMap<menu::KeyBind, MenuMessage>,
context_menu: Option<i32>,
}
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
enum MenuMessage {
Delete((LibraryKind, i32)),
Open,
None,
}
impl MenuAction for MenuMessage {
type Message = Message;
fn message(&self) -> Self::Message {
match self {
MenuMessage::Delete((kind, index)) => {
Message::DeleteItem((*kind, *index))
}
MenuMessage::Open => todo!(),
MenuMessage::None => todo!(),
}
}
} }
pub(crate) enum Action { pub(crate) enum Action {
@ -53,7 +79,7 @@ pub(crate) enum Action {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) enum Message { pub(crate) enum Message {
AddItem, AddItem,
RemoveItem, DeleteItem((LibraryKind, i32)),
OpenItem(Option<(LibraryKind, i32)>), OpenItem(Option<(LibraryKind, i32)>),
HoverLibrary(Option<LibraryKind>), HoverLibrary(Option<LibraryKind>),
OpenLibrary(Option<LibraryKind>), OpenLibrary(Option<LibraryKind>),
@ -69,6 +95,7 @@ pub(crate) enum Message {
UpdatePresentation(Presentation), UpdatePresentation(Presentation),
PresentationChanged, PresentationChanged,
Error(String), Error(String),
OpenContext(i32),
None, None,
} }
@ -90,6 +117,8 @@ impl<'a> Library {
dragged_item: None, dragged_item: None,
editing_item: None, editing_item: None,
db, db,
menu_keys: HashMap::new(),
context_menu: None,
} }
} }
@ -101,7 +130,16 @@ impl<'a> Library {
match message { match message {
Message::AddItem => (), Message::AddItem => (),
Message::None => (), Message::None => (),
Message::RemoveItem => (), Message::DeleteItem((kind, index)) => {
match kind {
LibraryKind::Song => todo!(),
LibraryKind::Video => todo!(),
LibraryKind::Image => todo!(),
LibraryKind::Presentation => {
self.presentation_library.remove_item(index);
}
};
}
Message::OpenItem(item) => { Message::OpenItem(item) => {
debug!(?item); debug!(?item);
self.editing_item = item; self.editing_item = item;
@ -133,18 +171,18 @@ impl<'a> Library {
if kind != LibraryKind::Song { if kind != LibraryKind::Song {
error!("Not editing a song item"); error!("Not editing a song item");
return Action::None; return Action::None;
}; }
match self match self
.song_library .song_library
.update_item(song.clone(), index) .update_item(song.clone(), index)
{ {
Ok(_) => { Ok(()) => {
return Action::Task( return Action::Task(
Task::future(self.db.acquire()).and_then( Task::future(self.db.acquire()).and_then(
move |conn| update_in_db(&song, conn), move |conn| update_in_db(&song, conn),
), ),
) );
} }
Err(_) => todo!(), Err(_) => todo!(),
} }
@ -162,13 +200,13 @@ impl<'a> Library {
if kind != LibraryKind::Image { if kind != LibraryKind::Image {
error!("Not editing a image item"); error!("Not editing a image item");
return Action::None; return Action::None;
}; }
match self match self
.image_library .image_library
.update_item(image.clone(), index) .update_item(image.clone(), index)
{ {
Ok(_) => { Ok(()) => {
return Action::Task( return Action::Task(
Task::future(self.db.acquire()).and_then( Task::future(self.db.acquire()).and_then(
move |conn| { move |conn| {
@ -181,7 +219,7 @@ impl<'a> Library {
) )
}, },
), ),
) );
} }
Err(_) => todo!(), Err(_) => todo!(),
} }
@ -196,13 +234,13 @@ impl<'a> Library {
if kind != LibraryKind::Video { if kind != LibraryKind::Video {
error!("Not editing a video item"); error!("Not editing a video item");
return Action::None; return Action::None;
}; }
match self match self
.video_library .video_library
.update_item(video.clone(), index) .update_item(video.clone(), index)
{ {
Ok(_) => { Ok(()) => {
return Action::Task( return Action::Task(
Task::future(self.db.acquire()).and_then( Task::future(self.db.acquire()).and_then(
move |conn| { move |conn| {
@ -215,7 +253,7 @@ impl<'a> Library {
) )
}, },
), ),
) );
} }
Err(_) => todo!(), Err(_) => todo!(),
} }
@ -230,13 +268,13 @@ impl<'a> Library {
if kind != LibraryKind::Presentation { if kind != LibraryKind::Presentation {
error!("Not editing a presentation item"); error!("Not editing a presentation item");
return Action::None; return Action::None;
}; }
match self match self
.presentation_library .presentation_library
.update_item(presentation.clone(), index) .update_item(presentation.clone(), index)
{ {
Ok(_) => return Action::Task( Ok(()) => return Action::Task(
Task::future(self.db.acquire()).and_then( Task::future(self.db.acquire()).and_then(
move |conn| { move |conn| {
Task::perform( Task::perform(
@ -254,7 +292,10 @@ impl<'a> Library {
} }
Message::PresentationChanged => (), Message::PresentationChanged => (),
Message::Error(_) => (), Message::Error(_) => (),
}; Message::OpenContext(index) => {
self.context_menu = Some(index);
}
}
Action::None Action::None
} }
@ -315,7 +356,7 @@ impl<'a> Library {
textm!("Presentations").align_y(Vertical::Center), textm!("Presentations").align_y(Vertical::Center),
); );
} }
}; }
let item_count = model.items.len(); let item_count = model.items.len();
row = row.push(horizontal_space()); row = row.push(horizontal_space());
row = row row = row
@ -373,9 +414,9 @@ impl<'a> Library {
let service_item = item.to_service_item(); let service_item = item.to_service_item();
let visual_item = self let visual_item = self
.single_item(index, item, model) .single_item(index, item, model)
.map(|_| Message::None); .map(|()| Message::None);
DndSource::<Message, ServiceItem>::new( DndSource::<Message, ServiceItem>::new({
mouse_area(visual_item) let mouse_area = Element::from(mouse_area(visual_item)
.on_drag(Message::DragItem(service_item.clone())) .on_drag(Message::DragItem(service_item.clone()))
.on_enter(Message::HoverItem( .on_enter(Message::HoverItem(
Some(( Some((
@ -389,13 +430,31 @@ impl<'a> Library {
index as i32, index as i32,
))), ))),
) )
.on_right_press(Message::OpenContext(index as i32))
.on_exit(Message::HoverItem(None)) .on_exit(Message::HoverItem(None))
.on_press(Message::SelectItem( .on_press(Message::SelectItem(
Some(( Some((
model.kind, model.kind,
index as i32, index as i32,
)), )),
)), )));
if let Some(context_id) = self.context_menu {
if index == context_id as usize {
let context_menu = context_menu(
mouse_area,
self.context_menu.map_or_else(|| None, |id| {
Some(menu::items(&self.menu_keys,
vec![menu::Item::Button("Delete", None, MenuMessage::Delete((model.kind, index as i32)))]))
})
);
Element::from(context_menu)
} else {
Element::from(mouse_area)
}
} else {
Element::from(mouse_area)
}
}
) )
.action(DndAction::Copy) .action(DndAction::Copy)
.drag_icon({ .drag_icon({
@ -418,7 +477,7 @@ impl<'a> Library {
) )
}}) }})
.drag_content(move || { .drag_content(move || {
service_item.to_owned() service_item.clone()
}) })
.into() .into()
}, },
@ -481,11 +540,10 @@ impl<'a> Library {
.accent_text_color() .accent_text_color()
.into() .into()
} }
} else if let Some((library, selected)) = self.selected_item } else if let Some((library, selected)) =
{ self.selected_item
if model.kind == library
&& selected == index as i32
{ {
if model.kind == library && selected == index as i32 {
theme::active().cosmic().control_0().into() theme::active().cosmic().control_0().into()
} else { } else {
theme::active() theme::active()
@ -563,6 +621,7 @@ impl<'a> Library {
.into() .into()
} }
#[allow(clippy::unused_async)]
pub async fn search_items( pub async fn search_items(
&self, &self,
query: String, query: String,
@ -573,14 +632,18 @@ impl<'a> Library {
.items .items
.iter() .iter()
.filter(|song| song.title.to_lowercase().contains(&query)) .filter(|song| song.title.to_lowercase().contains(&query))
.map(|song| song.to_service_item()) .map(
super::super::core::content::Content::to_service_item,
)
.collect(); .collect();
let videos: Vec<ServiceItem> = self let videos: Vec<ServiceItem> = self
.video_library .video_library
.items .items
.iter() .iter()
.filter(|vid| vid.title.to_lowercase().contains(&query)) .filter(|vid| vid.title.to_lowercase().contains(&query))
.map(|vid| vid.to_service_item()) .map(
super::super::core::content::Content::to_service_item,
)
.collect(); .collect();
let images: Vec<ServiceItem> = self let images: Vec<ServiceItem> = self
.image_library .image_library
@ -589,14 +652,18 @@ impl<'a> Library {
.filter(|image| { .filter(|image| {
image.title.to_lowercase().contains(&query) image.title.to_lowercase().contains(&query)
}) })
.map(|image| image.to_service_item()) .map(
super::super::core::content::Content::to_service_item,
)
.collect(); .collect();
let presentations: Vec<ServiceItem> = self let presentations: Vec<ServiceItem> = self
.presentation_library .presentation_library
.items .items
.iter() .iter()
.filter(|pres| pres.title.to_lowercase().contains(&query)) .filter(|pres| pres.title.to_lowercase().contains(&query))
.map(|pres| pres.to_service_item()) .map(
super::super::core::content::Content::to_service_item,
)
.collect(); .collect();
items.extend(videos); items.extend(videos);
items.extend(images); items.extend(images);
@ -656,7 +723,7 @@ fn update_in_db(
Task::perform( Task::perform(
update_song_in_db(song.to_owned(), conn).map( update_song_in_db(song.to_owned(), conn).map(
move |r| match r { move |r| match r {
Ok(_) => { Ok(()) => {
warn!( warn!(
"Should have updated song: {:?}", "Should have updated song: {:?}",
song_title song_title
@ -667,7 +734,7 @@ fn update_in_db(
} }
}, },
), ),
|_| Message::SongChanged, |()| Message::SongChanged,
) )
} }

View file

@ -10,11 +10,13 @@ use cosmic::{
iced_widget::{ iced_widget::{
scrollable::{ scrollable::{
scroll_to, AbsoluteOffset, Direction, Scrollbar, scroll_to, AbsoluteOffset, Direction, Scrollbar,
}, stack, vertical_rule, },
stack, vertical_rule,
}, },
prelude::*, prelude::*,
widget::{ widget::{
container, image, mouse_area, responsive, scrollable, text, Container, Id, Row, Space, container, image, mouse_area, responsive, scrollable, text,
Container, Id, Row, Space,
}, },
Task, Task,
}; };
@ -152,7 +154,7 @@ impl Presenter {
absolute_slide_index: 0, absolute_slide_index: 0,
total_slides, total_slides,
video, video,
audio: items[0].slides[0].audio().clone(), audio: items[0].slides[0].audio(),
service: items, service: items,
video_position: 0.0, video_position: 0.0,
hovered_slide: None, hovered_slide: None,
@ -219,8 +221,8 @@ impl Presenter {
let offset = AbsoluteOffset { let offset = AbsoluteOffset {
x: { x: {
if self.current_slide_index > 2 { if self.current_slide_index > 2 {
self.current_slide_index as f32 * 187.5 (self.current_slide_index as f32)
- 187.5 .mul_add(187.5, -187.5)
} else { } else {
0.0 0.0
} }
@ -254,7 +256,7 @@ impl Presenter {
?current_audio, ?current_audio,
"audio needs to change" "audio needs to change"
); );
self.audio = Some(new_audio.clone()); self.audio = Some(new_audio);
tasks.push(self.start_audio()); tasks.push(self.start_audio());
} }
Some(current_audio) => { Some(current_audio) => {
@ -269,10 +271,10 @@ impl Presenter {
?new_audio, ?new_audio,
"could not find audio, need to change" "could not find audio, need to change"
); );
self.audio = Some(new_audio.clone()); self.audio = Some(new_audio);
tasks.push(self.start_audio()); tasks.push(self.start_audio());
} }
}; }
} else { } else {
self.audio = None; self.audio = None;
self.update(Message::EndAudio); self.update(Message::EndAudio);
@ -324,7 +326,7 @@ impl Presenter {
std::time::Duration::from_secs_f32(position), std::time::Duration::from_secs_f32(position),
); );
match video.seek(position, false) { match video.seek(position, false) {
Ok(_) => debug!( Ok(()) => debug!(
"Video position changed: {:?}", "Video position changed: {:?}",
position position
), ),
@ -353,7 +355,7 @@ impl Presenter {
install_ctx install_ctx
.set_desktop_id(&format!("{}.desktop", "org.chriscochrun.lumina")); .set_desktop_id(&format!("{}.desktop", "org.chriscochrun.lumina"));
let install_detail = missing_plugin.installer_detail(); let install_detail = missing_plugin.installer_detail();
println!("installing plugins: {}", install_detail); println!("installing plugins: {install_detail}");
let status = gst_pbutils::missing_plugins::install_plugins_sync( let status = gst_pbutils::missing_plugins::install_plugins_sync(
&[&install_detail], &[&install_detail],
Some(&install_ctx), Some(&install_ctx),
@ -389,7 +391,7 @@ impl Presenter {
Message::Error(error) => { Message::Error(error) => {
error!(error); error!(error);
} }
}; }
Action::None Action::None
} }
@ -639,7 +641,7 @@ impl Presenter {
v.set_looping( v.set_looping(
self.current_slide.video_loop(), self.current_slide.video_loop(),
); );
self.video = Some(v) self.video = Some(v);
} }
Err(e) => { Err(e) => {
error!( error!(
@ -662,7 +664,7 @@ impl Presenter {
let audio = audio.clone(); let audio = audio.clone();
Task::perform( Task::perform(
start_audio(Arc::clone(&self.sink.1), audio), start_audio(Arc::clone(&self.sink.1), audio),
|_| Message::None, |()| Message::None,
) )
} else { } else {
debug!(?self.audio, "Apparently this doesn't exist"); debug!(?self.audio, "Apparently this doesn't exist");

View file

@ -54,10 +54,10 @@ struct EditorProgram {
} }
impl SlideEditor { impl SlideEditor {
pub fn view<'a>( pub fn view(
&'a self, &self,
font: Font, font: Font,
) -> cosmic::Element<'a, SlideWidget> { ) -> cosmic::Element<'_, SlideWidget> {
container( container(
widget::canvas(&self.program) widget::canvas(&self.program)
.height(Length::Fill) .height(Length::Fill)
@ -122,10 +122,10 @@ impl<'a> Program<SlideWidget, cosmic::Theme, cosmic::Renderer>
match event { match event {
canvas::Event::Mouse(event) => match event { canvas::Event::Mouse(event) => match event {
cosmic::iced::mouse::Event::CursorEntered => { cosmic::iced::mouse::Event::CursorEntered => {
debug!("cursor entered") debug!("cursor entered");
} }
cosmic::iced::mouse::Event::CursorLeft => { cosmic::iced::mouse::Event::CursorLeft => {
debug!("cursor left") debug!("cursor left");
} }
cosmic::iced::mouse::Event::CursorMoved { cosmic::iced::mouse::Event::CursorMoved {
position, position,
@ -140,7 +140,7 @@ impl<'a> Program<SlideWidget, cosmic::Theme, cosmic::Renderer>
} }
cosmic::iced::mouse::Event::ButtonPressed(button) => { cosmic::iced::mouse::Event::ButtonPressed(button) => {
// self.mouse_button_pressed = Some(button); // self.mouse_button_pressed = Some(button);
debug!(?button, "mouse button pressed") debug!(?button, "mouse button pressed");
} }
cosmic::iced::mouse::Event::ButtonReleased( cosmic::iced::mouse::Event::ButtonReleased(
button, button,

View file

@ -1,30 +1,29 @@
use std::{io, path::PathBuf, sync::Arc}; use std::{io, path::PathBuf, sync::Arc};
use cosmic::{ use cosmic::{
Element, Task,
dialog::file_chooser::open::Dialog, dialog::file_chooser::open::Dialog,
iced::{ iced::{
font::{Family, Stretch, Style, Weight},
Font, Length, Font, Length,
font::{Family, Stretch, Style, Weight},
}, },
iced_wgpu::graphics::text::cosmic_text::fontdb, iced_wgpu::graphics::text::cosmic_text::fontdb,
iced_widget::row, iced_widget::row,
theme, theme,
widget::{ widget::{
button, column, combo_box, container, horizontal_space, icon, text, text_editor, text_input, button, column, combo_box, container, horizontal_space, icon,
text, text_editor, text_input,
}, },
Element, Task,
}; };
use dirs::font_dir; use dirs::font_dir;
use iced_video_player::Video; use iced_video_player::Video;
use tracing::{debug, error}; use tracing::{debug, error};
use crate::{ use crate::{
core::{service_items::ServiceTrait, songs::Song}, Background, BackgroundKind, core::songs::Song,
ui::slide_editor::SlideEditor, ui::slide_editor::SlideEditor,
Background, BackgroundKind,
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct SongEditor { pub struct SongEditor {
pub song: Option<Song>, pub song: Option<Song>,
@ -140,11 +139,11 @@ impl SongEditor {
self.song = Some(song.clone()); self.song = Some(song.clone());
self.title = song.title; self.title = song.title;
if let Some(font) = song.font { if let Some(font) = song.font {
self.font = font self.font = font;
}; }
if let Some(font_size) = song.font_size { if let Some(font_size) = song.font_size {
self.font_size = font_size as usize self.font_size = font_size as usize;
}; }
if let Some(verse_order) = song.verse_order { if let Some(verse_order) = song.verse_order {
self.verse_order = verse_order self.verse_order = verse_order
.into_iter() .into_iter()
@ -155,18 +154,18 @@ impl SongEditor {
.collect(); .collect();
} }
if let Some(author) = song.author { if let Some(author) = song.author {
self.author = author self.author = author;
}; }
if let Some(audio) = song.audio { if let Some(audio) = song.audio {
self.audio = audio self.audio = audio;
}; }
if let Some(ccli) = song.ccli { if let Some(ccli) = song.ccli {
self.ccli = ccli self.ccli = ccli;
}; }
if let Some(lyrics) = song.lyrics { if let Some(lyrics) = song.lyrics {
self.lyrics = self.lyrics =
text_editor::Content::with_text(&lyrics) text_editor::Content::with_text(&lyrics);
}; }
self.background_video(&song.background); self.background_video(&song.background);
self.background = song.background; self.background = song.background;
} }
@ -212,8 +211,8 @@ impl SongEditor {
self.verse_order = verse_order.clone(); self.verse_order = verse_order.clone();
if let Some(mut song) = self.song.clone() { if let Some(mut song) = self.song.clone() {
let verse_order = verse_order let verse_order = verse_order
.split(" ") .split(' ')
.map(|s| s.to_owned()) .map(std::borrow::ToOwned::to_owned)
.collect(); .collect();
song.verse_order = Some(verse_order); song.verse_order = Some(verse_order);
return self.update_song(song); return self.update_song(song);
@ -244,8 +243,7 @@ impl SongEditor {
Message::ChangeBackground(Ok(path)) => { Message::ChangeBackground(Ok(path)) => {
debug!(?path); debug!(?path);
if let Some(mut song) = self.song.clone() { if let Some(mut song) = self.song.clone() {
let background = let background = Background::try_from(path).ok();
Background::try_from(path.clone()).ok();
self.background_video(&background); self.background_video(&background);
song.background = background; song.background = background;
return self.update_song(song); return self.update_song(song);
@ -258,7 +256,7 @@ impl SongEditor {
return Action::Task(Task::perform( return Action::Task(Task::perform(
pick_background(), pick_background(),
Message::ChangeBackground, Message::ChangeBackground,
)) ));
} }
_ => (), _ => (),
} }
@ -416,7 +414,7 @@ order",
.into() .into()
} }
pub fn editing(&self) -> bool { pub const fn editing(&self) -> bool {
self.editing self.editing
} }

View file

@ -121,15 +121,18 @@ impl From<&str> for Font {
} }
impl Font { impl Font {
#[must_use]
pub fn get_name(&self) -> String { pub fn get_name(&self) -> String {
self.name.clone() self.name.clone()
} }
pub fn get_weight(&self) -> Weight { #[must_use]
pub const fn get_weight(&self) -> Weight {
self.weight self.weight
} }
pub fn get_style(&self) -> Style { #[must_use]
pub const fn get_style(&self) -> Style {
self.style self.style
} }
@ -148,7 +151,8 @@ impl Font {
self self
} }
pub fn size(mut self, size: u8) -> Self { #[must_use]
pub const fn size(mut self, size: u8) -> Self {
self.size = size; self.size = size;
self self
} }
@ -161,12 +165,12 @@ impl Hash for Color {
} }
impl Color { impl Color {
pub fn from_hex_str(color: impl AsRef<str>) -> Color { pub fn from_hex_str(color: impl AsRef<str>) -> Self {
match Rgb::from_hex_str(color.as_ref()) { match Rgb::from_hex_str(color.as_ref()) {
Ok(rgb) => Color(rgb), Ok(rgb) => Self(rgb),
Err(e) => { Err(e) => {
error!("error in making color from hex_str: {:?}", e); error!("error in making color from hex_str: {:?}", e);
Color::default() Self::default()
} }
} }
} }
@ -236,7 +240,10 @@ impl TextSvg {
self self
} }
pub fn alignment(mut self, alignment: TextAlignment) -> Self { pub const fn alignment(
mut self,
alignment: TextAlignment,
) -> Self {
self.alignment = alignment; self.alignment = alignment;
self self
} }
@ -255,7 +262,7 @@ impl TextSvg {
shadow.spread, shadow.spread,
shadow.color) shadow.color)
} else { } else {
"".into() String::new()
}; };
let stroke = if let Some(stroke) = &self.stroke { let stroke = if let Some(stroke) = &self.stroke {
format!( format!(
@ -263,17 +270,17 @@ impl TextSvg {
stroke.color, stroke.size stroke.color, stroke.size
) )
} else { } else {
"".into() String::new()
}; };
let size = Size::new(1920.0, 1080.0); let size = Size::new(1920.0, 1080.0);
let font_size = self.font.size as f32; let font_size = f32::from(self.font.size);
let total_lines = self.text.lines().count(); let total_lines = self.text.lines().count();
let half_lines = (total_lines / 2) as f32; let half_lines = (total_lines / 2) as f32;
let middle_position = size.height / 2.0; let middle_position = size.height / 2.0;
let line_spacing = 10.0; let line_spacing = 10.0;
let text_and_line_spacing = font_size + line_spacing; let text_and_line_spacing = font_size + line_spacing;
let starting_y_position = let starting_y_position = half_lines
middle_position - (half_lines * text_and_line_spacing); .mul_add(-text_and_line_spacing, middle_position);
let text_pieces: Vec<String> = self let text_pieces: Vec<String> = self
.text .text
@ -282,8 +289,10 @@ impl TextSvg {
.map(|(index, text)| { .map(|(index, text)| {
format!( format!(
"<tspan x=\"50%\" y=\"{}\">{}</tspan>", "<tspan x=\"50%\" y=\"{}\">{}</tspan>",
(index as f32).mul_add(
text_and_line_spacing,
starting_y_position starting_y_position
+ (index as f32 * text_and_line_spacing), ),
text text
) )
}) })
@ -350,7 +359,7 @@ impl TextSvg {
self.text self.text
.lines() .lines()
.enumerate() .enumerate()
.map(|(i, t)| format!("<tspan x=\"50%\">{}</tspan>", t)) .map(|(i, t)| format!("<tspan x=\"50%\">{t}</tspan>"))
.collect() .collect()
} }
} }
@ -391,7 +400,7 @@ pub fn text_svg_generator(
.shadow(shadow(2, 2, 5, "#000000")) .shadow(shadow(2, 2, 5, "#000000"))
.stroke(stroke(3, "#000")) .stroke(stroke(3, "#000"))
.font( .font(
Font::from(slide.font().clone()) Font::from(slide.font())
.size(slide.font_size().try_into().unwrap()), .size(slide.font_size().try_into().unwrap()),
) )
.fontdb(Arc::clone(&fontdb)) .fontdb(Arc::clone(&fontdb))