Compare commits

..

No commits in common. "a06890d9e1e7d26320458de68b943f50781e2187" and "79ae0dac0fd7eed435e4896fcd6a237b5d53cd28" have entirely different histories.

10 changed files with 726 additions and 920 deletions

1003
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ description = "A cli presentation system"
[dependencies] [dependencies]
clap = { version = "4.5.20", features = ["debug", "derive"] } 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"] } libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false, features = ["debug", "winit", "winit_wgpu", "tokio", "rfd", "dbus-config", "a11y", "wgpu", "multi-window"] }
lexpr = "0.2.7" lexpr = "0.2.7"
miette = { version = "7.2.0", features = ["fancy"] } miette = { version = "7.2.0", features = ["fancy"] }
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"
@ -29,8 +29,7 @@ gstreamer-app = "0.23.3"
# cosmic-time = "0.2.0" # cosmic-time = "0.2.0"
url = "2" url = "2"
colors-transform = "0.2.11" colors-transform = "0.2.11"
# femtovg = { version = "0.16.0", features = ["wgpu"] } femtovg = { version = "0.16.0", features = ["wgpu"] }
# wgpu = "26.0.1"
# mupdf = "0.5.0" # mupdf = "0.5.0"
# rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false } # rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false }
@ -40,8 +39,7 @@ branch = "cosmic"
features = ["wgpu"] features = ["wgpu"]
[profile.dev] [profile.dev]
opt-level = 3 opt-level = 0
[profile.release] [profile.release]
opt-level = 3 opt-level = 3
debug = true

76
flake.lock generated
View file

