Compare commits
No commits in common. "a06890d9e1e7d26320458de68b943f50781e2187" and "79ae0dac0fd7eed435e4896fcd6a237b5d53cd28" have entirely different histories.
a06890d9e1
...
79ae0dac0f
10 changed files with 726 additions and 920 deletions
1003
Cargo.lock
generated
1003
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", "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"
|
||||
miette = { version = "7.2.0", features = ["fancy"] }
|
||||
pretty_assertions = "1.4.1"
|
||||
|
@ -29,8 +29,7 @@ gstreamer-app = "0.23.3"
|
|||
# cosmic-time = "0.2.0"
|
||||
url = "2"
|
||||
colors-transform = "0.2.11"
|
||||
# femtovg = { version = "0.16.0", features = ["wgpu"] }
|
||||
# wgpu = "26.0.1"
|
||||
femtovg = { version = "0.16.0", features = ["wgpu"] }
|
||||
# mupdf = "0.5.0"
|
||||
# rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false }
|
||||
|
||||
|
@ -40,8 +39,7 @@ branch = "cosmic"
|
|||
features = ["wgpu"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
opt-level = 0
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
|
76
flake.lock
generated
76
flake.lock
generated
|
@ -6,33 +6,11 @@
|
|||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1754980813,
|
||||
"narHash": "sha256-Wr9ei2V4rfr3HR5eJUA7pjMIrHH5o4DtWazQC5UwxHA=",
|
||||
"lastModified": 1745303921,
|
||||
"narHash": "sha256-zYucemS2QvJUR5GKJ/u3eZAoe82AKhcxMtNVZDERXsw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"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",
|
||||
"rev": "14850d5984f3696a2972f85f19085e5fb46daa95",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -61,15 +39,14 @@
|
|||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"fenix": "fenix_2",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1752689277,
|
||||
"narHash": "sha256-uldUBFkZe/E7qbvxa3mH1ItrWZyT6w1dBKJQF/3ZSsc=",
|
||||
"lastModified": 1743800763,
|
||||
"narHash": "sha256-YFKV+fxEpMgP5VsUcM6Il28lI0NlpM7+oB1XxbBAYCw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "0e72363d0938b0208d6c646d10649164c43f4d64",
|
||||
"rev": "ed0232117731a4c19d3ee93aa0c382a8fe754b01",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -80,11 +57,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1754725699,
|
||||
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=",
|
||||
"lastModified": 1744932701,
|
||||
"narHash": "sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054",
|
||||
"rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -96,11 +73,11 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1752077645,
|
||||
"narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=",
|
||||
"lastModified": 1744868846,
|
||||
"narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "be9e214982e20b8310878ac2baa063a961c1bdf6",
|
||||
"rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -112,11 +89,11 @@
|
|||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1754725699,
|
||||
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=",
|
||||
"lastModified": 1745234285,
|
||||
"narHash": "sha256-GfpyMzxwkfgRVN0cTGQSkTC0OHhEkv3Jf6Tcjm//qZ0=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054",
|
||||
"rev": "c11863f1e964833214b767f4a369c6e6a7aba141",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -137,28 +114,11 @@
|
|||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1754926538,
|
||||
"narHash": "sha256-fuHLsvM5z5/5ia3yL0/mr472wXnxSrtXECa+pspQchA=",
|
||||
"lastModified": 1745247864,
|
||||
"narHash": "sha256-QA1Ba8Flz5K+0GbG03HwiX9t46mh/jjKgwavbuKtwMg=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"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",
|
||||
"rev": "31dbec70c68e97060916d4754c687a3e93c2440f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -9,8 +9,6 @@ use std::{
|
|||
};
|
||||
use tracing::error;
|
||||
|
||||
use crate::ui::text_svg::{self, TextSvg};
|
||||
|
||||
use super::songs::Song;
|
||||
|
||||
#[derive(
|
||||
|
@ -236,8 +234,6 @@ 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 {
|
||||
|
@ -502,8 +498,6 @@ 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 {
|
||||
|
@ -577,14 +571,6 @@ 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"));
|
||||
|
@ -610,45 +596,18 @@ 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,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
110
src/main.rs
110
src/main.rs
|
@ -72,10 +72,7 @@ fn main() -> Result<()> {
|
|||
let settings;
|
||||
if args.ui {
|
||||
debug!("main view");
|
||||
settings = Settings::default()
|
||||
.debug(false)
|
||||
.is_daemon(true)
|
||||
.transparent(true);
|
||||
settings = Settings::default().debug(false).is_daemon(true);
|
||||
} else {
|
||||
debug!("window view");
|
||||
settings = Settings::default()
|
||||
|
@ -108,7 +105,6 @@ struct App {
|
|||
library_width: f32,
|
||||
editor_mode: Option<EditorMode>,
|
||||
song_editor: SongEditor,
|
||||
searching: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -130,7 +126,6 @@ enum Message {
|
|||
None,
|
||||
DndLeave(Entity),
|
||||
EditorToggle(bool),
|
||||
SearchFocus,
|
||||
}
|
||||
|
||||
const HEADER_SPACE: u16 = 6;
|
||||
|
@ -208,7 +203,6 @@ impl cosmic::Application for App {
|
|||
library_width: 60.0,
|
||||
editor_mode: None,
|
||||
song_editor,
|
||||
searching: false,
|
||||
};
|
||||
|
||||
let mut batch = vec![];
|
||||
|
@ -249,18 +243,18 @@ impl cosmic::Application for App {
|
|||
debug!("left");
|
||||
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())
|
||||
.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,
|
||||
))
|
||||
})
|
||||
.context_menu(None)
|
||||
.into_container()
|
||||
// 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>> {
|
||||
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")
|
||||
|
@ -320,7 +295,7 @@ impl cosmic::Application for App {
|
|||
text::body("Present")
|
||||
};
|
||||
|
||||
let row = row![
|
||||
vec![
|
||||
tooltip(
|
||||
button::custom(
|
||||
row!(
|
||||
|
@ -343,7 +318,9 @@ impl cosmic::Application for App {
|
|||
)),
|
||||
"Enter Edit Mode",
|
||||
TPosition::Bottom,
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
horizontal_space().width(HEADER_SPACE).into(),
|
||||
tooltip(
|
||||
button::custom(
|
||||
row!(
|
||||
|
@ -374,7 +351,33 @@ 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!(
|
||||
|
@ -396,10 +399,9 @@ 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>> {
|
||||
|
@ -464,14 +466,6 @@ 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) => {
|
||||
|
@ -593,16 +587,16 @@ impl cosmic::Application for App {
|
|||
Message::WindowOpened(id, _) => {
|
||||
debug!(?id, "Window opened");
|
||||
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?")
|
||||
{
|
||||
self.presentation_open = true;
|
||||
if let Some(video) = &mut self.presenter.video {
|
||||
video.set_muted(false);
|
||||
}
|
||||
window::change_mode(id, Mode::Fullscreen)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
|| 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;
|
||||
if let Some(video) = &mut self.presenter.video {
|
||||
video.set_muted(false);
|
||||
}
|
||||
window::change_mode(id, Mode::Fullscreen)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
Message::WindowClosed(id) => {
|
||||
warn!("Closing window: {id}");
|
||||
|
@ -664,10 +658,6 @@ impl cosmic::Application for App {
|
|||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::SearchFocus => {
|
||||
self.searching = true;
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ pub mod presenter;
|
|||
pub mod song_editor;
|
||||
pub mod text_svg;
|
||||
pub mod video;
|
||||
pub mod widgets;
|
||||
|
||||
pub enum EditorMode {
|
||||
Song,
|
||||
|
|
|
@ -33,7 +33,6 @@ use url::Url;
|
|||
use crate::{
|
||||
core::{service_items::ServiceItemModel, slide::Slide},
|
||||
ui::text_svg::{self, Font as SvgFont},
|
||||
// ui::widgets::slide_text,
|
||||
BackgroundKind,
|
||||
};
|
||||
|
||||
|
@ -89,7 +88,7 @@ impl Presenter {
|
|||
gst::init().into_diagnostic()?;
|
||||
|
||||
let pipeline = format!(
|
||||
r#"playbin uri="{}" video-sink="videoscale ! videoconvert ! appsink name=lumina_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1""#,
|
||||
r#"playbin uri="{}" video-sink="videoscale ! videoconvert ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1""#,
|
||||
url.as_str()
|
||||
);
|
||||
|
||||
|
@ -109,14 +108,13 @@ impl Presenter {
|
|||
.unwrap()
|
||||
.downcast::<gst::Bin>()
|
||||
.unwrap();
|
||||
let video_sink = bin.by_name("lumina_video").unwrap();
|
||||
let video_sink = bin.by_name("iced_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 = {
|
||||
|
@ -547,96 +545,90 @@ 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_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| {
|
||||
rich_text([span(format!("{}\n", t))
|
||||
.background(
|
||||
Background::Color(Color::BLACK)
|
||||
.scale_alpha(0.4),
|
||||
)
|
||||
.border(border::rounded(10))
|
||||
.padding(10)])
|
||||
// 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);
|
||||
|
||||
// text widget based
|
||||
let lines = slide_text.lines();
|
||||
let chars = lines
|
||||
.map(|t| t.chars().map(|c| format!("{}\n", c)))
|
||||
.collect();
|
||||
let mut spans = vec![];
|
||||
for c in chars {
|
||||
spans.push(
|
||||
span(format!("{}\n", c))
|
||||
.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)
|
||||
} 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 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)
|
||||
};
|
||||
.background(
|
||||
Background::Color(Color::BLACK)
|
||||
.scale_alpha(0.4),
|
||||
)
|
||||
.border(border::rounded(10))
|
||||
.padding(10),
|
||||
)
|
||||
}
|
||||
let text: Vec<Element<Message>> = lines
|
||||
.map(|t| {
|
||||
let chars = t
|
||||
.chars()
|
||||
.map(|c| {
|
||||
span(format!("{}\n", c))
|
||||
.size(font_size)
|
||||
.font(font)
|
||||
.background(
|
||||
Background::Color(Color::BLACK)
|
||||
.scale_alpha(0.4),
|
||||
)
|
||||
.border(border::rounded(10))
|
||||
.padding(10)
|
||||
})
|
||||
.collect();
|
||||
rich_text(chars).center().into()
|
||||
})
|
||||
.collect();
|
||||
let text = Column::with_children(text).spacing(26);
|
||||
|
||||
// let stroke_text_container = Container::new(stroke_text)
|
||||
// .center(Length::Fill)
|
||||
// .align_x(Horizontal::Left);
|
||||
let lines = slide_text.lines();
|
||||
let stroke_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)
|
||||
.border(border::rounded(10))
|
||||
.padding(10)])
|
||||
.center()
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
let stroke_text =
|
||||
Column::with_children(stroke_text).spacing(26);
|
||||
|
||||
// let text_stack =
|
||||
// stack!(stroke_text_container, text_container);
|
||||
//Next
|
||||
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))
|
||||
.style(|_| {
|
||||
container::background(Background::Color(Color::BLACK))
|
||||
|
@ -664,10 +656,11 @@ pub(crate) fn slide_view(
|
|||
Color::BLACK,
|
||||
))
|
||||
})
|
||||
.center(Length::Fill)
|
||||
.center_x(width)
|
||||
.center_y(size.height)
|
||||
.clip(true)
|
||||
.width(width)
|
||||
.height(size.height)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
} else if let Some(video) = &video {
|
||||
Container::new(
|
||||
VideoPlayer::new(video)
|
||||
|
@ -692,11 +685,8 @@ pub(crate) fn slide_view(
|
|||
}
|
||||
}
|
||||
};
|
||||
let stack = stack!(
|
||||
black,
|
||||
container.center(Length::Fill),
|
||||
text_container
|
||||
);
|
||||
let stack =
|
||||
stack!(black, container.center(Length::Fill), text_stack);
|
||||
Container::new(stack).center(Length::Fill).into()
|
||||
})
|
||||
.into()
|
||||
|
|
|
@ -7,7 +7,7 @@ use colors_transform::Rgb;
|
|||
use cosmic::{
|
||||
iced::{
|
||||
font::{Style, Weight},
|
||||
Length, Size,
|
||||
Length,
|
||||
},
|
||||
prelude::*,
|
||||
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 {
|
||||
fn from(value: &str) -> Self {
|
||||
Self {
|
||||
|
@ -177,9 +168,8 @@ pub enum Message {
|
|||
}
|
||||
|
||||
impl TextSvg {
|
||||
pub fn new(text: impl Into<String>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +206,7 @@ impl TextSvg {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn build(mut self) -> Self {
|
||||
pub fn view<'a>(self) -> Element<'a, Message> {
|
||||
let shadow = if let Some(shadow) = &self.shadow {
|
||||
format!("<filter id=\"shadow\"><feDropShadow dx=\"{}\" dy=\"{}\" stdDeviation=\"{}\" flood-color=\"{}\"/></filter>",
|
||||
shadow.offset_x,
|
||||
|
@ -234,58 +224,38 @@ impl TextSvg {
|
|||
} else {
|
||||
"".into()
|
||||
};
|
||||
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 = 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);
|
||||
container(
|
||||
responsive(move |s| {
|
||||
let total_lines = self.text.lines().count();
|
||||
let half_lines = (total_lines / 2) as f32;
|
||||
let middle_position = s.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_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();
|
||||
let text: String = text_pieces.join("\n");
|
||||
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();
|
||||
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>",
|
||||
size.width,
|
||||
size.height,
|
||||
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,
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
// debug!(final_svg);
|
||||
Svg::new(Handle::from_memory(
|
||||
Box::leak(<std::string::String as Clone>::clone(&final_svg).into_boxed_str()).as_bytes(),
|
||||
))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
})).width(Length::Fill).height(Length::Fill).into()
|
||||
}
|
||||
|
||||
fn text_spans(&self) -> Vec<String> {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
// pub mod slide_text;
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue