updating and fixing some small performance issues
Some checks are pending
/ test (push) Waiting to run
Some checks are pending
/ test (push) Waiting to run
This commit is contained in:
parent
fd94f1dfa6
commit
a06890d9e1
10 changed files with 911 additions and 712 deletions
1001
Cargo.lock
generated
1001
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -8,7 +8,7 @@ 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", "winit_wgpu", "tokio", "rfd", "dbus-config", "a11y", "wgpu", "multi-window"] }
|
||||
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"
|
||||
|
@ -29,7 +29,8 @@ gstreamer-app = "0.23.3"
|
|||
# cosmic-time = "0.2.0"
|
||||
url = "2"
|
||||
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"
|
||||
# rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false }
|
||||
|
||||
|
@ -39,7 +40,8 @@ branch = "cosmic"
|
|||
features = ["wgpu"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
|
76
flake.lock
generated
76
flake.lock
generated
|
@ -6,11 +6,33 @@
|
|||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1745303921,
|
||||
"narHash": "sha256-zYucemS2QvJUR5GKJ/u3eZAoe82AKhcxMtNVZDERXsw=",
|
||||
"lastModified": 1754980813,
|
||||
"narHash": "sha256-Wr9ei2V4rfr3HR5eJUA7pjMIrHH5o4DtWazQC5UwxHA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "14850d5984f3696a2972f85f19085e5fb46daa95",
|
||||
"rev": "a1ce805b08279ee4e697b47aa3aa28fe2b335de6",
|
||||
"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"
|
||||
},
|
||||
"original": {
|
||||
|
@ -39,14 +61,15 @@
|
|||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"fenix": "fenix_2",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743800763,
|
||||
"narHash": "sha256-YFKV+fxEpMgP5VsUcM6Il28lI0NlpM7+oB1XxbBAYCw=",
|
||||
"lastModified": 1752689277,
|
||||
"narHash": "sha256-uldUBFkZe/E7qbvxa3mH1ItrWZyT6w1dBKJQF/3ZSsc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "ed0232117731a4c19d3ee93aa0c382a8fe754b01",
|
||||
"rev": "0e72363d0938b0208d6c646d10649164c43f4d64",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -57,11 +80,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1744932701,
|
||||
"narHash": "sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU=",
|
||||
"lastModified": 1754725699,
|
||||
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef",
|
||||
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -73,11 +96,11 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1744868846,
|
||||
"narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
|
||||
"lastModified": 1752077645,
|
||||
"narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
|
||||
"rev": "be9e214982e20b8310878ac2baa063a961c1bdf6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -89,11 +112,11 @@
|
|||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1745234285,
|
||||
"narHash": "sha256-GfpyMzxwkfgRVN0cTGQSkTC0OHhEkv3Jf6Tcjm//qZ0=",
|
||||
"lastModified": 1754725699,
|
||||
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c11863f1e964833214b767f4a369c6e6a7aba141",
|
||||
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -114,11 +137,28 @@
|
|||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1745247864,
|
||||
"narHash": "sha256-QA1Ba8Flz5K+0GbG03HwiX9t46mh/jjKgwavbuKtwMg=",
|
||||
"lastModified": 1754926538,
|
||||
"narHash": "sha256-fuHLsvM5z5/5ia3yL0/mr472wXnxSrtXECa+pspQchA=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "31dbec70c68e97060916d4754c687a3e93c2440f",
|
||||
"rev": "9db05508ed08a4c952017769b45b57c4ad505872",
|
||||
"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"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -9,6 +9,8 @@ use std::{
|
|||
};
|
||||
use tracing::error;
|
||||
|
||||
use crate::ui::text_svg::{self, TextSvg};
|
||||
|
||||
use super::songs::Song;
|
||||
|
||||
#[derive(
|
||||
|
@ -234,6 +236,8 @@ pub struct Slide {
|
|||
video_loop: bool,
|
||||
video_start_time: f32,
|
||||
video_end_time: f32,
|
||||
#[serde(skip)]
|
||||
pub text_svg: TextSvg,
|
||||
}
|
||||
|
||||
impl From<&Slide> for Value {
|
||||
|
@ -498,6 +502,8 @@ pub struct SlideBuilder {
|
|||
video_loop: Option<bool>,
|
||||
video_start_time: Option<f32>,
|
||||
video_end_time: Option<f32>,
|
||||
#[serde(skip)]
|
||||
text_svg: Option<TextSvg>,
|
||||
}
|
||||
|
||||
impl SlideBuilder {
|
||||
|
@ -571,6 +577,14 @@ impl SlideBuilder {
|
|||
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> {
|
||||
let Some(background) = self.background else {
|
||||
return Err(miette!("No background"));
|
||||
|
@ -596,6 +610,7 @@ 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,
|
||||
|
@ -606,8 +621,34 @@ impl SlideBuilder {
|
|||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
90
src/main.rs
90
src/main.rs
|
@ -72,7 +72,10 @@ fn main() -> Result<()> {
|
|||
let settings;
|
||||
if args.ui {
|
||||
debug!("main view");
|
||||
settings = Settings::default().debug(false).is_daemon(true);
|
||||
settings = Settings::default()
|
||||
.debug(false)
|
||||
.is_daemon(true)
|
||||
.transparent(true);
|
||||
} else {
|
||||
debug!("window view");
|
||||
settings = Settings::default()
|
||||
|
@ -105,6 +108,7 @@ struct App {
|
|||
library_width: f32,
|
||||
editor_mode: Option<EditorMode>,
|
||||
song_editor: SongEditor,
|
||||
searching: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -126,6 +130,7 @@ enum Message {
|
|||
None,
|
||||
DndLeave(Entity),
|
||||
EditorToggle(bool),
|
||||
SearchFocus,
|
||||
}
|
||||
|
||||
const HEADER_SPACE: u16 = 6;
|
||||
|
@ -203,6 +208,7 @@ impl cosmic::Application for App {
|
|||
library_width: 60.0,
|
||||
editor_mode: None,
|
||||
song_editor,
|
||||
searching: false,
|
||||
};
|
||||
|
||||
let mut batch = vec![];
|
||||
|
@ -243,18 +249,18 @@ impl cosmic::Application for App {
|
|||
debug!("left");
|
||||
cosmic::Action::App(Message::DndLeave(entity))
|
||||
})
|
||||
.drag_id(DragId::new())
|
||||
.on_context(|id| {
|
||||
cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::NavBarContext(id),
|
||||
)
|
||||
})
|
||||
.on_dnd_drop::<ServiceItem>(|entity, data, action| {
|
||||
debug!("dropped");
|
||||
cosmic::Action::App(Message::DndDrop(
|
||||
entity, data, action,
|
||||
))
|
||||
})
|
||||
.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
|
||||
|
@ -288,6 +294,25 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
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 text = if self.presentation_open {
|
||||
text::body("End Presentation")
|
||||
|
@ -295,7 +320,7 @@ impl cosmic::Application for App {
|
|||
text::body("Present")
|
||||
};
|
||||
|
||||
vec![
|
||||
let row = row![
|
||||
tooltip(
|
||||
button::custom(
|
||||
row!(
|
||||
|
@ -318,9 +343,7 @@ impl cosmic::Application for App {
|
|||
)),
|
||||
"Enter Edit Mode",
|
||||
TPosition::Bottom,
|
||||
)
|
||||
.into(),
|
||||
horizontal_space().width(HEADER_SPACE).into(),
|
||||
),
|
||||
tooltip(
|
||||
button::custom(
|
||||
row!(
|
||||
|
@ -351,33 +374,7 @@ impl cosmic::Application for App {
|
|||
}),
|
||||
"Start Presentation",
|
||||
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(
|
||||
button::custom(
|
||||
row!(
|
||||
|
@ -399,9 +396,10 @@ impl cosmic::Application for App {
|
|||
"Open Library",
|
||||
TPosition::Bottom,
|
||||
)
|
||||
.into(),
|
||||
horizontal_space().width(HEADER_SPACE).into(),
|
||||
]
|
||||
.spacing(HEADER_SPACE)
|
||||
.into();
|
||||
vec![row]
|
||||
}
|
||||
|
||||
fn footer(&self) -> Option<Element<Self::Message>> {
|
||||
|
@ -466,6 +464,14 @@ impl cosmic::Application for App {
|
|||
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> {
|
||||
match message {
|
||||
Message::Key(key, modifiers) => {
|
||||
|
@ -658,6 +664,10 @@ impl cosmic::Application for App {
|
|||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::SearchFocus => {
|
||||
self.searching = true;
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ pub mod presenter;
|
|||
pub mod song_editor;
|
||||
pub mod text_svg;
|
||||
pub mod video;
|
||||
pub mod widgets;
|
||||
|
||||
pub enum EditorMode {
|
||||
Song,
|
||||
|
|
|
@ -33,6 +33,7 @@ use url::Url;
|
|||
use crate::{
|
||||
core::{service_items::ServiceItemModel, slide::Slide},
|
||||
ui::text_svg::{self, Font as SvgFont},
|
||||
// ui::widgets::slide_text,
|
||||
BackgroundKind,
|
||||
};
|
||||
|
||||
|
@ -88,7 +89,7 @@ impl Presenter {
|
|||
gst::init().into_diagnostic()?;
|
||||
|
||||
let pipeline = format!(
|
||||
r#"playbin uri="{}" video-sink="videoscale ! videoconvert ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1""#,
|
||||
r#"playbin uri="{}" video-sink="videoscale ! videoconvert ! appsink name=lumina_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1""#,
|
||||
url.as_str()
|
||||
);
|
||||
|
||||
|
@ -108,13 +109,14 @@ impl Presenter {
|
|||
.unwrap()
|
||||
.downcast::<gst::Bin>()
|
||||
.unwrap();
|
||||
let video_sink = bin.by_name("iced_video").unwrap();
|
||||
let video_sink = bin.by_name("lumina_video").unwrap();
|
||||
let video_sink =
|
||||
video_sink.downcast::<gst_app::AppSink>().unwrap();
|
||||
let result =
|
||||
Video::from_gst_pipeline(pipeline, video_sink, None);
|
||||
result.into_diagnostic()
|
||||
}
|
||||
|
||||
pub fn with_items(items: ServiceItemModel) -> Self {
|
||||
let slides = items.to_slides().unwrap_or_default();
|
||||
let video = {
|
||||
|
@ -545,21 +547,13 @@ pub(crate) fn slide_view(
|
|||
) -> Element<'_, Message> {
|
||||
responsive(move |size| {
|
||||
let width = size.height * 16.0 / 9.0;
|
||||
let font_size = scale_font(slide.font_size() as f32, width);
|
||||
let slide_text = slide.text();
|
||||
|
||||
// SVG based
|
||||
// let font = SvgFont::from(font).size(font_size.floor() as u8);
|
||||
// let text = text_svg::TextSvg::new()
|
||||
// .text(&slide_text)
|
||||
// .fill("#fff")
|
||||
// .shadow(text_svg::shadow(2, 2, 5, "#000000"))
|
||||
// .stroke(text_svg::stroke(3, "#000"))
|
||||
// .font(font)
|
||||
// .view()
|
||||
// .map(|m| Message::None);
|
||||
|
||||
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<Element<Message>> = lines
|
||||
.map(|t| {
|
||||
|
@ -590,30 +584,52 @@ pub(crate) fn slide_view(
|
|||
})
|
||||
.collect();
|
||||
let text = Column::with_children(text).spacing(26);
|
||||
|
||||
Container::new(text)
|
||||
.center(Length::Fill)
|
||||
.align_x(Horizontal::Left)
|
||||
} else {
|
||||
// SVG based
|
||||
let text = slide.text_svg.view().map(|m| Message::None);
|
||||
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 stroke_text: Vec<Element<Message>> = lines
|
||||
// let text: Vec<Element<Message>> = lines
|
||||
// .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)
|
||||
// .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 stroke_text =
|
||||
// Column::with_children(stroke_text).spacing(26);
|
||||
|
||||
//Next
|
||||
let text_container = Container::new(text)
|
||||
.center(Length::Fill)
|
||||
.align_x(Horizontal::Left);
|
||||
// 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)
|
||||
|
@ -648,11 +664,10 @@ pub(crate) fn slide_view(
|
|||
Color::BLACK,
|
||||
))
|
||||
})
|
||||
.center_x(width)
|
||||
.center_y(size.height)
|
||||
.center(Length::Fill)
|
||||
.clip(true)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.width(width)
|
||||
.height(size.height)
|
||||
} else if let Some(video) = &video {
|
||||
Container::new(
|
||||
VideoPlayer::new(video)
|
||||
|
|
|
@ -7,7 +7,7 @@ use colors_transform::Rgb;
|
|||
use cosmic::{
|
||||
iced::{
|
||||
font::{Style, Weight},
|
||||
Length,
|
||||
Length, Size,
|
||||
},
|
||||
prelude::*,
|
||||
widget::{container, lazy, responsive, svg::Handle, Svg},
|
||||
|
@ -61,6 +61,15 @@ 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 {
|
||||
fn from(value: &str) -> Self {
|
||||
Self {
|
||||
|
@ -168,8 +177,9 @@ pub enum Message {
|
|||
}
|
||||
|
||||
impl TextSvg {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(text: impl Into<String>) -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -206,7 +216,7 @@ impl TextSvg {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn view<'a>(self) -> Element<'a, Message> {
|
||||
pub fn build(mut self) -> Self {
|
||||
let shadow = if let Some(shadow) = &self.shadow {
|
||||
format!("<filter id=\"shadow\"><feDropShadow dx=\"{}\" dy=\"{}\" stdDeviation=\"{}\" flood-color=\"{}\"/></filter>",
|
||||
shadow.offset_x,
|
||||
|
@ -224,38 +234,58 @@ impl TextSvg {
|
|||
} else {
|
||||
"".into()
|
||||
};
|
||||
container(
|
||||
responsive(move |s| {
|
||||
let size = Size::new(640.0, 360.0);
|
||||
let total_lines = self.text.lines().count();
|
||||
let half_lines = (total_lines / 2) as f32;
|
||||
let middle_position = s.height / 2.0;
|
||||
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 starting_y_position = middle_position - (half_lines * text_and_line_spacing);
|
||||
let text_and_line_spacing =
|
||||
self.font.size as f32 + line_spacing;
|
||||
let starting_y_position =
|
||||
middle_position - (half_lines * text_and_line_spacing);
|
||||
|
||||
let text_pieces: Vec<String> = self.text.lines()
|
||||
let text_pieces: Vec<String> = self
|
||||
.text
|
||||
.lines()
|
||||
.enumerate()
|
||||
.map(|(index, text)| {
|
||||
format!("<tspan x=\"50%\" y=\"{}\">{}</tspan>", starting_y_position + (index as f32 * text_and_line_spacing), text)
|
||||
}).collect();
|
||||
format!(
|
||||
"<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>",
|
||||
s.width,
|
||||
s.height,
|
||||
size.width,
|
||||
size.height,
|
||||
shadow,
|
||||
self.font.name,
|
||||
self.font.size,
|
||||
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
|
||||
}
|
||||
|
||||
// debug!(final_svg);
|
||||
Svg::new(Handle::from_memory(
|
||||
Box::leak(<std::string::String as Clone>::clone(&final_svg).into_boxed_str()).as_bytes(),
|
||||
))
|
||||
pub fn view<'a>(&self) -> Element<'a, Message> {
|
||||
container(
|
||||
Svg::new(self.handle.clone().unwrap())
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
})).width(Length::Fill).height(Length::Fill).into()
|
||||
}
|
||||
|
||||
fn text_spans(&self) -> Vec<String> {
|
||||
|
|
1
src/ui/widgets/mod.rs
Normal file
1
src/ui/widgets/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
// pub mod slide_text;
|
|
@ -0,0 +1,116 @@
|
|||
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)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue