From 213e47bf6dee7c09bbdf7dddd8e2a49de17854d0 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Wed, 27 Aug 2025 09:45:00 -0500 Subject: [PATCH 01/11] rearrange libcosmic dep for clarity --- Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4f5c510..fd26207 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ description = "A cli presentation system" [dependencies] clap = { version = "4.5.20", features = ["debug", "derive"] } -libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false, features = ["debug", "winit", "desktop", "winit_wgpu", "winit_tokio", "tokio", "rfd", "dbus-config", "a11y", "wgpu", "multi-window"] } lexpr = "0.2.7" miette = { version = "7.2.0", features = ["fancy"] } pretty_assertions = "1.4.1" @@ -39,6 +38,11 @@ rayon = "1.11.0" # mupdf = "0.5.0" # rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false } +[dependencies.libcosmic] +git = "https://github.com/pop-os/libcosmic" +default-features = false +features = ["debug", "winit", "desktop", "winit_wgpu", "winit_tokio", "tokio", "rfd", "dbus-config", "a11y", "wgpu", "multi-window"] + [dependencies.iced_video_player] git = "https://github.com/jackpot51/iced_video_player.git" branch = "cosmic" From aa5e7420f172c9e4dcc6de05f18df32e3d5eb109 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Wed, 27 Aug 2025 09:45:16 -0500 Subject: [PATCH 02/11] moving service_list out of nav_bar --- src/main.rs | 381 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 240 insertions(+), 141 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9d1cd41..dfa0858 100644 --- a/src/main.rs +++ b/src/main.rs @@ -235,155 +235,155 @@ impl cosmic::Application for App { (app, batch) } - /// Allows COSMIC to integrate with your application's [`nav_bar::Model`]. - fn nav_model(&self) -> Option<&nav_bar::Model> { - Some(&self.nav_model) - } + // /// Allows COSMIC to integrate with your application's [`nav_bar::Model`]. + // fn nav_model(&self) -> Option<&nav_bar::Model> { + // Some(&self.nav_model) + // } - fn nav_bar(&self) -> Option>> { - if !self.core().nav_bar_active() { - return None; - } + // fn nav_bar(&self) -> Option>> { + // if !self.core().nav_bar_active() { + // return None; + // } - // let nav_model = self.nav_model()?; + // // let nav_model = self.nav_model()?; - // let mut nav = cosmic::widget::nav_bar(nav_model, |id| { - // cosmic::Action::Cosmic(cosmic::app::Action::NavBar(id)) - // }) - // .on_dnd_drop::(|entity, data, action| { - // debug!(?entity); - // debug!(?data); - // debug!(?action); - // cosmic::Action::App(Message::DndDrop) - // }) - // .on_dnd_enter(|entity, data| { - // debug!("entered"); - // cosmic::Action::App(Message::DndEnter(entity, data)) - // }) - // .on_dnd_leave(|entity| { - // debug!("left"); - // cosmic::Action::App(Message::DndLeave(entity)) - // }) - // .drag_id(DragId::new()) - // .on_context(|id| { - // cosmic::Action::Cosmic( - // cosmic::app::Action::NavBarContext(id), - // ) - // }) - // .context_menu(None) - // .into_container() - // // XXX both must be shrink to avoid flex layout from ignoring it - // .width(Length::Shrink) - // .height(Length::Shrink); + // // let mut nav = cosmic::widget::nav_bar(nav_model, |id| { + // // cosmic::Action::Cosmic(cosmic::app::Action::NavBar(id)) + // // }) + // // .on_dnd_drop::(|entity, data, action| { + // // debug!(?entity); + // // debug!(?data); + // // debug!(?action); + // // cosmic::Action::App(Message::DndDrop) + // // }) + // // .on_dnd_enter(|entity, data| { + // // debug!("entered"); + // // cosmic::Action::App(Message::DndEnter(entity, data)) + // // }) + // // .on_dnd_leave(|entity| { + // // debug!("left"); + // // cosmic::Action::App(Message::DndLeave(entity)) + // // }) + // // .drag_id(DragId::new()) + // // .on_context(|id| { + // // cosmic::Action::Cosmic( + // // cosmic::app::Action::NavBarContext(id), + // // ) + // // }) + // // .context_menu(None) + // // .into_container() + // // // XXX both must be shrink to avoid flex layout from ignoring it + // // .width(Length::Shrink) + // // .height(Length::Shrink); - let list = self - .service - .iter() - .enumerate() - .map(|(index, item)| { - let button = button::standard(item.title.clone()) - .leading_icon({ - match item.kind { - core::kinds::ServiceItemKind::Song(_) => { - icon::from_name("folder-music-symbolic") - }, - core::kinds::ServiceItemKind::Video(_) => { - icon::from_name("folder-videos-symbolic") - }, - core::kinds::ServiceItemKind::Image(_) => { - icon::from_name("folder-pictures-symbolic") - }, - core::kinds::ServiceItemKind::Presentation(_) => { - icon::from_name("x-office-presentation-symbolic") - }, - core::kinds::ServiceItemKind::Content(_) => { - icon::from_name("x-office-presentation-symbolic") - }, - } - }) - .class(cosmic::theme::style::Button::HeaderBar) - .padding(5) - .width(Length::Fill) - .on_press(cosmic::Action::App(Message::ChangeServiceItem(index))); - let tooltip = tooltip(button, - text::body(item.kind.to_string()), - TPosition::Right); - dnd_destination(tooltip, vec!["application/service-item".into()]) - .data_received_for::( move |item| { - if let Some(item) = item { - cosmic::Action::App(Message::AddServiceItem(index, item)) - } else { - cosmic::Action::None - } - }).on_drop(move |x, y| { - debug!(x, y); - cosmic::Action::App(Message::AddServiceItemDrop(index)) - }).on_finish(move |mime, data, action, x, y| { - debug!(mime, ?data, ?action, x, y); - let Ok(item) = ServiceItem::try_from((data, mime)) else { - return cosmic::Action::None; - }; - debug!(?item); - cosmic::Action::App(Message::AddServiceItem(index, item)) - }) - .into() - }); + // let list = self + // .service + // .iter() + // .enumerate() + // .map(|(index, item)| { + // let button = button::standard(item.title.clone()) + // .leading_icon({ + // match item.kind { + // core::kinds::ServiceItemKind::Song(_) => { + // icon::from_name("folder-music-symbolic") + // }, + // core::kinds::ServiceItemKind::Video(_) => { + // icon::from_name("folder-videos-symbolic") + // }, + // core::kinds::ServiceItemKind::Image(_) => { + // icon::from_name("folder-pictures-symbolic") + // }, + // core::kinds::ServiceItemKind::Presentation(_) => { + // icon::from_name("x-office-presentation-symbolic") + // }, + // core::kinds::ServiceItemKind::Content(_) => { + // icon::from_name("x-office-presentation-symbolic") + // }, + // } + // }) + // .class(cosmic::theme::style::Button::HeaderBar) + // .padding(5) + // .width(Length::Fill) + // .on_press(cosmic::Action::App(Message::ChangeServiceItem(index))); + // let tooltip = tooltip(button, + // text::body(item.kind.to_string()), + // TPosition::Right); + // dnd_destination(tooltip, vec!["application/service-item".into()]) + // .data_received_for::( move |item| { + // if let Some(item) = item { + // cosmic::Action::App(Message::AddServiceItem(index, item)) + // } else { + // cosmic::Action::None + // } + // }).on_drop(move |x, y| { + // debug!(x, y); + // cosmic::Action::App(Message::AddServiceItemDrop(index)) + // }).on_finish(move |mime, data, action, x, y| { + // debug!(mime, ?data, ?action, x, y); + // let Ok(item) = ServiceItem::try_from((data, mime)) else { + // return cosmic::Action::None; + // }; + // debug!(?item); + // cosmic::Action::App(Message::AddServiceItem(index, item)) + // }) + // .into() + // }); - let end_index = self.service.len(); - let column = column![ - text::heading("Service List").center().width(280), - column(list).spacing(10), - dnd_destination( - vertical_space(), - vec!["application/service-item".into()] - ) - .data_received_for::(|item| { - if let Some(item) = item { - cosmic::Action::App(Message::AppendServiceItem( - item, - )) - } else { - cosmic::Action::None - } - }) - .on_finish( - move |mime, data, action, x, y| { - debug!(mime, ?data, ?action, x, y); - let Ok(item) = - ServiceItem::try_from((data, mime)) - else { - return cosmic::Action::None; - }; - debug!(?item); - cosmic::Action::App(Message::AddServiceItem( - end_index, item, - )) - } - ) - ] - .padding(10) - .spacing(10); - let padding = Padding::new(0.0).top(20); - let mut container = Container::new(column) - // .height(Length::Fill) - .style(nav_bar_style) - .padding(padding); + // let end_index = self.service.len(); + // let column = column![ + // text::heading("Service List").center().width(280), + // column(list).spacing(10), + // dnd_destination( + // vertical_space(), + // vec!["application/service-item".into()] + // ) + // .data_received_for::(|item| { + // if let Some(item) = item { + // cosmic::Action::App(Message::AppendServiceItem( + // item, + // )) + // } else { + // cosmic::Action::None + // } + // }) + // .on_finish( + // move |mime, data, action, x, y| { + // debug!(mime, ?data, ?action, x, y); + // let Ok(item) = + // ServiceItem::try_from((data, mime)) + // else { + // return cosmic::Action::None; + // }; + // debug!(?item); + // cosmic::Action::App(Message::AddServiceItem( + // end_index, item, + // )) + // } + // ) + // ] + // .padding(10) + // .spacing(10); + // let padding = Padding::new(0.0).top(20); + // let mut container = Container::new(column) + // // .height(Length::Fill) + // .style(nav_bar_style) + // .padding(padding); - if !self.core().is_condensed() { - container = container.max_width(280); - } - Some(container.into()) - } + // if !self.core().is_condensed() { + // container = container.max_width(280); + // } + // Some(container.into()) + // } /// Called when a navigation item is selected. - fn on_nav_select( - &mut self, - id: nav_bar::Id, - ) -> Task { - self.nav_model.activate(id); - // debug!(?id); - self.update_title() - } + // fn on_nav_select( + // &mut self, + // id: nav_bar::Id, + // ) -> Task { + // self.nav_model.activate(id); + // // debug!(?id); + // self.update_title() + // } fn header_start(&self) -> Vec> { vec![] @@ -998,6 +998,9 @@ impl cosmic::Application for App { )) }; + let service_list = + Container::new(self.service_list()).padding(5); + let slide_preview = column![ Space::with_height(Length::Fill), Container::new( @@ -1048,6 +1051,7 @@ impl cosmic::Application for App { self.song_editor.view().map(Message::SongEditor); let row = row![ + service_list, Container::new( button::icon(icon_left) .icon_size(128) @@ -1212,6 +1216,101 @@ where _ => Task::none(), } } + + fn service_list(&self) -> Element { + let list = self + .service + .iter() + .enumerate() + .map(|(index, item)| { + let button = button::standard(item.title.clone()) + .leading_icon({ + match item.kind { + core::kinds::ServiceItemKind::Song(_) => { + icon::from_name("folder-music-symbolic") + }, + core::kinds::ServiceItemKind::Video(_) => { + icon::from_name("folder-videos-symbolic") + }, + core::kinds::ServiceItemKind::Image(_) => { + icon::from_name("folder-pictures-symbolic") + }, + core::kinds::ServiceItemKind::Presentation(_) => { + icon::from_name("x-office-presentation-symbolic") + }, + core::kinds::ServiceItemKind::Content(_) => { + icon::from_name("x-office-presentation-symbolic") + }, + } + }) + .class(cosmic::theme::style::Button::HeaderBar) + .padding(5) + .width(Length::Fill) + .on_press(Message::ChangeServiceItem(index)); + let tooltip = tooltip(button, + text::body(item.kind.to_string()), + TPosition::Right); + dnd_destination(tooltip, vec!["application/service-item".into()]) + .data_received_for::( move |item| { + if let Some(item) = item { + Message::AddServiceItem(index, item) + } else { + Message::None + } + }).on_drop(move |x, y| { + debug!(x, y); + Message::AddServiceItemDrop(index) + }).on_finish(move |mime, data, action, x, y| { + debug!(mime, ?data, ?action, x, y); + let Ok(item) = ServiceItem::try_from((data, mime)) else { + return Message::None; + }; + debug!(?item); + Message::AddServiceItem(index, item) + }) + .into() + }); + + let end_index = self.service.len(); + let column = column![ + text::heading("Service List").center().width(280), + iced::widget::horizontal_rule(1), + column(list).spacing(10), + dnd_destination( + vertical_space(), + vec!["application/service-item".into()] + ) + .data_received_for::(|item| { + if let Some(item) = item { + Message::AppendServiceItem(item) + } else { + Message::None + } + }) + .on_finish( + move |mime, data, action, x, y| { + debug!(mime, ?data, ?action, x, y); + let Ok(item) = + ServiceItem::try_from((data, mime)) + else { + return Message::None; + }; + debug!(?item); + Message::AddServiceItem(end_index, item) + } + ) + ] + .padding(10) + .spacing(10); + let mut container = Container::new(column) + // .height(Length::Fill) + .style(nav_bar_style); + + if !self.core().is_condensed() { + container = container.max_width(280); + } + container.into() + } } #[cfg(test)] From 5f3d867ad754f55574ebcfa7ec34b927659acf49 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Wed, 27 Aug 2025 13:06:20 -0500 Subject: [PATCH 03/11] move the library to the left --- src/main.rs | 37 +++++++++++++++++++++---------------- src/ui/library.rs | 10 ++++++++-- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index dfa0858..1a9eb64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -998,9 +998,6 @@ impl cosmic::Application for App { )) }; - let service_list = - Container::new(self.service_list()).padding(5); - let slide_preview = column![ Space::with_height(Length::Fill), Container::new( @@ -1035,14 +1032,23 @@ impl cosmic::Application for App { ] .spacing(3); + let service_list = Container::new(self.service_list()) + .padding(5) + .width(Length::FillPortion(2)); + let library = if self.library_open { - Container::new(if let Some(library) = &self.library { - library.view().map(Message::Library) - } else { - Space::new(0, 0).into() - }) - .style(nav_bar_style) - .center(Length::FillPortion(2)) + Container::new( + Container::new( + if let Some(library) = &self.library { + library.view().map(Message::Library) + } else { + Space::new(0, 0).into() + }, + ) + .style(nav_bar_style), + ) + .padding(5) + .width(Length::FillPortion(2)) } else { Container::new(horizontal_space().width(0)) }; @@ -1051,6 +1057,7 @@ impl cosmic::Application for App { self.song_editor.view().map(Message::SongEditor); let row = row![ + library, service_list, Container::new( button::icon(icon_left) @@ -1079,7 +1086,6 @@ impl cosmic::Application for App { ) .center_y(Length::Fill) .align_left(Length::FillPortion(1)), - library ] .width(Length::Fill) .height(Length::Fill) @@ -1273,7 +1279,9 @@ where let end_index = self.service.len(); let column = column![ - text::heading("Service List").center().width(280), + text::heading("Service List") + .center() + .width(Length::Fill), iced::widget::horizontal_rule(1), column(list).spacing(10), dnd_destination( @@ -1306,10 +1314,7 @@ where // .height(Length::Fill) .style(nav_bar_style); - if !self.core().is_condensed() { - container = container.max_width(280); - } - container.into() + container.center(Length::FillPortion(2)).into() } } diff --git a/src/ui/library.rs b/src/ui/library.rs index 56b47f6..c83760d 100644 --- a/src/ui/library.rs +++ b/src/ui/library.rs @@ -264,12 +264,18 @@ impl<'a> Library { let presentation_library = self.library_item(&self.presentation_library); let column = column![ + text::heading("Library").center().width(Length::Fill), + cosmic::iced::widget::horizontal_rule(1), song_library, image_library, video_library, presentation_library, - ]; - column.height(Length::Fill).spacing(5).into() + ] + .height(Length::Fill) + .padding(10) + .spacing(10) + .into(); + column } pub fn library_item( From 1446e35c58181531af0b80d8b8672d0cb0943199 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Wed, 27 Aug 2025 15:33:27 -0500 Subject: [PATCH 04/11] trying to figure out a more performant way to do svgs --- Cargo.lock | 113 +++++++++++++++++++++++++++++++++++--- Cargo.toml | 2 +- src/core/service_items.rs | 3 +- src/core/slide.rs | 70 +++++++++++------------ src/ui/text_svg.rs | 98 +++++++++++++++++++++------------ 5 files changed, 208 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9de55f7..7de30a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1276,7 +1276,7 @@ dependencies = [ "log", "rangemap", "rustc-hash 1.1.0", - "rustybuzz", + "rustybuzz 0.14.1", "self_cell", "smol_str", "swash", @@ -2943,7 +2943,7 @@ dependencies = [ "iced_graphics", "kurbo 0.10.4", "log", - "resvg", + "resvg 0.42.0", "rustc-hash 2.1.1", "softbuffer", "tiny-skia", @@ -2984,7 +2984,7 @@ dependencies = [ "lyon", "once_cell", "raw-window-handle", - "resvg", + "resvg 0.42.0", "rustc-hash 2.1.1", "rustix 0.38.44", "thiserror 1.0.69", @@ -3166,12 +3166,28 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + [[package]] name = "imagesize" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + [[package]] name = "immutable-chunkmap" version = "2.0.6" @@ -3643,6 +3659,7 @@ dependencies = [ "miette", "pretty_assertions", "rayon", + "resvg 0.45.1", "rodio", "ron 0.8.1", "serde", @@ -4927,6 +4944,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.37.5" @@ -5200,7 +5223,24 @@ dependencies = [ "rgb", "svgtypes", "tiny-skia", - "usvg", + "usvg 0.42.0", +] + +[[package]] +name = "resvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +dependencies = [ + "gif", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg 0.45.1", + "zune-jpeg", ] [[package]] @@ -5376,8 +5416,26 @@ dependencies = [ "libm", "smallvec", "ttf-parser 0.21.1", - "unicode-bidi-mirroring", - "unicode-ccc", + "unicode-bidi-mirroring 0.2.0", + "unicode-ccc 0.2.0", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.9.2", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser 0.25.1", + "unicode-bidi-mirroring 0.4.0", + "unicode-ccc 0.4.0", "unicode-properties", "unicode-script", ] @@ -6729,12 +6787,24 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + [[package]] name = "unicode-ccc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -6826,12 +6896,39 @@ dependencies = [ "data-url", "flate2", "fontdb 0.18.0", - "imagesize", + "imagesize 0.12.0", "kurbo 0.11.3", "log", "pico-args", "roxmltree", - "rustybuzz", + "rustybuzz 0.14.1", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "usvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb 0.23.0", + "imagesize 0.13.0", + "kurbo 0.11.3", + "log", + "pico-args", + "roxmltree", + "rustybuzz 0.20.1", "simplecss", "siphasher", "strict-num", diff --git a/Cargo.toml b/Cargo.toml index fd26207..acda2ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ gstreamer-app = "0.23" url = "2" colors-transform = "0.2.11" rayon = "1.11.0" -# resvg = "0.45.1" +resvg = "0.45.1" # femtovg = { version = "0.16.0", features = ["wgpu"] } # wgpu = "26.0.1" # mupdf = "0.5.0" diff --git a/src/core/service_items.rs b/src/core/service_items.rs index 19c2301..2ee9893 100644 --- a/src/core/service_items.rs +++ b/src/core/service_items.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; use crisp::types::{Keyword, Symbol, Value}; use miette::Result; +use resvg::usvg::fontdb; use tracing::{debug, error}; use crate::Slide; @@ -17,7 +18,7 @@ use super::videos::Video; use super::kinds::ServiceItemKind; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ServiceItem { pub id: i32, pub title: String, diff --git a/src/core/slide.rs b/src/core/slide.rs index a6cb45a..f54df80 100644 --- a/src/core/slide.rs +++ b/src/core/slide.rs @@ -2,10 +2,12 @@ use crisp::types::{Keyword, Symbol, Value}; use iced_video_player::Video; use miette::{miette, Result}; +use resvg::usvg::fontdb; use serde::{Deserialize, Serialize}; use std::{ fmt::Display, path::{Path, PathBuf}, + sync::Arc, }; use tracing::error; @@ -13,6 +15,40 @@ use crate::ui::text_svg::{self, TextSvg}; use super::songs::Song; +#[derive( + Clone, Debug, Default, PartialEq, Serialize, Deserialize, +)] +pub struct Slide { + id: i32, + background: Background, + text: String, + font: String, + font_size: i32, + text_alignment: TextAlignment, + audio: Option, + video_loop: bool, + video_start_time: f32, + video_end_time: f32, + #[serde(skip)] + pub text_svg: TextSvg, +} + +#[derive( + Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, +)] +pub enum BackgroundKind { + #[default] + Image, + Video, +} + +#[derive(Debug, Clone, Default)] +struct Image { + pub source: String, + pub fit: String, + pub children: Vec, +} + #[derive( Clone, Copy, @@ -203,15 +239,6 @@ impl Display for ParseError { } } -#[derive( - Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, -)] -pub enum BackgroundKind { - #[default] - Image, - Video, -} - impl From for BackgroundKind { fn from(value: String) -> Self { if value == "image" { @@ -222,24 +249,6 @@ impl From for BackgroundKind { } } -#[derive( - Clone, Debug, Default, PartialEq, Serialize, Deserialize, -)] -pub struct Slide { - id: i32, - background: Background, - text: String, - font: String, - font_size: i32, - text_alignment: TextAlignment, - audio: Option, - video_loop: bool, - video_start_time: f32, - video_end_time: f32, - #[serde(skip)] - pub text_svg: TextSvg, -} - impl From<&Slide> for Value { fn from(value: &Slide) -> Self { Self::List(vec![Self::Symbol(Symbol("slide".into()))]) @@ -656,13 +665,6 @@ impl SlideBuilder { } } -#[derive(Debug, Clone, Default)] -struct Image { - pub source: String, - pub fit: String, - pub children: Vec, -} - impl Image { fn new() -> Self { Self { diff --git a/src/ui/text_svg.rs b/src/ui/text_svg.rs index cbfd280..b04ec4c 100644 --- a/src/ui/text_svg.rs +++ b/src/ui/text_svg.rs @@ -1,22 +1,28 @@ use std::{ fmt::Display, hash::{Hash, Hasher}, + io::Read, + sync::Arc, }; use colors_transform::Rgb; use cosmic::{ iced::{ font::{Style, Weight}, - Length, Size, + ContentFit, Length, Size, }, prelude::*, - widget::{container, svg::Handle, Svg}, + widget::{container, image::Handle, Image}, }; -use tracing::error; +use resvg::{ + tiny_skia::{self, Pixmap}, + usvg::Tree, +}; +use tracing::{debug, error}; use crate::TextAlignment; -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default)] pub struct TextSvg { text: String, font: Font, @@ -25,6 +31,19 @@ pub struct TextSvg { fill: Color, alignment: TextAlignment, handle: Option, + fontdb: Arc, +} + +impl PartialEq for TextSvg { + fn eq(&self, other: &Self) -> bool { + self.text == other.text + && self.font == other.font + && self.shadow == other.shadow + && self.stroke == other.stroke + && self.fill == other.fill + && self.alignment == other.alignment + && self.handle == other.handle + } } impl Hash for TextSvg { @@ -46,6 +65,27 @@ pub struct Font { size: u8, } +#[derive(Clone, Debug, Default, PartialEq, Hash)] +pub struct Shadow { + pub offset_x: i16, + pub offset_y: i16, + pub spread: u16, + pub color: Color, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash)] +pub struct Stroke { + size: u16, + color: Color, +} + +pub enum Message { + None, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Color(Rgb); + impl From for Font { fn from(value: cosmic::font::Font) -> Self { Self { @@ -113,9 +153,6 @@ impl Font { } } -#[derive(Clone, Debug, PartialEq)] -pub struct Color(Rgb); - impl Hash for Color { fn hash(&self, state: &mut H) { self.0.to_css_hex_string().hash(state); @@ -158,24 +195,6 @@ impl Display for Color { } } -#[derive(Clone, Debug, Default, PartialEq, Hash)] -pub struct Shadow { - pub offset_x: i16, - pub offset_y: i16, - pub spread: u16, - pub color: Color, -} - -#[derive(Clone, Debug, Default, PartialEq, Hash)] -pub struct Stroke { - size: u16, - color: Color, -} - -pub enum Message { - None, -} - impl TextSvg { pub fn new(text: impl Into) -> Self { Self { @@ -234,7 +253,7 @@ impl TextSvg { } else { "".into() }; - let size = Size::new(640.0, 360.0); + let size = Size::new(1920.0, 1080.0); let total_lines = self.text.lines().count(); let half_lines = (total_lines / 2) as f32; let middle_position = size.height / 2.0; @@ -266,20 +285,31 @@ impl TextSvg { self.font.name, self.font.size, self.fill, stroke, text); - let handle = Handle::from_memory( - Box::leak( - ::clone(&final_svg) - .into_boxed_str(), - ) - .as_bytes(), - ); + debug!(?final_svg); + let resvg_tree = Tree::from_str( + &final_svg, + &resvg::usvg::Options { + fontdb: Arc::clone(&self.fontdb), + ..Default::default() + }, + ) + .expect("Woops mama"); + // debug!(?resvg_tree); + let transform = tiny_skia::Transform::default(); + let mut pixmap = + Pixmap::new(size.width as u32, size.height as u32) + .expect("opops"); + resvg::render(&resvg_tree, transform, &mut pixmap.as_mut()); + // debug!(?pixmap); + let handle = Handle::from_bytes(pixmap.data().to_owned()); self.handle = Some(handle); self } pub fn view<'a>(&self) -> Element<'a, Message> { container( - Svg::new(self.handle.clone().unwrap()) + Image::new(self.handle.clone().unwrap()) + .content_fit(ContentFit::Contain) .width(Length::Fill) .height(Length::Fill), ) From 4ccb186189b45bc5b6f9bb8f3233e689a5aeea81 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Fri, 29 Aug 2025 16:41:24 -0500 Subject: [PATCH 05/11] closer to using these text_svgs --- src/core/service_items.rs | 8 ++--- src/core/slide.rs | 63 ++++++++++++++------------------------- src/main.rs | 48 ++++++++++++++++++++++------- src/ui/presenter.rs | 11 +++++-- src/ui/song_editor.rs | 12 ++++---- src/ui/text_svg.rs | 45 ++++++++++++++++++++-------- 6 files changed, 112 insertions(+), 75 deletions(-) diff --git a/src/core/service_items.rs b/src/core/service_items.rs index 2ee9893..76e99cc 100644 --- a/src/core/service_items.rs +++ b/src/core/service_items.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::cmp::Ordering; use std::ops::Deref; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; use crisp::types::{Keyword, Symbol, Value}; @@ -24,7 +24,7 @@ pub struct ServiceItem { pub title: String, pub database_id: i32, pub kind: ServiceItemKind, - pub slides: Arc<[Slide]>, + pub slides: Vec, // pub item: Box, } @@ -122,7 +122,7 @@ impl Default for ServiceItem { title: String::default(), database_id: 0, kind: ServiceItemKind::Content(Slide::default()), - slides: Arc::new([]), + slides: vec![], // item: Box::new(Image::default()), } } @@ -172,7 +172,7 @@ impl From<&Value> for ServiceItem { kind: ServiceItemKind::Content( slide.clone(), ), - slides: Arc::new([slide]), + slides: vec![slide], } } else if let Some(background) = list.get(background_pos) diff --git a/src/core/slide.rs b/src/core/slide.rs index f54df80..d1ef8b6 100644 --- a/src/core/slide.rs +++ b/src/core/slide.rs @@ -30,7 +30,7 @@ pub struct Slide { video_start_time: f32, video_end_time: f32, #[serde(skip)] - pub text_svg: TextSvg, + pub text_svg: Option, } #[derive( @@ -261,6 +261,11 @@ impl Slide { self } + pub fn with_text_svg(mut self, text_svg: TextSvg) -> Self { + self.text_svg = Some(text_svg); + self + } + pub fn set_font(mut self, font: impl AsRef) -> Self { self.font = font.as_ref().into(); self @@ -284,6 +289,10 @@ impl Slide { self.text.clone() } + pub fn text_alignment(&self) -> TextAlignment { + self.text_alignment.clone() + } + pub fn font_size(&self) -> i32 { self.font_size } @@ -623,45 +632,19 @@ impl SlideBuilder { let Some(video_end_time) = self.video_end_time else { return Err(miette!("No video_end_time")); }; - if let Some(text_svg) = self.text_svg { - Ok(Slide { - background, - text, - font, - font_size, - text_alignment, - audio: self.audio, - video_loop, - video_start_time, - video_end_time, - text_svg, - ..Default::default() - }) - } else { - let text_svg = TextSvg::new(text.clone()) - .alignment(text_alignment) - .fill("#fff") - .shadow(text_svg::shadow(2, 2, 5, "#000000")) - .stroke(text_svg::stroke(3, "#000")) - .font( - text_svg::Font::from(font.clone()) - .size(font_size.try_into().unwrap()), - ) - .build(); - Ok(Slide { - background, - text, - font, - font_size, - text_alignment, - audio: self.audio, - video_loop, - video_start_time, - video_end_time, - text_svg, - ..Default::default() - }) - } + Ok(Slide { + background, + text, + font, + font_size, + text_alignment, + audio: self.audio, + video_loop, + video_start_time, + video_end_time, + text_svg: self.text_svg, + ..Default::default() + }) } } diff --git a/src/main.rs b/src/main.rs index 1a9eb64..202205c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,8 +28,10 @@ use crisp::types::Value; use lisp::parse_lisp; use miette::{miette, Result}; use rayon::prelude::*; +use resvg::usvg::fontdb; use std::fs::read_to_string; use std::path::PathBuf; +use std::sync::Arc; use tracing::{debug, level_filters::LevelFilter}; use tracing::{error, warn}; use tracing_subscriber::EnvFilter; @@ -38,6 +40,8 @@ use ui::presenter::{self, Presenter}; use ui::song_editor::{self, SongEditor}; use ui::EditorMode; +use crate::ui::text_svg; + pub mod core; pub mod lisp; pub mod ui; @@ -111,6 +115,7 @@ struct App { song_editor: SongEditor, searching: bool, library_dragged_item: Option, + fontdb: Arc, } #[derive(Debug, Clone)] @@ -159,15 +164,19 @@ impl cosmic::Application for App { debug!("init"); let nav_model = nav_bar::Model::default(); + let mut fontdb = fontdb::Database::new(); + fontdb.load_system_fonts(); + let fontdb = Arc::new(fontdb); + let mut windows = vec![]; if input.ui { windows.push(core.main_window_id().unwrap()); } - let items = match read_to_string(input.file) { + let mut items = match read_to_string(input.file) { Ok(lisp) => { - let mut slide_vector = vec![]; + let mut service_items = vec![]; let lisp = crisp::reader::read(&lisp); match lisp { Value::List(vec) => { @@ -178,12 +187,12 @@ impl cosmic::Application for App { // slide_vector.append(items); for value in vec { let mut inner_vector = parse_lisp(value); - slide_vector.append(&mut inner_vector); + service_items.append(&mut inner_vector); } } _ => todo!(), } - slide_vector + service_items } Err(e) => { warn!("Missing file or could not read: {e}"); @@ -191,15 +200,32 @@ impl cosmic::Application for App { } }; + let items: Vec = items + .into_par_iter() + .map(|mut item| { + item.slides = item + .slides + .into_par_iter() + .map(|mut slide| { + text_svg::text_svg_generator( + &mut slide, + Arc::clone(&fontdb), + ); + slide + }) + .collect(); + item + }) + .collect(); + let presenter = Presenter::with_items(items.clone()); - let song_editor = SongEditor::new(); + let song_editor = SongEditor::new(Arc::clone(&fontdb)); // for item in items.iter() { // nav_model.insert().text(item.title()).data(item.clone()); // } // nav_model.activate_position(0); - let mut app = App { presenter, core, @@ -217,6 +243,7 @@ impl cosmic::Application for App { searching: false, current_item: (0, 0), library_dragged_item: None, + fontdb, }; let mut batch = vec![]; @@ -1289,11 +1316,10 @@ where vec!["application/service-item".into()] ) .data_received_for::(|item| { - if let Some(item) = item { - Message::AppendServiceItem(item) - } else { - Message::None - } + item.map_or_else( + || Message::None, + |item| Message::AppendServiceItem(item), + ) }) .on_finish( move |mime, data, action, x, y| { diff --git a/src/ui/presenter.rs b/src/ui/presenter.rs index 779e964..565cead 100644 --- a/src/ui/presenter.rs +++ b/src/ui/presenter.rs @@ -1,4 +1,5 @@ use miette::{IntoDiagnostic, Result}; +use resvg::usvg::fontdb; use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc}; use cosmic::{ @@ -30,7 +31,7 @@ use url::Url; use crate::{ core::{service_items::ServiceItem, slide::Slide}, - // ui::widgets::slide_text, + ui::text_svg, BackgroundKind, }; @@ -148,6 +149,7 @@ impl Presenter { }; let total_slides: usize = items.iter().fold(0, |a, item| a + item.slides.len()); + Self { current_slide: items[0].slides[0].clone(), current_item: 0, @@ -741,7 +743,12 @@ pub(crate) fn slide_view( .align_x(Horizontal::Left) } else { // SVG based - let text = slide.text_svg.view().map(|m| Message::None); + let text: Element = + if let Some(text) = &slide.text_svg { + text.view().map(|_| Message::None).into() + } else { + Space::with_width(0).into() + }; Container::new(text) .center(Length::Fill) .align_x(Horizontal::Left) diff --git a/src/ui/song_editor.rs b/src/ui/song_editor.rs index 89ebf89..943379c 100644 --- a/src/ui/song_editor.rs +++ b/src/ui/song_editor.rs @@ -1,4 +1,4 @@ -use std::{io, path::PathBuf}; +use std::{io, path::PathBuf, sync::Arc}; use cosmic::{ dialog::file_chooser::open::Dialog, @@ -31,7 +31,7 @@ use super::presenter::slide_view; pub struct SongEditor { pub song: Option, title: String, - font_db: fontdb::Database, + font_db: Arc, fonts: Vec<(fontdb::ID, String)>, fonts_combo: combo_box::State, font_sizes: combo_box::State, @@ -72,11 +72,9 @@ pub enum Message { } impl SongEditor { - pub fn new() -> Self { + pub fn new(font_db: Arc) -> Self { let fonts = font_dir(); debug!(?fonts); - let mut font_db = fontdb::Database::new(); - font_db.load_system_fonts(); let mut fonts: Vec<(fontdb::ID, String)> = font_db .faces() .map(|f| { @@ -447,7 +445,9 @@ order", impl Default for SongEditor { fn default() -> Self { - Self::new() + let mut fontdb = fontdb::Database::new(); + fontdb.load_system_fonts(); + Self::new(Arc::new(fontdb)) } } diff --git a/src/ui/text_svg.rs b/src/ui/text_svg.rs index b04ec4c..e8e185a 100644 --- a/src/ui/text_svg.rs +++ b/src/ui/text_svg.rs @@ -16,7 +16,7 @@ use cosmic::{ }; use resvg::{ tiny_skia::{self, Pixmap}, - usvg::Tree, + usvg::{fontdb, Tree}, }; use tracing::{debug, error}; @@ -230,6 +230,11 @@ impl TextSvg { self } + pub fn fontdb(mut self, fontdb: Arc) -> Self { + self.fontdb = fontdb; + self + } + pub fn alignment(mut self, alignment: TextAlignment) -> Self { self.alignment = alignment; self @@ -285,7 +290,7 @@ impl TextSvg { self.font.name, self.font.size, self.fill, stroke, text); - debug!(?final_svg); + // debug!(?final_svg); let resvg_tree = Tree::from_str( &final_svg, &resvg::usvg::Options { @@ -301,21 +306,17 @@ impl TextSvg { .expect("opops"); resvg::render(&resvg_tree, transform, &mut pixmap.as_mut()); // debug!(?pixmap); - let handle = Handle::from_bytes(pixmap.data().to_owned()); + let handle = Handle::from_bytes(pixmap.take()); self.handle = Some(handle); self } pub fn view<'a>(&self) -> Element<'a, Message> { - container( - Image::new(self.handle.clone().unwrap()) - .content_fit(ContentFit::Contain) - .width(Length::Fill) - .height(Length::Fill), - ) - .width(Length::Fill) - .height(Length::Fill) - .into() + Image::new(self.handle.clone().unwrap()) + .content_fit(ContentFit::Contain) + .width(Length::Fill) + .height(Length::Fill) + .into() } fn text_spans(&self) -> Vec { @@ -352,6 +353,26 @@ pub fn color(color: impl AsRef) -> Color { Color::from_hex_str(color) } +pub fn text_svg_generator( + slide: &mut crate::core::slide::Slide, + fontdb: Arc, +) { + if slide.text().len() > 0 { + let text_svg = TextSvg::new(slide.text()) + .alignment(slide.text_alignment()) + .fill("#fff") + .shadow(shadow(2, 2, 5, "#000000")) + .stroke(stroke(3, "#000")) + .font( + Font::from(slide.font().clone()) + .size(slide.font_size().try_into().unwrap()), + ) + .fontdb(Arc::clone(&fontdb)) + .build(); + slide.text_svg = Some(text_svg); + } +} + #[cfg(test)] mod test { use pretty_assertions::assert_eq; From 23cd34388b11dc081c9ba5888e053bf55102cdb9 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Tue, 2 Sep 2025 09:19:10 -0500 Subject: [PATCH 06/11] update todo --- todo.org | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/todo.org b/todo.org index 005086d..427d985 100644 --- a/todo.org +++ b/todo.org @@ -16,6 +16,10 @@ Actually, what if we just made the svg at load/creation time and stored it in th ** SVG performs badly Since SVG's apparently run poorly in iced, instead I'll need to see about either creating a new text element, or teaching Iced to render strokes and shadows on text. +** Fork Cryoglyph +This fork will render text 3 times. Once for the text, once for the stroke, once for the shadow. This will only be used in the slides and therefore should not be much of a performance hit since we will only be render 3 copies of the given text. This should not be bad performance since it's not a large amount of text. + +This also means in our custom widget with our custom fork, we can animate each individually perhaps. * TODO [#C] Make the presenter more modular so things are easier to change. From 4792304d8b75af1e159324d44c66be14a3c06fe3 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Tue, 2 Sep 2025 09:19:17 -0500 Subject: [PATCH 07/11] remove unused use statements --- src/ui/presenter.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/ui/presenter.rs b/src/ui/presenter.rs index 565cead..5a604b5 100644 --- a/src/ui/presenter.rs +++ b/src/ui/presenter.rs @@ -1,38 +1,37 @@ use miette::{IntoDiagnostic, Result}; -use resvg::usvg::fontdb; use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc}; use cosmic::{ + Task, iced::{ + Background, Border, Color, ContentFit, Font, Length, Shadow, + Vector, alignment::Horizontal, border, font::{Family, Stretch, Style, Weight}, - Background, Border, Color, ContentFit, Font, Length, Shadow, - Vector, }, iced_widget::{ rich_text, scrollable::{ - scroll_to, AbsoluteOffset, Direction, Scrollbar, + AbsoluteOffset, Direction, Scrollbar, scroll_to, }, span, stack, vertical_rule, }, prelude::*, widget::{ - container, image, mouse_area, responsive, scrollable, text, - Column, Container, Id, Row, Space, + Column, Container, Id, Row, Space, container, image, + mouse_area, responsive, scrollable, text, }, - Task, }; -use iced_video_player::{gst_pbutils, Position, Video, VideoPlayer}; +use iced_video_player::{Position, Video, VideoPlayer, gst_pbutils}; use rodio::{Decoder, OutputStream, Sink}; use tracing::{debug, error, info, warn}; use url::Url; use crate::{ + BackgroundKind, core::{service_items::ServiceItem, slide::Slide}, ui::text_svg, - BackgroundKind, }; const REFERENCE_WIDTH: f32 = 1920.0; @@ -133,7 +132,9 @@ impl Presenter { Some(v) } Err(e) => { - error!("Had an error creating the video object: {e}, likely the first slide isn't a video"); + error!( + "Had an error creating the video object: {e}, likely the first slide isn't a video" + ); None } } @@ -379,7 +380,7 @@ impl Presenter { self.hovered_slide = slide; } Message::StartAudio => { - return Action::Task(self.start_audio()) + return Action::Task(self.start_audio()); } Message::EndAudio => { self.sink.1.stop(); @@ -642,7 +643,9 @@ impl Presenter { self.video = Some(v) } Err(e) => { - error!("Had an error creating the video object: {e}"); + error!( + "Had an error creating the video object: {e}" + ); self.video = None; } } From d6b4cc6297bb1ec721e400722a006d785751c24f Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Tue, 2 Sep 2025 15:27:38 -0500 Subject: [PATCH 08/11] some ui tweaks --- src/ui/library.rs | 58 +++++++++++++++++++++++++++++++++++++++------ src/ui/presenter.rs | 16 ++++++------- src/ui/text_svg.rs | 6 +++-- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/ui/library.rs b/src/ui/library.rs index c83760d..5c67fa5 100644 --- a/src/ui/library.rs +++ b/src/ui/library.rs @@ -460,14 +460,45 @@ impl<'a> Library { })) .center_y(20) .center_x(Length::Fill); - let subtext = container(responsive(|size| { + let subtext = container(responsive(move |size| { let color: Color = if item.background().is_some() { - theme::active().cosmic().accent_text_color().into() + if let Some((library, selected)) = self.selected_item + { + if model.kind == library + && selected == index as i32 + { + theme::active().cosmic().control_0().into() + } else { + theme::active() + .cosmic() + .accent_text_color() + .into() + } + } else { + theme::active() + .cosmic() + .accent_text_color() + .into() + } } else { - theme::active() - .cosmic() - .destructive_text_color() - .into() + if let Some((library, selected)) = self.selected_item + { + if model.kind == library + && selected == index as i32 + { + theme::active().cosmic().control_0().into() + } else { + theme::active() + .cosmic() + .destructive_text_color() + .into() + } + } else { + theme::active() + .cosmic() + .destructive_text_color() + .into() + } }; text::body(elide_text(item.subtext(), size.width)) .center() @@ -498,7 +529,19 @@ impl<'a> Library { { t.cosmic().accent.selected.into() } else { - t.cosmic().button.base.into() + if let Some((library, hovered)) = + self.hovered_item + { + if model.kind == library + && hovered == index as i32 + { + t.cosmic().button.hover.into() + } else { + t.cosmic().button.base.into() + } + } else { + t.cosmic().button.base.into() + } } } else if let Some((library, hovered)) = self.hovered_item @@ -519,6 +562,7 @@ impl<'a> Library { .rounded(t.cosmic().corner_radii.radius_m), ) }) + .padding([3, 0]) .into() } diff --git a/src/ui/presenter.rs b/src/ui/presenter.rs index 5a604b5..5880d8a 100644 --- a/src/ui/presenter.rs +++ b/src/ui/presenter.rs @@ -2,36 +2,36 @@ use miette::{IntoDiagnostic, Result}; use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc}; use cosmic::{ - Task, iced::{ - Background, Border, Color, ContentFit, Font, Length, Shadow, - Vector, alignment::Horizontal, border, font::{Family, Stretch, Style, Weight}, + Background, Border, Color, ContentFit, Font, Length, Shadow, + Vector, }, iced_widget::{ rich_text, scrollable::{ - AbsoluteOffset, Direction, Scrollbar, scroll_to, + scroll_to, AbsoluteOffset, Direction, Scrollbar, }, span, stack, vertical_rule, }, prelude::*, widget::{ - Column, Container, Id, Row, Space, container, image, - mouse_area, responsive, scrollable, text, + container, image, mouse_area, responsive, scrollable, text, + Column, Container, Id, Row, Space, }, + Task, }; -use iced_video_player::{Position, Video, VideoPlayer, gst_pbutils}; +use iced_video_player::{gst_pbutils, Position, Video, VideoPlayer}; use rodio::{Decoder, OutputStream, Sink}; use tracing::{debug, error, info, warn}; use url::Url; use crate::{ - BackgroundKind, core::{service_items::ServiceItem, slide::Slide}, ui::text_svg, + BackgroundKind, }; const REFERENCE_WIDTH: f32 = 1920.0; diff --git a/src/ui/text_svg.rs b/src/ui/text_svg.rs index e8e185a..a2e844a 100644 --- a/src/ui/text_svg.rs +++ b/src/ui/text_svg.rs @@ -290,7 +290,7 @@ impl TextSvg { self.font.name, self.font.size, self.fill, stroke, text); - // debug!(?final_svg); + debug!("starting..."); let resvg_tree = Tree::from_str( &final_svg, &resvg::usvg::Options { @@ -299,15 +299,17 @@ impl TextSvg { }, ) .expect("Woops mama"); - // debug!(?resvg_tree); + debug!("parsed"); let transform = tiny_skia::Transform::default(); let mut pixmap = Pixmap::new(size.width as u32, size.height as u32) .expect("opops"); resvg::render(&resvg_tree, transform, &mut pixmap.as_mut()); // debug!(?pixmap); + debug!("rendered"); let handle = Handle::from_bytes(pixmap.take()); self.handle = Some(handle); + debug!("stored"); self } From 6c8cb6c5b24fce05191342fb77356e34061889b1 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Wed, 3 Sep 2025 15:49:35 -0500 Subject: [PATCH 09/11] hey we are rasterizing right, just not loading into handle --- src/ui/presenter.rs | 206 ++++++++++++++++++++++++-------------------- src/ui/text_svg.rs | 19 ++-- 2 files changed, 128 insertions(+), 97 deletions(-) diff --git a/src/ui/presenter.rs b/src/ui/presenter.rs index 5880d8a..183208d 100644 --- a/src/ui/presenter.rs +++ b/src/ui/presenter.rs @@ -19,7 +19,7 @@ use cosmic::{ prelude::*, widget::{ container, image, mouse_area, responsive, scrollable, text, - Column, Container, Id, Row, Space, + Column, Container, Id, Image, Row, Space, }, Task, }; @@ -707,91 +707,100 @@ pub(crate) fn slide_view( let slide_text = slide.text(); // let font = SvgFont::from(font).size(font_size.floor() as u8); - let text_container = if delegate { - // text widget based - let font_size = - scale_font(slide.font_size() as f32, width); - let lines = slide_text.lines(); - let text: Vec> = lines - .map(|t| { - rich_text([span(format!("{}\n", t)) - .background( - Background::Color(Color::BLACK) - .scale_alpha(0.4), - ) - .border(border::rounded(10)) - .padding(10)]) - .size(font_size) - .font(font) - .center() - .into() - // let chars: Vec = t - // .chars() - // .map(|c| -> Span { - // let character: String = format!("{}/n", c); - // span(character) - // .size(font_size) - // .font(font) - // .background( - // Background::Color(Color::BLACK) - // .scale_alpha(0.4), - // ) - // .border(border::rounded(10)) - // .padding(10) - }) - .collect(); - let text = Column::with_children(text).spacing(26); - Container::new(text) - .center(Length::Fill) - .align_x(Horizontal::Left) - } else { - // SVG based - let text: Element = - if let Some(text) = &slide.text_svg { - text.view().map(|_| Message::None).into() - } else { - Space::with_width(0).into() - }; - Container::new(text) - .center(Length::Fill) - .align_x(Horizontal::Left) - // text widget based - // let font_size = - // scale_font(slide.font_size() as f32, width); - // let lines = slide_text.lines(); - // let text: Vec> = lines - // .map(|t| { - // rich_text([span(format!("{}\n", t)) - // .background( - // Background::Color(Color::BLACK) - // .scale_alpha(0.4), - // ) - // .border(border::rounded(10)) - // .padding(10)]) - // .size(font_size) - // .font(font) - // .center() - // .into() - // // let chars: Vec = t - // // .chars() - // // .map(|c| -> Span { - // // let character: String = format!("{}/n", c); - // // span(character) - // // .size(font_size) - // // .font(font) - // // .background( - // // Background::Color(Color::BLACK) - // // .scale_alpha(0.4), - // // ) - // // .border(border::rounded(10)) - // // .padding(10) - // }) - // .collect(); - // let text = Column::with_children(text).spacing(26); - // Container::new(text) - // .center(Length::Fill) - // .align_x(Horizontal::Left) - }; + // let text_container = if delegate { + // // text widget based + // let font_size = + // scale_font(slide.font_size() as f32, width); + // let lines = slide_text.lines(); + // let text: Vec> = lines + // .map(|t| { + // rich_text([span(format!("{}\n", t)) + // .background( + // Background::Color(Color::BLACK) + // .scale_alpha(0.4), + // ) + // .border(border::rounded(10)) + // .padding(10)]) + // .size(font_size) + // .font(font) + // .center() + // .into() + // // let chars: Vec = t + // // .chars() + // // .map(|c| -> Span { + // // let character: String = format!("{}/n", c); + // // span(character) + // // .size(font_size) + // // .font(font) + // // .background( + // // Background::Color(Color::BLACK) + // // .scale_alpha(0.4), + // // ) + // // .border(border::rounded(10)) + // // .padding(10) + // }) + // .collect(); + // let text = Column::with_children(text).spacing(26); + // Container::new(text) + // .center(Length::Fill) + // .align_x(Horizontal::Left) + // } else { + // // SVG based + // let text: Element = + // if let Some(text) = &slide.text_svg { + // if let Some(handle) = &text.handle { + // debug!("we made it boys"); + // Image::new(handle) + // .content_fit(ContentFit::Cover) + // .width(Length::Fill) + // .height(Length::Fill) + // .into() + // } else { + // Space::with_width(0).into() + // } + // } else { + // Space::with_width(0).into() + // }; + // Container::new(text) + // .center(Length::Fill) + // .align_x(Horizontal::Left) + // // text widget based + // // let font_size = + // // scale_font(slide.font_size() as f32, width); + // // let lines = slide_text.lines(); + // // let text: Vec> = lines + // // .map(|t| { + // // rich_text([span(format!("{}\n", t)) + // // .background( + // // Background::Color(Color::BLACK) + // // .scale_alpha(0.4), + // // ) + // // .border(border::rounded(10)) + // // .padding(10)]) + // // .size(font_size) + // // .font(font) + // // .center() + // // .into() + // // // let chars: Vec = t + // // // .chars() + // // // .map(|c| -> Span { + // // // let character: String = format!("{}/n", c); + // // // span(character) + // // // .size(font_size) + // // // .font(font) + // // // .background( + // // // Background::Color(Color::BLACK) + // // // .scale_alpha(0.4), + // // // ) + // // // .border(border::rounded(10)) + // // // .padding(10) + // // }) + // // .collect(); + // // let text = Column::with_children(text).spacing(26); + // // Container::new(text) + // // .center(Length::Fill) + // // .align_x(Horizontal::Left) + // }; // let stroke_text_container = Container::new(stroke_text) // .center(Length::Fill) @@ -799,6 +808,22 @@ pub(crate) fn slide_view( // let text_stack = // stack!(stroke_text_container, text_container); + + let text: Element = + if let Some(text) = &slide.text_svg { + if let Some(handle) = &text.handle { + debug!("we made it boys"); + Image::new(handle) + .content_fit(ContentFit::Cover) + .width(Length::Fill) + .height(Length::Fill) + .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)) @@ -806,7 +831,7 @@ pub(crate) fn slide_view( .clip(true) .width(width) .height(size.height); - let container = match slide.background().kind { + let background = match slide.background().kind { BackgroundKind::Image => { let path = slide.background().path.clone(); Container::new( @@ -855,11 +880,8 @@ pub(crate) fn slide_view( } } }; - let stack = stack!( - black, - container.center(Length::Fill), - text_container - ); + let stack = + stack!(black, background.center(Length::Fill), text); Container::new(stack).center(Length::Fill).into() }); // let vid = if let Some(video) = &video { diff --git a/src/ui/text_svg.rs b/src/ui/text_svg.rs index a2e844a..a70055f 100644 --- a/src/ui/text_svg.rs +++ b/src/ui/text_svg.rs @@ -2,6 +2,7 @@ use std::{ fmt::Display, hash::{Hash, Hasher}, io::Read, + path::PathBuf, sync::Arc, }; @@ -30,7 +31,7 @@ pub struct TextSvg { stroke: Option, fill: Color, alignment: TextAlignment, - handle: Option, + pub handle: Option, fontdb: Arc, } @@ -258,7 +259,7 @@ impl TextSvg { } else { "".into() }; - let size = Size::new(1920.0, 1080.0); + let size = Size::new(3840.0, 2160.0); let total_lines = self.text.lines().count(); let half_lines = (total_lines / 2) as f32; let middle_position = size.height / 2.0; @@ -291,8 +292,8 @@ impl TextSvg { self.font.size, self.fill, stroke, text); debug!("starting..."); - let resvg_tree = Tree::from_str( - &final_svg, + let resvg_tree = Tree::from_data( + &final_svg.as_bytes(), &resvg::usvg::Options { fontdb: Arc::clone(&self.fontdb), ..Default::default() @@ -306,6 +307,14 @@ impl TextSvg { .expect("opops"); resvg::render(&resvg_tree, transform, &mut pixmap.as_mut()); // debug!(?pixmap); + // let mut path = dirs::data_local_dir().unwrap(); + // path.push(PathBuf::from("lumina")); + // path.push(PathBuf::from("temp")); + // let file_title = + // &self.text.lines().next().unwrap().trim_end(); + // path.push(PathBuf::from(file_title)); + // path.set_extension("png"); + // let _ = pixmap.save_png(path); debug!("rendered"); let handle = Handle::from_bytes(pixmap.take()); self.handle = Some(handle); @@ -315,7 +324,7 @@ impl TextSvg { pub fn view<'a>(&self) -> Element<'a, Message> { Image::new(self.handle.clone().unwrap()) - .content_fit(ContentFit::Contain) + .content_fit(ContentFit::Cover) .width(Length::Fill) .height(Length::Fill) .into() From 035f8896f180f293c78ab9fae006b2ad7c7654b6 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Fri, 5 Sep 2025 13:05:54 -0500 Subject: [PATCH 10/11] updating to use the in memory rendered images for text --- Cargo.lock | 429 ++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/main.rs | 98 +++++----- src/ui/presenter.rs | 1 - src/ui/text_svg.rs | 17 +- todo.org | 1 + 6 files changed, 482 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7de30a3..106d5e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -267,6 +276,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + [[package]] name = "apply" version = "0.3.0" @@ -282,6 +297,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -614,6 +646,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "av1-grain" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -689,6 +744,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -704,6 +765,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + [[package]] name = "block" version = "0.1.6" @@ -750,6 +817,12 @@ dependencies = [ "piper", ] +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + [[package]] name = "bumpalo" version = "3.19.0" @@ -852,6 +925,16 @@ dependencies = [ "nom", ] +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon 0.12.16", +] + [[package]] name = "cfg-expr" version = "0.20.2" @@ -859,7 +942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d458d63f0f0f482c8da9b7c8b76c21bd885a02056cc94c6404d861ca2b8206" dependencies = [ "smallvec", - "target-lexicon", + "target-lexicon 0.13.2", ] [[package]] @@ -1796,6 +1879,26 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1886,6 +1989,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "extended" version = "0.1.0" @@ -1913,6 +2031,26 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "fd-lock" version = "4.0.4" @@ -2300,7 +2438,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 7.0.5", "windows-sys 0.59.0", ] @@ -2362,7 +2500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215" dependencies = [ "libc", - "system-deps", + "system-deps 7.0.5", ] [[package]] @@ -2400,7 +2538,7 @@ checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 7.0.5", ] [[package]] @@ -2511,7 +2649,7 @@ dependencies = [ "gstreamer-base-sys", "gstreamer-sys", "libc", - "system-deps", + "system-deps 7.0.5", ] [[package]] @@ -2541,7 +2679,7 @@ dependencies = [ "gstreamer-base-sys", "gstreamer-sys", "libc", - "system-deps", + "system-deps 7.0.5", ] [[package]] @@ -2568,7 +2706,7 @@ dependencies = [ "gobject-sys", "gstreamer-sys", "libc", - "system-deps", + "system-deps 7.0.5", ] [[package]] @@ -2598,7 +2736,7 @@ dependencies = [ "gstreamer-sys", "gstreamer-video-sys", "libc", - "system-deps", + "system-deps 7.0.5", ] [[package]] @@ -2610,7 +2748,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 7.0.5", ] [[package]] @@ -2641,7 +2779,7 @@ dependencies = [ "gstreamer-base-sys", "gstreamer-sys", "libc", - "system-deps", + "system-deps 7.0.5", ] [[package]] @@ -3154,14 +3292,24 @@ dependencies = [ [[package]] name = "image" -version = "0.25.6" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" dependencies = [ "bytemuck", "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", "num-traits", - "png", + "png 0.18.0", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", "zune-core", "zune-jpeg", ] @@ -3188,6 +3336,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + [[package]] name = "immutable-chunkmap" version = "2.0.6" @@ -3257,6 +3411,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -3291,6 +3456,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -3448,6 +3622,12 @@ dependencies = [ "spin", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "lewton" version = "0.10.2" @@ -3534,6 +3714,16 @@ dependencies = [ "zbus 5.9.0", ] +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libloading" version = "0.8.8" @@ -3637,6 +3827,15 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "lru" version = "0.12.5" @@ -3654,6 +3853,7 @@ dependencies = [ "gstreamer", "gstreamer-app", "iced_video_player", + "image", "lexpr", "libcosmic", "miette", @@ -3753,6 +3953,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "md-5" version = "0.10.6" @@ -3892,6 +4102,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "moxcms" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "muldiv" version = "1.0.1" @@ -3978,6 +4198,12 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nibble_vec" version = "0.1.0" @@ -4034,6 +4260,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "notify" version = "8.2.0" @@ -4068,6 +4300,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -4128,6 +4370,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ + "num-bigint", "num-integer", "num-traits", ] @@ -4821,6 +5064,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.9.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "2.8.0" @@ -4943,6 +5199,37 @@ name = "profiling" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pxfm" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e790881194f6f6e86945f0a42a6981977323669aeb6c40e9c7ec253133b96f8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] [[package]] name = "quick-error" @@ -5055,6 +5342,56 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223" +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand 0.8.5", + "rand_chacha 0.3.1", + "simd_helpers", + "system-deps 6.2.2", + "thiserror 1.0.69", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -5701,6 +6038,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "simplecss" version = "0.2.2" @@ -6362,13 +6708,26 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr 0.15.8", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + [[package]] name = "system-deps" version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" dependencies = [ - "cfg-expr", + "cfg-expr 0.20.2", "heck 0.5.0", "pkg-config", "toml", @@ -6386,6 +6745,12 @@ dependencies = [ "slotmap", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "target-lexicon" version = "0.13.2" @@ -6489,6 +6854,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.41" @@ -6533,7 +6912,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", - "png", + "png 0.17.16", "tiny-skia-path", ] @@ -6958,6 +7337,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -8275,6 +8665,15 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zune-jpeg" version = "0.4.20" diff --git a/Cargo.toml b/Cargo.toml index acda2ac..b9bc246 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ url = "2" colors-transform = "0.2.11" rayon = "1.11.0" resvg = "0.45.1" +image = "0.25.8" # femtovg = { version = "0.16.0", features = ["wgpu"] } # wgpu = "26.0.1" # mupdf = "0.5.0" diff --git a/src/main.rs b/src/main.rs index 202205c..4faaa4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -142,6 +142,7 @@ enum Message { AddServiceItem(usize, ServiceItem), AddServiceItemDrop(usize), AppendServiceItem(ServiceItem), + AddService(Vec), } const HEADER_SPACE: u16 = 6; @@ -174,7 +175,7 @@ impl cosmic::Application for App { windows.push(core.main_window_id().unwrap()); } - let mut items = match read_to_string(input.file) { + let items = match read_to_string(input.file) { Ok(lisp) => { let mut service_items = vec![]; let lisp = crisp::reader::read(&lisp); @@ -200,23 +201,23 @@ impl cosmic::Application for App { } }; - let items: Vec = items - .into_par_iter() - .map(|mut item| { - item.slides = item - .slides - .into_par_iter() - .map(|mut slide| { - text_svg::text_svg_generator( - &mut slide, - Arc::clone(&fontdb), - ); - slide - }) - .collect(); - item - }) - .collect(); + // let items: Vec = items + // .into_par_iter() + // .map(|mut item| { + // item.slides = item + // .slides + // .into_par_iter() + // .map(|mut slide| { + // text_svg::text_svg_generator( + // &mut slide, + // Arc::clone(&fontdb), + // ); + // slide + // }) + // .collect(); + // item + // }) + // .collect(); let presenter = Presenter::with_items(items.clone()); let song_editor = SongEditor::new(Arc::clone(&fontdb)); @@ -230,7 +231,7 @@ impl cosmic::Application for App { presenter, core, nav_model, - service: items, + service: items.clone(), file: PathBuf::default(), windows, presentation_open: false, @@ -243,7 +244,7 @@ impl cosmic::Application for App { searching: false, current_item: (0, 0), library_dragged_item: None, - fontdb, + fontdb: Arc::clone(&fontdb), }; let mut batch = vec![]; @@ -257,7 +258,7 @@ impl cosmic::Application for App { }; batch.push(app.add_library()); - // batch.push(app.add_service(items)); + batch.push(app.add_service(items, Arc::clone(&fontdb))); let batch = Task::batch(batch); (app, batch) } @@ -944,6 +945,10 @@ impl cosmic::Application for App { self.library = Some(library); Task::none() } + Message::AddService(service) => { + self.service = service; + Task::none() + } Message::None => Task::none(), Message::DndLeave(entity) => { // debug!(?entity); @@ -1182,28 +1187,35 @@ where }) } - // fn add_service( - // &mut self, - // items: Vec, - // ) -> Task { - // Task::perform( - // async move { - // for item in items { - // debug!(?item, "Item to be appended"); - // let slides = item.to_slides().unwrap_or(vec![]); - // map.insert(item, slides); - // } - // let len = map.len(); - // debug!(len, "to be append: "); - // map - // }, - // |x| { - // let len = x.len(); - // debug!(len, "to append: "); - // cosmic::Action::App(Message::AppendService(x)) - // }, - // ) - // } + fn add_service( + &mut self, + items: Vec, + fontdb: Arc, + ) -> Task { + Task::perform( + async move { + let items: Vec = items + .into_par_iter() + .map(|mut item| { + item.slides = item + .slides + .into_par_iter() + .map(|mut slide| { + text_svg::text_svg_generator( + &mut slide, + Arc::clone(&fontdb), + ); + slide + }) + .collect(); + item + }) + .collect(); + items + }, + |x| cosmic::Action::App(Message::AddService(x)), + ) + } fn process_key_press( &mut self, diff --git a/src/ui/presenter.rs b/src/ui/presenter.rs index 183208d..9dfa8b1 100644 --- a/src/ui/presenter.rs +++ b/src/ui/presenter.rs @@ -812,7 +812,6 @@ pub(crate) fn slide_view( let text: Element = if let Some(text) = &slide.text_svg { if let Some(handle) = &text.handle { - debug!("we made it boys"); Image::new(handle) .content_fit(ContentFit::Cover) .width(Length::Fill) diff --git a/src/ui/text_svg.rs b/src/ui/text_svg.rs index a70055f..3d12330 100644 --- a/src/ui/text_svg.rs +++ b/src/ui/text_svg.rs @@ -242,6 +242,7 @@ impl TextSvg { } pub fn build(mut self) -> Self { + debug!("starting..."); let shadow = if let Some(shadow) = &self.shadow { format!("", shadow.offset_x, @@ -260,12 +261,12 @@ impl TextSvg { "".into() }; let size = Size::new(3840.0, 2160.0); + let font_size = self.font.size as f32 * (size.width / 960.0); let total_lines = self.text.lines().count(); let half_lines = (total_lines / 2) as f32; let middle_position = size.height / 2.0; let line_spacing = 10.0; - let text_and_line_spacing = - self.font.size as f32 + line_spacing; + let text_and_line_spacing = font_size + line_spacing; let starting_y_position = middle_position - (half_lines * text_and_line_spacing); @@ -289,9 +290,9 @@ impl TextSvg { size.height, shadow, self.font.name, - self.font.size, + font_size, self.fill, stroke, text); - debug!("starting..."); + debug!("text string built..."); let resvg_tree = Tree::from_data( &final_svg.as_bytes(), &resvg::usvg::Options { @@ -314,9 +315,13 @@ impl TextSvg { // &self.text.lines().next().unwrap().trim_end(); // path.push(PathBuf::from(file_title)); // path.set_extension("png"); - // let _ = pixmap.save_png(path); + // let _ = pixmap.save_png(&path); debug!("rendered"); - let handle = Handle::from_bytes(pixmap.take()); + let handle = Handle::from_rgba( + size.width as u32, + size.height as u32, + pixmap.take(), + ); self.handle = Some(handle); debug!("stored"); self diff --git a/todo.org b/todo.org index 427d985..2c7c574 100644 --- a/todo.org +++ b/todo.org @@ -16,6 +16,7 @@ Actually, what if we just made the svg at load/creation time and stored it in th ** SVG performs badly Since SVG's apparently run poorly in iced, instead I'll need to see about either creating a new text element, or teaching Iced to render strokes and shadows on text. + ** Fork Cryoglyph This fork will render text 3 times. Once for the text, once for the stroke, once for the shadow. This will only be used in the slides and therefore should not be much of a performance hit since we will only be render 3 copies of the given text. This should not be bad performance since it's not a large amount of text. From fae83aedc556e2e47ee51c4e080165001f6b8c9e Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Fri, 5 Sep 2025 15:26:14 -0500 Subject: [PATCH 11/11] trying to adjust the text_svg --- src/main.rs | 36 ++++++++++++++++++------------------ src/ui/presenter.rs | 6 +++--- src/ui/text_svg.rs | 36 ++++++++++++++++++++---------------- test_presentation.lisp | 4 ++-- test_song.lisp | 2 +- 5 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4faaa4f..5725086 100644 --- a/src/main.rs +++ b/src/main.rs @@ -201,23 +201,23 @@ impl cosmic::Application for App { } }; - // let items: Vec = items - // .into_par_iter() - // .map(|mut item| { - // item.slides = item - // .slides - // .into_par_iter() - // .map(|mut slide| { - // text_svg::text_svg_generator( - // &mut slide, - // Arc::clone(&fontdb), - // ); - // slide - // }) - // .collect(); - // item - // }) - // .collect(); + let items: Vec = items + .into_par_iter() + .map(|mut item| { + item.slides = item + .slides + .into_par_iter() + .map(|mut slide| { + text_svg::text_svg_generator( + &mut slide, + Arc::clone(&fontdb), + ); + slide + }) + .collect(); + item + }) + .collect(); let presenter = Presenter::with_items(items.clone()); let song_editor = SongEditor::new(Arc::clone(&fontdb)); @@ -258,7 +258,7 @@ impl cosmic::Application for App { }; batch.push(app.add_library()); - batch.push(app.add_service(items, Arc::clone(&fontdb))); + // batch.push(app.add_service(items, Arc::clone(&fontdb))); let batch = Task::batch(batch); (app, batch) } diff --git a/src/ui/presenter.rs b/src/ui/presenter.rs index 9dfa8b1..86d7bc7 100644 --- a/src/ui/presenter.rs +++ b/src/ui/presenter.rs @@ -812,10 +812,10 @@ pub(crate) fn slide_view( let text: Element = if let Some(text) = &slide.text_svg { if let Some(handle) = &text.handle { - Image::new(handle) + image(handle) .content_fit(ContentFit::Cover) - .width(Length::Fill) - .height(Length::Fill) + .width(width) + .height(size.height) .into() } else { Space::with_width(0).into() diff --git a/src/ui/text_svg.rs b/src/ui/text_svg.rs index 3d12330..3ac16ad 100644 --- a/src/ui/text_svg.rs +++ b/src/ui/text_svg.rs @@ -243,6 +243,21 @@ impl TextSvg { pub fn build(mut self) -> Self { debug!("starting..."); + + let mut path = dirs::data_local_dir().unwrap(); + path.push(PathBuf::from("lumina")); + path.push(PathBuf::from("temp")); + let file_title = + &self.text.lines().next().unwrap().trim_end(); + path.push(PathBuf::from(file_title)); + path.set_extension("png"); + + if path.exists() { + debug!("cached"); + let handle = Handle::from_path(path); + self.handle = Some(handle); + return self; + } let shadow = if let Some(shadow) = &self.shadow { format!("", shadow.offset_x, @@ -260,8 +275,8 @@ impl TextSvg { } else { "".into() }; - let size = Size::new(3840.0, 2160.0); - let font_size = self.font.size as f32 * (size.width / 960.0); + let size = Size::new(1920.0, 1080.0); + let font_size = self.font.size as f32; let total_lines = self.text.lines().count(); let half_lines = (total_lines / 2) as f32; let middle_position = size.height / 2.0; @@ -307,21 +322,10 @@ impl TextSvg { Pixmap::new(size.width as u32, size.height as u32) .expect("opops"); resvg::render(&resvg_tree, transform, &mut pixmap.as_mut()); - // debug!(?pixmap); - // let mut path = dirs::data_local_dir().unwrap(); - // path.push(PathBuf::from("lumina")); - // path.push(PathBuf::from("temp")); - // let file_title = - // &self.text.lines().next().unwrap().trim_end(); - // path.push(PathBuf::from(file_title)); - // path.set_extension("png"); - // let _ = pixmap.save_png(&path); + let _ = pixmap.save_png(&path); + debug!("rendered"); - let handle = Handle::from_rgba( - size.width as u32, - size.height as u32, - pixmap.take(), - ); + let handle = Handle::from_path(path); self.handle = Some(handle); debug!("stored"); self diff --git a/test_presentation.lisp b/test_presentation.lisp index 553f254..37919e7 100644 --- a/test_presentation.lisp +++ b/test_presentation.lisp @@ -1,10 +1,10 @@ (slide :background (image :source "~/pics/frodo.jpg" :fit fill) - (text "This is frodo" :font-size 90)) + (text "This is frodo" :font-size 140)) (slide (video :source "~/vids/test/camprules2024.mp4" :fit contain)) (slide (video :source "~/vids/never give up.mkv" :fit contain)) (slide (video :source "~/vids/The promise of Rust.mkv" :fit contain)) (song :id 7 :author "North Point Worship" - :font "Quicksand Bold" :font-size 60 + :font "Quicksand" :font-size 140 :shadow "" :stroke "" :title "Death Was Arrested" :background (image :source "file:///home/chris/nc/tfc/openlp/CMG - Bright Mountains 01.jpg" :fit cover) diff --git a/test_song.lisp b/test_song.lisp index ccee693..a0705c8 100644 --- a/test_song.lisp +++ b/test_song.lisp @@ -1,5 +1,5 @@ (song :id 7 :author "North Point Worship" - :font "Quicksand Bold" :font-size 60 + :font "Quicksand" :font-size 140 :title "Death Was Arrested" :background (image :source "~/nc/tfc/openlp/CMG - Bright Mountains 01.jpg" :fit cover) :text-alignment center