@ -6,33 +6,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1754980813, "lastModified": 1745303921,
"narHash": "sha256-Wr9ei2V4rfr3HR5eJUA7pjMIrHH5o4DtWazQC5UwxHA=", "narHash": "sha256-zYucemS2QvJUR5GKJ/u3eZAoe82AKhcxMtNVZDERXsw=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "a1ce805b08279ee4e697b47aa3aa28fe2b335de6", "rev": "14850d5984f3696a2972f85f19085e5fb46daa95",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"fenix_2": {
"inputs": {
"nixpkgs": [
"naersk",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src_2"
},
"locked": {
"lastModified": 1752475459,
"narHash": "sha256-z6QEu4ZFuHiqdOPbYss4/Q8B0BFhacR8ts6jO/F/aOU=",
"owner": "nix-community",
"repo": "fenix",
"rev": "bf0d6f70f4c9a9cf8845f992105652173f4b617f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -61,15 +39,14 @@
}, },
"naersk": { "naersk": {
"inputs": { "inputs": {
"fenix": "fenix_2",
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1752689277, "lastModified": 1743800763,
"narHash": "sha256-uldUBFkZe/E7qbvxa3mH1ItrWZyT6w1dBKJQF/3ZSsc=", "narHash": "sha256-YFKV+fxEpMgP5VsUcM6Il28lI0NlpM7+oB1XxbBAYCw=",
"owner": "nix-community", "owner": "nix-community",
"repo": "naersk", "repo": "naersk",
"rev": "0e72363d0938b0208d6c646d10649164c43f4d64", "rev": "ed0232117731a4c19d3ee93aa0c382a8fe754b01",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -80,11 +57,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1754725699, "lastModified": 1744932701,
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=", "narHash": "sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054", "rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -96,11 +73,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1752077645, "lastModified": 1744868846,
"narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=", "narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "be9e214982e20b8310878ac2baa063a961c1bdf6", "rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -112,11 +89,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1754725699, "lastModified": 1745234285,
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=", "narHash": "sha256-GfpyMzxwkfgRVN0cTGQSkTC0OHhEkv3Jf6Tcjm//qZ0=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054", "rev": "c11863f1e964833214b767f4a369c6e6a7aba141",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -137,28 +114,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1754926538, "lastModified": 1745247864,
"narHash": "sha256-fuHLsvM5z5/5ia3yL0/mr472wXnxSrtXECa+pspQchA=", "narHash": "sha256-QA1Ba8Flz5K+0GbG03HwiX9t46mh/jjKgwavbuKtwMg=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "9db05508ed08a4c952017769b45b57c4ad505872", "rev": "31dbec70c68e97060916d4754c687a3e93c2440f",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"rust-analyzer-src_2": {
"flake": false,
"locked": {
"lastModified": 1752428706,
"narHash": "sha256-EJcdxw3aXfP8Ex1Nm3s0awyH9egQvB2Gu+QEnJn2Sfg=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "591e3b7624be97e4443ea7b5542c191311aa141d",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -9,8 +9,6 @@ use std::{
}; };
use tracing::error; use tracing::error;
use crate::ui::text_svg::{self, TextSvg};
use super::songs::Song; use super::songs::Song;
#[derive( #[derive(
@ -236,8 +234,6 @@ pub struct Slide {
video_loop: bool, video_loop: bool,
video_start_time: f32, video_start_time: f32,
video_end_time: f32, video_end_time: f32,
#[serde(skip)]
pub text_svg: TextSvg,
} }
impl From<&Slide> for Value { impl From<&Slide> for Value {
@ -502,8 +498,6 @@ pub struct SlideBuilder {
video_loop: Option<bool>, video_loop: Option<bool>,
video_start_time: Option<f32>, video_start_time: Option<f32>,
video_end_time: Option<f32>, video_end_time: Option<f32>,
#[serde(skip)]
text_svg: Option<TextSvg>,
} }
impl SlideBuilder { impl SlideBuilder {
@ -577,14 +571,6 @@ impl SlideBuilder {
self self
} }
pub(crate) fn text_svg(
mut self,
text_svg: impl Into<TextSvg>,
) -> Self {
let _ = self.text_svg.insert(text_svg.into());
self
}
pub(crate) fn build(self) -> Result<Slide> { pub(crate) fn build(self) -> Result<Slide> {
let Some(background) = self.background else { let Some(background) = self.background else {
return Err(miette!("No background")); return Err(miette!("No background"));
@ -610,45 +596,18 @@ impl SlideBuilder {
let Some(video_end_time) = self.video_end_time else { let Some(video_end_time) = self.video_end_time else {
return Err(miette!("No video_end_time")); return Err(miette!("No video_end_time"));
}; };
if let Some(text_svg) = self.text_svg { Ok(Slide {
Ok(Slide { background,
background, text,
text, font,
font, font_size,
font_size, text_alignment,
text_alignment, audio: self.audio,
audio: self.audio, video_loop,
video_loop, video_start_time,
video_start_time, video_end_time,
video_end_time, ..Default::default()
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()
})
}
} }
} }

View file

@ -72,10 +72,7 @@ fn main() -> Result<()> {
let settings; let settings;
if args.ui { if args.ui {
debug!("main view"); debug!("main view");
settings = Settings::default() settings = Settings::default().debug(false).is_daemon(true);
.debug(false)
.is_daemon(true)
.transparent(true);
} else { } else {
debug!("window view"); debug!("window view");
settings = Settings::default() settings = Settings::default()
@ -108,7 +105,6 @@ struct App {
library_width: f32, library_width: f32,
editor_mode: Option<EditorMode>, editor_mode: Option<EditorMode>,
song_editor: SongEditor, song_editor: SongEditor,
searching: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -130,7 +126,6 @@ enum Message {
None, None,
DndLeave(Entity), DndLeave(Entity),
EditorToggle(bool), EditorToggle(bool),
SearchFocus,
} }
const HEADER_SPACE: u16 = 6; const HEADER_SPACE: u16 = 6;
@ -208,7 +203,6 @@ impl cosmic::Application for App {
library_width: 60.0, library_width: 60.0,
editor_mode: None, editor_mode: None,
song_editor, song_editor,
searching: false,
}; };
let mut batch = vec![]; let mut batch = vec![];
@ -249,18 +243,18 @@ impl cosmic::Application for App {
debug!("left"); debug!("left");
cosmic::Action::App(Message::DndLeave(entity)) cosmic::Action::App(Message::DndLeave(entity))
}) })
.on_dnd_drop::<ServiceItem>(|entity, data, action| {
debug!("dropped");
cosmic::Action::App(Message::DndDrop(
entity, data, action,
))
})
.drag_id(DragId::new()) .drag_id(DragId::new())
.on_context(|id| { .on_context(|id| {
cosmic::Action::Cosmic( cosmic::Action::Cosmic(
cosmic::app::Action::NavBarContext(id), cosmic::app::Action::NavBarContext(id),
) )
}) })
.on_dnd_drop::<ServiceItem>(|entity, data, action| {
debug!("dropped");
cosmic::Action::App(Message::DndDrop(
entity, data, action,
))
})
.context_menu(None) .context_menu(None)
.into_container() .into_container()
// XXX both must be shrink to avoid flex layout from ignoring it // XXX both must be shrink to avoid flex layout from ignoring it
@ -294,25 +288,6 @@ impl cosmic::Application for App {
} }
fn header_start(&self) -> Vec<Element<Self::Message>> { fn header_start(&self) -> Vec<Element<Self::Message>> {
vec![]
}
fn header_center(&self) -> Vec<Element<Self::Message>> {
vec![search_input("Search...", "")
.on_input(|_| Message::None)
.on_submit(|_| Message::None)
.on_focus(Message::SearchFocus)
.width(1200)
.into()]
}
fn header_end(&self) -> Vec<Element<Self::Message>> {
// let editor_toggle = toggler(self.editor_mode.is_some())
// .label("Editor")
// .spacing(10)
// .width(Length::Shrink)
// .on_toggle(Message::EditorToggle);
let presenter_window = self.windows.get(1); let presenter_window = self.windows.get(1);
let text = if self.presentation_open { let text = if self.presentation_open {
text::body("End Presentation") text::body("End Presentation")
@ -320,7 +295,7 @@ impl cosmic::Application for App {
text::body("Present") text::body("Present")
}; };
let row = row![ vec![
tooltip( tooltip(
button::custom( button::custom(
row!( row!(
@ -343,7 +318,9 @@ impl cosmic::Application for App {
)), )),
"Enter Edit Mode", "Enter Edit Mode",
TPosition::Bottom, TPosition::Bottom,
), )
.into(),
horizontal_space().width(HEADER_SPACE).into(),
tooltip( tooltip(
button::custom( button::custom(
row!( row!(
@ -374,7 +351,33 @@ impl cosmic::Application for App {
}), }),
"Start Presentation", "Start Presentation",
TPosition::Bottom, TPosition::Bottom,
), )
.into(),
horizontal_space().width(HEADER_SPACE).into(),
]
}
fn header_center(&self) -> Vec<Element<Self::Message>> {
vec![search_input("Search...", "")
.on_input(|_| Message::None)
.on_submit(|_| Message::None)
.width(300)
.into()]
}
fn header_end(&self) -> Vec<Element<Self::Message>> {
// let editor_toggle = toggler(self.editor_mode.is_some())
// .label("Editor")
// .spacing(10)
// .width(Length::Shrink)
// .on_toggle(Message::EditorToggle);
let presenter_window = self.windows.get(1);
let text = if self.presentation_open {
text::body("End Presentation")
} else {
text::body("Present")
};
vec![
tooltip( tooltip(
button::custom( button::custom(
row!( row!(
@ -396,10 +399,9 @@ impl cosmic::Application for App {
"Open Library", "Open Library",
TPosition::Bottom, TPosition::Bottom,
) )
.into(),
horizontal_space().width(HEADER_SPACE).into(),
] ]
.spacing(HEADER_SPACE)
.into();
vec![row]
} }
fn footer(&self) -> Option<Element<Self::Message>> { fn footer(&self) -> Option<Element<Self::Message>> {
@ -464,14 +466,6 @@ impl cosmic::Application for App {
None None
} }
fn dialog(&self) -> Option<Element<'_, Self::Message>> {
if self.searching {
Some(text("hello").into())
} else {
None
}
}
fn update(&mut self, message: Message) -> Task<Message> { fn update(&mut self, message: Message) -> Task<Message> {
match message { match message {
Message::Key(key, modifiers) => { Message::Key(key, modifiers) => {
@ -593,16 +587,16 @@ impl cosmic::Application for App {
Message::WindowOpened(id, _) => { Message::WindowOpened(id, _) => {
debug!(?id, "Window opened"); debug!(?id, "Window opened");
if self.cli_mode if self.cli_mode
|| id > self.core.main_window_id().expect("Cosmic core seems to be missing a main window, was this started in cli mode?") || id > self.core.main_window_id().expect("Cosmic core seems to be missing a main window, was this started in cli mode?")
{ {
self.presentation_open = true; self.presentation_open = true;
if let Some(video) = &mut self.presenter.video { if let Some(video) = &mut self.presenter.video {
video.set_muted(false); video.set_muted(false);
} }
window::change_mode(id, Mode::Fullscreen) window::change_mode(id, Mode::Fullscreen)
} else { } else {
Task::none() Task::none()
} }
} }
Message::WindowClosed(id) => { Message::WindowClosed(id) => {
warn!("Closing window: {id}"); warn!("Closing window: {id}");
@ -664,10 +658,6 @@ impl cosmic::Application for App {
} }
Task::none() Task::none()
} }
Message::SearchFocus => {
self.searching = true;
Task::none()
}
} }
} }

View file

@ -6,7 +6,6 @@ pub mod presenter;
pub mod song_editor; pub mod song_editor;
pub mod text_svg; pub mod text_svg;
pub mod video; pub mod video;
pub mod widgets;
pub enum EditorMode { pub enum EditorMode {
Song, Song,

View file

@ -33,7 +33,6 @@ use url::Url;
use crate::{ use crate::{
core::{service_items::ServiceItemModel, slide::Slide}, core::{service_items::ServiceItemModel, slide::Slide},
ui::text_svg::{self, Font as SvgFont}, ui::text_svg::{self, Font as SvgFont},
// ui::widgets::slide_text,
BackgroundKind, BackgroundKind,
}; };
@ -89,7 +88,7 @@ impl Presenter {
gst::init().into_diagnostic()?; gst::init().into_diagnostic()?;
let pipeline = format!( 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 ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1""#,
url.as_str() url.as_str()
); );
@ -109,14 +108,13 @@ impl Presenter {
.unwrap() .unwrap()
.downcast::<gst::Bin>() .downcast::<gst::Bin>()
.unwrap(); .unwrap();
let video_sink = bin.by_name("lumina_video").unwrap(); let video_sink = bin.by_name("iced_video").unwrap();
let video_sink = let video_sink =
video_sink.downcast::<gst_app::AppSink>().unwrap(); video_sink.downcast::<gst_app::AppSink>().unwrap();
let result = let result =
Video::from_gst_pipeline(pipeline, video_sink, None); Video::from_gst_pipeline(pipeline, video_sink, None);
result.into_diagnostic() result.into_diagnostic()
} }
pub fn with_items(items: ServiceItemModel) -> Self { pub fn with_items(items: ServiceItemModel) -> Self {
let slides = items.to_slides().unwrap_or_default(); let slides = items.to_slides().unwrap_or_default();
let video = { let video = {
@ -547,96 +545,90 @@ pub(crate) fn slide_view(
) -> Element<'_, Message> { ) -> Element<'_, Message> {
responsive(move |size| { responsive(move |size| {
let width = size.height * 16.0 / 9.0; let width = size.height * 16.0 / 9.0;
let font_size = scale_font(slide.font_size() as f32, width);
let slide_text = slide.text(); let slide_text = slide.text();
// SVG based
// let font = SvgFont::from(font).size(font_size.floor() as u8); // let font = SvgFont::from(font).size(font_size.floor() as u8);
let text_container = if delegate { // let text = text_svg::TextSvg::new()
// text widget based // .text(&slide_text)
let font_size = // .fill("#fff")
scale_font(slide.font_size() as f32, width); // .shadow(text_svg::shadow(2, 2, 5, "#000000"))
let lines = slide_text.lines(); // .stroke(text_svg::stroke(3, "#000"))
let text: Vec<Element<Message>> = lines // .font(font)
.map(|t| { // .view()
rich_text([span(format!("{}\n", t)) // .map(|m| Message::None);
.background(
Background::Color(Color::BLACK) // text widget based
.scale_alpha(0.4), let lines = slide_text.lines();
) let chars = lines
.border(border::rounded(10)) .map(|t| t.chars().map(|c| format!("{}\n", c)))
.padding(10)]) .collect();
let mut spans = vec![];
for c in chars {
spans.push(
span(format!("{}\n", c))
.size(font_size) .size(font_size)
.font(font) .font(font)
.center() .background(
.into() Background::Color(Color::BLACK)
// let chars: Vec<Span> = t .scale_alpha(0.4),
// .chars() )
// .map(|c| -> Span { .border(border::rounded(10))
// let character: String = format!("{}/n", c); .padding(10),
// span(character) )
// .size(font_size) }
// .font(font) let text: Vec<Element<Message>> = lines
// .background( .map(|t| {
// Background::Color(Color::BLACK) let chars = t
// .scale_alpha(0.4), .chars()
// ) .map(|c| {
// .border(border::rounded(10)) span(format!("{}\n", c))
// .padding(10) .size(font_size)
}) .font(font)
.collect(); .background(
let text = Column::with_children(text).spacing(26); Background::Color(Color::BLACK)
Container::new(text) .scale_alpha(0.4),
.center(Length::Fill) )
.align_x(Horizontal::Left) .border(border::rounded(10))
} else { .padding(10)
// SVG based })
let text = slide.text_svg.view().map(|m| Message::None); .collect();
Container::new(text) rich_text(chars).center().into()
.center(Length::Fill) })
.align_x(Horizontal::Left) .collect();
// text widget based let text = Column::with_children(text).spacing(26);
// let font_size =
// scale_font(slide.font_size() as f32, width);
// let lines = slide_text.lines();
// let text: Vec<Element<Message>> = 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<Span> = 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) let lines = slide_text.lines();
// .center(Length::Fill) let stroke_text: Vec<Element<Message>> = lines
// .align_x(Horizontal::Left); .map(|t| {
let mut stroke_font = font.clone();
stroke_font.stretch = Stretch::Condensed;
stroke_font.weight = Weight::Bold;
rich_text([span(format!("{}\n", t))
.size(font_size + 0.3)
.font(stroke_font)
.color(Color::BLACK)
.border(border::rounded(10))
.padding(10)])
.center()
.into()
})
.collect();
let stroke_text =
Column::with_children(stroke_text).spacing(26);
// let text_stack = //Next
// stack!(stroke_text_container, text_container); let text_container = Container::new(text)
.center(Length::Fill)
.align_x(Horizontal::Left);
let stroke_text_container = Container::new(stroke_text)
.center(Length::Fill)
.align_x(Horizontal::Left);
let text_stack =
stack!(stroke_text_container, text_container);
let black = Container::new(Space::new(0, 0)) let black = Container::new(Space::new(0, 0))
.style(|_| { .style(|_| {
container::background(Background::Color(Color::BLACK)) container::background(Background::Color(Color::BLACK))
@ -664,10 +656,11 @@ pub(crate) fn slide_view(
Color::BLACK, Color::BLACK,
)) ))
}) })
.center(Length::Fill) .center_x(width)
.center_y(size.height)
.clip(true) .clip(true)
.width(width) .width(Length::Fill)
.height(size.height) .height(Length::Fill)
} else if let Some(video) = &video { } else if let Some(video) = &video {
Container::new( Container::new(
VideoPlayer::new(video) VideoPlayer::new(video)
@ -692,11 +685,8 @@ pub(crate) fn slide_view(
} }
} }
}; };
let stack = stack!( let stack =
black, stack!(black, container.center(Length::Fill), text_stack);
container.center(Length::Fill),
text_container
);
Container::new(stack).center(Length::Fill).into() Container::new(stack).center(Length::Fill).into()
}) })
.into() .into()

View file

@ -7,7 +7,7 @@ use colors_transform::Rgb;
use cosmic::{ use cosmic::{
iced::{ iced::{
font::{Style, Weight}, font::{Style, Weight},
Length, Size, Length,
}, },
prelude::*, prelude::*,
widget::{container, lazy, responsive, svg::Handle, Svg}, widget::{container, lazy, responsive, svg::Handle, Svg},
@ -61,15 +61,6 @@ impl From<cosmic::font::Font> for Font {
} }
} }
impl From<String> for Font {
fn from(value: String) -> Self {
Self {
name: value,
..Default::default()
}
}
}
impl From<&str> for Font { impl From<&str> for Font {
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
Self { Self {
@ -177,9 +168,8 @@ pub enum Message {
} }
impl TextSvg { impl TextSvg {
pub fn new(text: impl Into<String>) -> Self { pub fn new() -> Self {
Self { Self {
text: text.into(),
..Default::default() ..Default::default()
} }
} }
@ -216,7 +206,7 @@ impl TextSvg {
self self
} }
pub fn build(mut self) -> Self { pub fn view<'a>(self) -> Element<'a, Message> {
let shadow = if let Some(shadow) = &self.shadow { let shadow = if let Some(shadow) = &self.shadow {
format!("<filter id=\"shadow\"><feDropShadow dx=\"{}\" dy=\"{}\" stdDeviation=\"{}\" flood-color=\"{}\"/></filter>", format!("<filter id=\"shadow\"><feDropShadow dx=\"{}\" dy=\"{}\" stdDeviation=\"{}\" flood-color=\"{}\"/></filter>",
shadow.offset_x, shadow.offset_x,
@ -234,58 +224,38 @@ impl TextSvg {
} else { } else {
"".into() "".into()
}; };
let size = Size::new(640.0, 360.0); container(
let total_lines = self.text.lines().count(); responsive(move |s| {
let half_lines = (total_lines / 2) as f32; let total_lines = self.text.lines().count();
let middle_position = size.height / 2.0; let half_lines = (total_lines / 2) as f32;
let line_spacing = 10.0; let middle_position = s.height / 2.0;
let text_and_line_spacing = let line_spacing = 10.0;
self.font.size as f32 + line_spacing; let text_and_line_spacing = self.font.size as f32 + line_spacing;
let starting_y_position = let starting_y_position = middle_position - (half_lines * text_and_line_spacing);
middle_position - (half_lines * text_and_line_spacing);
let text_pieces: Vec<String> = self let text_pieces: Vec<String> = self.text.lines()
.text .enumerate()
.lines() .map(|(index, text)| {
.enumerate() format!("<tspan x=\"50%\" y=\"{}\">{}</tspan>", starting_y_position + (index as f32 * text_and_line_spacing), text)
.map(|(index, text)| { }).collect();
format!( let text: String = text_pieces.join("\n");
"<tspan x=\"50%\" y=\"{}\">{}</tspan>",
starting_y_position
+ (index as f32 * text_and_line_spacing),
text
)
})
.collect();
let text: String = text_pieces.join("\n");
let final_svg = format!("<svg viewBox=\"0 0 {} {}\" xmlns=\"http://www.w3.org/2000/svg\"><defs>{}</defs><text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-weight=\"bold\" font-family=\"{}\" font-size=\"{}\" fill=\"{}\" {} style=\"filter:url(#shadow);\">{}</text></svg>", let final_svg = format!("<svg viewBox=\"0 0 {} {}\" xmlns=\"http://www.w3.org/2000/svg\"><defs>{}</defs><text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-weight=\"bold\" font-family=\"{}\" font-size=\"{}\" fill=\"{}\" {} style=\"filter:url(#shadow);\">{}</text></svg>",
size.width, s.width,
size.height, s.height,
shadow, shadow,
self.font.name, self.font.name,
self.font.size, self.font.size,
self.fill, stroke, text); self.fill, stroke, text);
let handle = Handle::from_memory(
Box::leak(
<std::string::String as Clone>::clone(&final_svg)
.into_boxed_str(),
)
.as_bytes(),
);
self.handle = Some(handle);
self
}
pub fn view<'a>(&self) -> Element<'a, Message> { // debug!(final_svg);
container( Svg::new(Handle::from_memory(
Svg::new(self.handle.clone().unwrap()) Box::leak(<std::string::String as Clone>::clone(&final_svg).into_boxed_str()).as_bytes(),
.width(Length::Fill) ))
.height(Length::Fill), .width(Length::Fill)
) .height(Length::Fill)
.width(Length::Fill) .into()
.height(Length::Fill) })).width(Length::Fill).height(Length::Fill).into()
.into()
} }
fn text_spans(&self) -> Vec<String> { fn text_spans(&self) -> Vec<String> {

View file

@ -1 +0,0 @@
// pub mod slide_text;

View file

@ -1,116 +0,0 @@
use cosmic::iced::advanced::layout::{self, Layout};
use cosmic::iced::advanced::renderer;
use cosmic::iced::advanced::widget::{self, Widget};
use cosmic::iced::border;
use cosmic::iced::mouse;
use cosmic::iced::{Color, Element, Length, Rectangle, Size};
use femtovg::renderer::WGPURenderer;
use femtovg::{Canvas, TextContext};
pub struct SlideText {
text: String,
font_size: f32,
canvas: Canvas<WGPURenderer>,
}
impl SlideText {
pub async fn new(text: &str) -> Self {
let backends = wgpu::Backends::PRIMARY;
let instance =
wgpu::Instance::new(wgpu::InstanceDescriptor {
backends,
..Default::default()
});
let surface =
instance.create_surface(window.clone()).unwrap();
let adapter = cosmic::iced::wgpu::util::initialize_adapter_from_env_or_default(&instance, Some(&surface))
.await
.expect("Failed to find an appropriate adapter");
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: adapter.features(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::Performance,
},
None,
)
.await
.expect("failed to device it");
let renderer = WGPURenderer::new(device, queue);
let canvas =
Canvas::new_with_text_context(renderer, text_context)
.expect("oops femtovg");
Self {
text: text.to_owned(),
font_size: 50.0,
canvas,
}
}
}
fn get_canvas(text_context: TextContext) -> Canvas {
let renderer = WGPURenderer::new(device, queue);
Canvas::new_with_text_context(renderer, text_context)
.expect("oops femtovg")
}
pub fn slide_text(text: &str) -> SlideText {
SlideText::new(text)
}
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for SlideText
where
Renderer: renderer::Renderer,
{
fn size(&self) -> Size<Length> {
Size {
width: Length::Shrink,
height: Length::Shrink,
}
}
fn layout(
&self,
_tree: &mut widget::Tree,
_renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {
layout::Node::new(Size::new(
self.font_size * 2.0,
self.font_size * 2.0,
))
}
fn draw(
&self,
_state: &widget::Tree,
renderer: &mut Renderer,
_theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border: border::rounded(self.font_size),
..renderer::Quad::default()
},
Color::BLACK,
);
}
}
impl<Message, Theme, Renderer> From<SlideText>
for Element<'_, Message, Theme, Renderer>
where
Renderer: renderer::Renderer,
{
fn from(circle: SlideText) -> Self {
Self::new(circle)
}
}