From 61bba082aa1f0b5fd2488b2496e808f91c42aa91 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Sun, 1 Feb 2026 08:43:25 -0600 Subject: [PATCH] improving some performance --- src/core/songs.rs | 8 ++- src/ui/presenter.rs | 132 +++++++++++++++++++----------------------- src/ui/song_editor.rs | 62 ++++++++++++++++++++ 3 files changed, 127 insertions(+), 75 deletions(-) diff --git a/src/core/songs.rs b/src/core/songs.rs index 6b52983..33fde6c 100644 --- a/src/core/songs.rs +++ b/src/core/songs.rs @@ -2,7 +2,7 @@ use std::{ borrow::Cow, collections::HashMap, option::Option, path::PathBuf, }; -use cosmic::iced::clipboard::mime::AsMimeTypes; +use cosmic::iced::{Color, clipboard::mime::AsMimeTypes}; use crisp::types::{Keyword, Symbol, Value}; use miette::{IntoDiagnostic, Result, miette}; use serde::{Deserialize, Serialize}; @@ -38,6 +38,10 @@ pub struct Song { pub font: Option, pub font_size: Option, pub stroke_size: Option, + pub stroke_color: Option, + pub shadow_size: Option, + pub shadow_offset: Option<(i32, i32)>, + pub shadow_color: Option, pub verses: Option>, pub verse_map: Option>, } @@ -342,6 +346,7 @@ impl FromRow<'_, SqliteRow> for Song { stroke_size: None, verses, verse_map, + ..Default::default() }) } } @@ -1133,6 +1138,7 @@ You saved my soul" stroke_size: None, verses: Some(vec![]), verse_map: None, + ..Default::default() } } diff --git a/src/ui/presenter.rs b/src/ui/presenter.rs index 03c2385..ac6cc34 100644 --- a/src/ui/presenter.rs +++ b/src/ui/presenter.rs @@ -205,7 +205,7 @@ impl Presenter { gst::init().into_diagnostic()?; let pipeline = format!( - r#"playbin uri="{}" video-sink="videoscale ! videoconvert ! appsink name=lumina_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1""#, + r#"playbin uri="{}" video-sink="videoscale ! videoconvert ! videoflip method=automatic ! appsink name=lumina_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1""#, url.as_str() ); @@ -1050,100 +1050,84 @@ pub(crate) fn slide_view<'a>( delegate: bool, hide_mouse: bool, ) -> Element<'a, Message> { - let res = responsive(move |size| { + responsive(move |size| { let width = size.height * 16.0 / 9.0; - - let text: Element = - if let Some(text) = &slide.text_svg { - if let Some(handle) = &text.handle { - image(handle) - .content_fit(ContentFit::ScaleDown) - .width(Length::Shrink) - .height(Length::Shrink) - .into() - } else { - Space::with_width(0).into() - } - } else { - Space::with_width(0).into() - }; let black = Container::new(Space::new(0, 0)) .style(|_| { container::background(Background::Color(Color::BLACK)) }) .clip(true) .width(width) - .height(size.height); - let background = match slide.background().kind { + .height(Length::Fill); + let mut stack = stack(vec![black.into()]); + + match slide.background().kind { BackgroundKind::Image => { let path = slide.background().path.clone(); - Container::new( - image(path) - .content_fit(ContentFit::Cover) - .width(width) - .height(size.height), - ) - .center(Length::Shrink) - .clip(true) - } - BackgroundKind::Video => { - if delegate { - Container::new(Space::new(0, 0)) - .style(|_| { - container::background(Background::Color( - Color::BLACK, - )) - }) - .center(Length::Fill) - .clip(true) - .width(width) - .height(size.height) - } else if let Some(video) = &video { + stack = stack.push( Container::new( - VideoPlayer::new(video) - .mouse_hidden(hide_mouse) + image(path) + .content_fit(ContentFit::Contain) .width(width) - .height(size.height) - .on_end_of_stream(Message::EndVideo) - .on_new_frame(Message::VideoFrame) - .on_missing_plugin(Message::MissingPlugin) - .on_warning(|w| { - Message::Error(w.to_string()) - }) - .on_error(|e| { - Message::Error(e.to_string()) - }) - .content_fit(ContentFit::Cover), + .height(Length::Fill), ) .center(Length::Fill) - .clip(true) - // Container::new(Space::new(0, 0)) - } else { - Container::new(Space::new(0, 0)) + .clip(true), + ); + } + BackgroundKind::Video => { + if let Some(video) = &video + && !delegate + { + stack = stack.push( + Container::new( + VideoPlayer::new(video) + .mouse_hidden(hide_mouse) + .width(width) + .height(Length::Fill) + .on_end_of_stream(Message::EndVideo) + .on_new_frame(Message::VideoFrame) + .on_missing_plugin( + Message::MissingPlugin, + ) + .on_warning(|w| { + Message::Error(w.to_string()) + }) + .on_error(|e| { + Message::Error(e.to_string()) + }) + .content_fit(ContentFit::Contain), + ) + .center(Length::Fill) + .clip(true), + ); } } BackgroundKind::Pdf => { if let Some(pdf) = slide.pdf_page() { - Container::new( - image(pdf).content_fit(ContentFit::Contain), - ) - .center(Length::Fill) - .clip(true) - } else { - Container::new(Space::new(0.0, 0.0)) + stack = stack.push( + Container::new( + image(pdf) + .content_fit(ContentFit::Contain), + ) .center(Length::Fill) - .clip(true) + .clip(true), + ); } } BackgroundKind::Html => todo!(), }; - let stack = stack!( - black, - background.center_x(Length::Fill), - container(text).center(Length::Fill) - ); + if let Some(text) = &slide.text_svg { + if let Some(handle) = &text.handle { + stack = stack.push( + image(handle) + .content_fit(ContentFit::ScaleDown) + .width(Length::Shrink) + .height(Length::Shrink), + ); + } + }; Container::new(stack).center(Length::Fill).into() - }); - - res.into() + }) + .into() } diff --git a/src/ui/song_editor.rs b/src/ui/song_editor.rs index d537fe3..dc80732 100644 --- a/src/ui/song_editor.rs +++ b/src/ui/song_editor.rs @@ -102,6 +102,7 @@ pub enum Message { ChangeLyrics(text_editor::Action), ChangeBackground(Result), UpdateSlides(Vec), + UpdateSlide((usize, Slide)), PickBackground, Edit(bool), None, @@ -240,7 +241,28 @@ impl SongEditor { } self.background_video(&song.background); self.background = song.background.clone(); + self.song_slides = None; let font_db = Arc::clone(&self.font_db); + + // let slides = song_slides.ok(); + // let mut task = Task::none(); + // if let Some(slides) = slides { + // for (index, mut slide) in + // slides.into_iter().enumerate() + // { + // let font_db = Arc::clone(&font_db); + // task = task.chain(Task::perform( + // async move { + // text_svg::text_svg_generator( + // &mut slide, font_db, + // ); + // (index, slide) + // }, + // Message::UpdateSlide, + // )); + // } + // } + let task = Task::perform( async move { song_slides @@ -260,6 +282,7 @@ impl SongEditor { }, Message::UpdateSlides, ); + self.verses = song.verse_map.map(|map| { map.into_iter() .sorted() @@ -368,6 +391,18 @@ impl SongEditor { Message::UpdateSlides(slides) => { self.song_slides = Some(slides); } + Message::UpdateSlide((index, slide)) => { + if let Some(slides) = self.song_slides.as_mut() { + if let Some(_old) = slides.get(index) { + let _ = slides.remove(index); + slides.insert(index, slide); + } else { + slides.push(slide); + } + } else { + self.song_slides = Some(vec![slide]); + } + } Message::UpdateSong(song) => { self.song = Some(song.clone()); return Action::UpdateSong(song); @@ -1009,6 +1044,32 @@ impl SongEditor { let font_db = Arc::clone(&self.font_db); let update_task = Task::done(Message::UpdateSong(song.clone())); + + // need to test to see which of these methods yields faster + // text_svg slide creation. There is a small thought in me that + // believes it's better for the user to see the slides being added + // one by one, rather than all at once, but that isn't how + // the task appears to happen. + + // let slides = song.to_slides().ok(); + // let mut task = vec![]; + // if let Some(slides) = slides { + // for (index, mut slide) in slides.into_iter().enumerate() { + // let font_db = Arc::clone(&font_db); + // task.push(Task::perform( + // async move { + // text_svg::text_svg_generator( + // &mut slide, font_db, + // ); + // (index, slide) + // }, + // Message::UpdateSlide, + // )); + // } + // } + + // I think this implementation is faster + let task = Task::perform( async move { song.to_slides() @@ -1028,6 +1089,7 @@ impl SongEditor { }, Message::UpdateSlides, ); + Action::Task(task.chain(update_task)) }