lots of cleanup and some ui tweaks
Some checks are pending
/ test (push) Waiting to run

This commit is contained in:
Chris Cochrun 2025-09-23 06:25:25 -05:00
parent bb1057e950
commit 991feb18c8
11 changed files with 219 additions and 374 deletions

33
Cargo.lock generated
View file

@ -3888,27 +3888,6 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]]
name = "lexpr"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a84de6a9df442363b08f5dbf0cd5b92edc70097b89c4ce4bfea4679fe48bc67"
dependencies = [
"itoa",
"lexpr-macros",
"ryu",
]
[[package]]
name = "lexpr-macros"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36b5cb8bb985c81a8ac1a0f8b5c4865214f574ddd64397ef7a99c236e21f35bb"
dependencies = [
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.175" version = "0.2.175"
@ -4105,7 +4084,6 @@ dependencies = [
"gstreamer-app", "gstreamer-app",
"iced_video_player", "iced_video_player",
"image", "image",
"lexpr",
"libcosmic", "libcosmic",
"miette", "miette",
"mupdf", "mupdf",
@ -4117,7 +4095,6 @@ dependencies = [
"rodio", "rodio",
"ron 0.8.1", "ron 0.8.1",
"serde", "serde",
"serde-lexpr",
"sqlx", "sqlx",
"strum", "strum",
"strum_macros", "strum_macros",
@ -6223,16 +6200,6 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-lexpr"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb4cda13396159f59e7946118cdac0beadeecfb7cf76b197f4147e546f4ead6f"
dependencies = [
"lexpr",
"serde",
]
[[package]] [[package]]
name = "serde_core" name = "serde_core"
version = "1.0.225" version = "1.0.225"

View file

@ -8,11 +8,9 @@ description = "A cli presentation system"
[dependencies] [dependencies]
clap = { version = "4.5.20", features = ["debug", "derive"] } clap = { version = "4.5.20", features = ["debug", "derive"] }
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"
serde = { version = "1.0.213", features = ["derive"] } serde = { version = "1.0.213", features = ["derive"] }
serde-lexpr = "0.1.3"
tracing = "0.1.40" tracing = "0.1.40"
tracing-log = "0.2.0" tracing-log = "0.2.0"
tracing-subscriber = { version = "0.3.18", features = ["fmt", "std", "chrono", "time", "local-time", "env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["fmt", "std", "chrono", "time", "local-time", "env-filter"] }

View file

@ -8,6 +8,8 @@ build:
sbuild: sbuild:
RUST_LOG=debug sccache cargo build RUST_LOG=debug sccache cargo build
run: run:
RUST_LOG=debug cargo run -- {{ui}}
run-file:
RUST_LOG=debug cargo run -- {{ui}} {{file}} RUST_LOG=debug cargo run -- {{ui}} {{file}}
srun: srun:
RUST_LOG=debug sccache cargo run -- {{ui}} {{file}} RUST_LOG=debug sccache cargo run -- {{ui}} {{file}}
@ -20,5 +22,6 @@ profile:
alias b := build alias b := build
alias r := run alias r := run
alias rf := run-file
alias sr := srun alias sr := srun
alias c := clean alias c := clean

View file

@ -1,157 +0,0 @@
use lexpr::Value;
use strum_macros::EnumString;
#[derive(Debug, Clone, Default, PartialEq, Eq, EnumString)]
pub(crate) enum Symbol {
#[strum(ascii_case_insensitive)]
Slide,
#[strum(ascii_case_insensitive)]
Image,
#[strum(ascii_case_insensitive)]
Text,
#[strum(ascii_case_insensitive)]
Video,
#[strum(ascii_case_insensitive)]
Song,
#[strum(disabled)]
ImageFit(ImageFit),
#[strum(disabled)]
VerseOrder(VerseOrder),
#[strum(disabled)]
#[default]
None,
}
#[derive(Debug, Clone, PartialEq, Eq, EnumString)]
pub(crate) enum Keyword {
ImageFit(ImageFit),
}
#[derive(Debug, Default, Clone, PartialEq, Eq, EnumString)]
pub(crate) enum ImageFit {
#[strum(ascii_case_insensitive)]
#[default]
Cover,
#[strum(ascii_case_insensitive)]
Fill,
#[strum(ascii_case_insensitive)]
Crop,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, EnumString)]
pub(crate) enum VerseOrder {
#[strum(ascii_case_insensitive)]
#[default]
V1,
#[strum(ascii_case_insensitive)]
V2,
#[strum(ascii_case_insensitive)]
V3,
#[strum(ascii_case_insensitive)]
V4,
#[strum(ascii_case_insensitive)]
V5,
#[strum(ascii_case_insensitive)]
V6,
#[strum(ascii_case_insensitive)]
C1,
#[strum(ascii_case_insensitive)]
C2,
#[strum(ascii_case_insensitive)]
C3,
#[strum(ascii_case_insensitive)]
C4,
#[strum(ascii_case_insensitive)]
B1,
#[strum(ascii_case_insensitive)]
B2,
#[strum(ascii_case_insensitive)]
B3,
#[strum(ascii_case_insensitive)]
B4,
#[strum(ascii_case_insensitive)]
O1,
#[strum(ascii_case_insensitive)]
O2,
#[strum(ascii_case_insensitive)]
O3,
#[strum(ascii_case_insensitive)]
O4,
#[strum(ascii_case_insensitive)]
E1,
#[strum(ascii_case_insensitive)]
E2,
#[strum(ascii_case_insensitive)]
I1,
#[strum(ascii_case_insensitive)]
I2,
}
#[derive(Clone, Debug, PartialEq, Eq, EnumString)]
pub(crate) enum SongKeyword {
#[strum(ascii_case_insensitive)]
Title,
#[strum(ascii_case_insensitive)]
Author,
#[strum(ascii_case_insensitive)]
Ccli,
#[strum(ascii_case_insensitive)]
Audio,
#[strum(ascii_case_insensitive)]
Font,
#[strum(ascii_case_insensitive)]
FontSize,
#[strum(ascii_case_insensitive)]
Background,
#[strum(ascii_case_insensitive)]
VerseOrder(VerseOrder),
}
#[derive(Clone, Debug, PartialEq, Eq, EnumString)]
pub(crate) enum ImageKeyword {
#[strum(ascii_case_insensitive)]
Source,
#[strum(ascii_case_insensitive)]
Fit,
}
#[derive(Clone, Debug, Eq, PartialEq, EnumString)]
pub(crate) enum VideoKeyword {
#[strum(ascii_case_insensitive)]
Source,
#[strum(ascii_case_insensitive)]
Fit,
}
pub(crate) fn get_lists(exp: &Value) -> Vec<Value> {
if exp.is_cons() {
exp.as_cons().unwrap().to_vec().0
} else {
vec![]
}
}
#[cfg(test)]
mod test {
// #[test]
// fn test_list() {
// let lisp =
// read_to_string("./test_presentation.lisp").expect("oops");
// // println!("{lisp}");
// let mut parser =
// Parser::from_str_custom(&lisp, Options::elisp());
// for atom in parser.value_iter() {
// match atom {
// Ok(atom) => {
// // println!("{atom}");
// let lists = get_lists(&atom);
// assert_eq!(lists, vec![Value::Null])
// }
// Err(e) => {
// panic!("{e}");
// }
// }
// }
// }
}

View file

@ -1,7 +1,6 @@
pub mod content; pub mod content;
pub mod images; pub mod images;
pub mod kinds; pub mod kinds;
pub mod lisp;
pub mod model; pub mod model;
pub mod presentations; pub mod presentations;
pub mod service_items; pub mod service_items;

View file

@ -9,9 +9,11 @@ use cosmic::app::{Core, Settings, Task};
use cosmic::iced::alignment::Vertical; use cosmic::iced::alignment::Vertical;
use cosmic::iced::keyboard::{Key, Modifiers}; use cosmic::iced::keyboard::{Key, Modifiers};
use cosmic::iced::window::{Mode, Position}; use cosmic::iced::window::{Mode, Position};
use cosmic::iced::{self, event, window, Color, Length, Point}; use cosmic::iced::{
self, event, window, Background as IcedBackground, Border, Length,
};
use cosmic::iced_core::text::Wrapping;
use cosmic::iced_futures::Subscription; use cosmic::iced_futures::Subscription;
use cosmic::iced_runtime::dnd::DndAction;
use cosmic::iced_widget::{column, row, stack}; use cosmic::iced_widget::{column, row, stack};
use cosmic::theme; use cosmic::theme;
use cosmic::widget::dnd_destination::dnd_destination; use cosmic::widget::dnd_destination::dnd_destination;
@ -20,8 +22,8 @@ use cosmic::widget::menu::{ItemWidth, KeyBind};
use cosmic::widget::nav_bar::nav_bar_style; use cosmic::widget::nav_bar::nav_bar_style;
use cosmic::widget::tooltip::Position as TPosition; use cosmic::widget::tooltip::Position as TPosition;
use cosmic::widget::{ use cosmic::widget::{
button, dnd_source, horizontal_space, mouse_area, nav_bar, button, horizontal_space, mouse_area, nav_bar, responsive,
search_input, tooltip, vertical_space, RcElementWrapper, Space, search_input, tooltip, vertical_space, Space,
}; };
use cosmic::widget::{container, text}; use cosmic::widget::{container, text};
use cosmic::widget::{icon, slider}; use cosmic::widget::{icon, slider};
@ -58,7 +60,7 @@ struct Cli {
watch: bool, watch: bool,
#[arg(short = 'i', long)] #[arg(short = 'i', long)]
ui: bool, ui: bool,
file: PathBuf, file: Option<PathBuf>,
} }
fn main() -> Result<()> { fn main() -> Result<()> {
@ -111,6 +113,7 @@ struct App {
windows: Vec<window::Id>, windows: Vec<window::Id>,
service: Vec<ServiceItem>, service: Vec<ServiceItem>,
current_item: (usize, usize), current_item: (usize, usize),
hovered_item: Option<usize>,
presentation_open: bool, presentation_open: bool,
cli_mode: bool, cli_mode: bool,
library: Option<Library>, library: Option<Library>,
@ -134,7 +137,7 @@ enum Message {
File(PathBuf), File(PathBuf),
OpenWindow, OpenWindow,
CloseWindow(Option<window::Id>), CloseWindow(Option<window::Id>),
WindowOpened(window::Id, Option<Point>), WindowOpened(window::Id),
WindowClosed(window::Id), WindowClosed(window::Id),
AddLibrary(Library), AddLibrary(Library),
LibraryToggle, LibraryToggle,
@ -143,10 +146,10 @@ enum Message {
None, None,
EditorToggle(bool), EditorToggle(bool),
ChangeServiceItem(usize), ChangeServiceItem(usize),
HoveredServiceItem(Option<usize>),
AddServiceItem(usize, ServiceItem), AddServiceItem(usize, ServiceItem),
AddServiceItemDrop(usize), AddServiceItemDrop(usize),
AppendServiceItem(ServiceItem), AppendServiceItem(ServiceItem),
AddService(Vec<ServiceItem>),
SearchFocus, SearchFocus,
Search(String), Search(String),
CloseSearch, CloseSearch,
@ -213,7 +216,8 @@ impl cosmic::Application for App {
windows.push(core.main_window_id().unwrap()); windows.push(core.main_window_id().unwrap());
} }
let items = match read_to_string(input.file) { let items = if let Some(file) = input.file {
match read_to_string(file) {
Ok(lisp) => { Ok(lisp) => {
let mut service_items = vec![]; let mut service_items = vec![];
let lisp = crisp::reader::read(&lisp); let lisp = crisp::reader::read(&lisp);
@ -225,8 +229,10 @@ impl cosmic::Application for App {
// .collect(); // .collect();
// slide_vector.append(items); // slide_vector.append(items);
for value in vec { for value in vec {
let mut inner_vector = parse_lisp(value); let mut inner_vector =
service_items.append(&mut inner_vector); parse_lisp(value);
service_items
.append(&mut inner_vector);
} }
} }
_ => todo!(), _ => todo!(),
@ -237,6 +243,9 @@ impl cosmic::Application for App {
warn!("Missing file or could not read: {e}"); warn!("Missing file or could not read: {e}");
vec![] vec![]
} }
}
} else {
vec![]
}; };
let items: Vec<ServiceItem> = items let items: Vec<ServiceItem> = items
@ -308,6 +317,7 @@ impl cosmic::Application for App {
library_dragged_item: None, library_dragged_item: None,
fontdb: Arc::clone(&fontdb), fontdb: Arc::clone(&fontdb),
menu_keys, menu_keys,
hovered_item: None,
}; };
let mut batch = vec![]; let mut batch = vec![];
@ -530,23 +540,33 @@ impl cosmic::Application for App {
debug!("Closing window"); debug!("Closing window");
Some(Message::CloseWindow(Some(id))) Some(Message::CloseWindow(Some(id)))
} }
window::Event::Opened { window::Event::Opened { .. } => {
position, ..
} => {
debug!(?window_event, ?id); debug!(?window_event, ?id);
Some(Message::WindowOpened(id, position)) Some(Message::WindowOpened(id))
} }
window::Event::Closed => { window::Event::Closed => {
debug!("Closed window"); debug!("Closed window");
Some(Message::WindowClosed(id)) Some(Message::WindowClosed(id))
} }
window::Event::FileHovered(file) => {
debug!(?file);
None
}
window::Event::FileDropped(file) => {
debug!(?file);
None
}
_ => None, _ => None,
} }
} }
iced::Event::Touch(_touch) => None, iced::Event::Touch(_touch) => None,
iced::Event::A11y(_id, _action_request) => None, iced::Event::A11y(_id, _action_request) => None,
iced::Event::Dnd(_dnd_event) => None, iced::Event::Dnd(_dnd_event) => {
iced::Event::PlatformSpecific(_platform_specific) => { // debug!(?dnd_event);
None
}
iced::Event::PlatformSpecific(platform_specific) => {
debug!(?platform_specific);
None None
} }
} }
@ -676,7 +696,7 @@ impl cosmic::Application for App {
}) })
} }
song_editor::Action::UpdateSong(song) => { song_editor::Action::UpdateSong(song) => {
if let Some(library) = &mut self.library { if let Some(_) = &mut self.library {
self.update(Message::Library( self.update(Message::Library(
library::Message::UpdateSong(song), library::Message::UpdateSong(song),
)) ))
@ -914,9 +934,7 @@ impl cosmic::Application for App {
.set_window_title(format!("window_{count}"), id); .set_window_title(format!("window_{count}"), id);
spawn_window.map(|id| { spawn_window.map(|id| {
cosmic::Action::App(Message::WindowOpened( cosmic::Action::App(Message::WindowOpened(id))
id, None,
))
}) })
} }
Message::CloseWindow(id) => { Message::CloseWindow(id) => {
@ -926,7 +944,7 @@ impl cosmic::Application for App {
Task::none() Task::none()
} }
} }
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?")
@ -969,10 +987,6 @@ impl cosmic::Application for App {
self.library = Some(library); self.library = Some(library);
Task::none() Task::none()
} }
Message::AddService(service) => {
self.service = service;
Task::none()
}
Message::None => Task::none(), Message::None => Task::none(),
Message::EditorToggle(edit) => { Message::EditorToggle(edit) => {
if edit { if edit {
@ -988,6 +1002,10 @@ impl cosmic::Application for App {
self.search_id.clone(), self.search_id.clone(),
) )
} }
Message::HoveredServiceItem(index) => {
self.hovered_item = index;
Task::none()
}
Message::ChangeServiceItem(index) => { Message::ChangeServiceItem(index) => {
if let Some((index, item)) = self if let Some((index, item)) = self
.service .service
@ -1283,9 +1301,8 @@ where
}); });
self.windows.push(id); self.windows.push(id);
_ = self.set_window_title("Lumina Presenter".to_owned(), id); _ = self.set_window_title("Lumina Presenter".to_owned(), id);
spawn_window.map(|id| { spawn_window
cosmic::Action::App(Message::WindowOpened(id, None)) .map(|id| cosmic::Action::App(Message::WindowOpened(id)))
})
} }
fn add_library(&self) -> Task<Message> { fn add_library(&self) -> Task<Message> {
@ -1309,36 +1326,6 @@ where
} }
} }
fn add_service(
&self,
items: Vec<ServiceItem>,
fontdb: Arc<fontdb::Database>,
) -> Task<Message> {
Task::perform(
async move {
let items: Vec<ServiceItem> = 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( fn process_key_press(
&mut self, &mut self,
key: Key, key: Key,
@ -1411,50 +1398,117 @@ where
} }
fn service_list(&self) -> Element<Message> { fn service_list(&self) -> Element<Message> {
let list = self let list =
.service self.service.iter().enumerate().map(|(index, item)| {
.iter() let icon = match item.kind {
.enumerate() ServiceItemKind::Song(_) => {
.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") 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")
},
} }
ServiceItemKind::Video(_) => {
icon::from_name("folder-videos-symbolic")
}
ServiceItemKind::Image(_) => {
icon::from_name("folder-pictures-symbolic")
}
ServiceItemKind::Presentation(_) => {
icon::from_name(
"x-office-presentation-symbolic",
)
}
ServiceItemKind::Content(_) => icon::from_name(
"x-office-presentation-symbolic",
),
};
let title = responsive(|size| {
text::heading(library::elide_text(
&item.title,
size.width,
))
.wrapping(Wrapping::None)
.into()
});
let container = container(
row![icon, title]
.align_y(Vertical::Center)
.spacing(cosmic::theme::spacing().space_xs),
)
.padding(cosmic::theme::spacing().space_s)
.class(cosmic::theme::style::Container::Secondary)
.style(move |t| {
container::Style::default()
.background(IcedBackground::Color(
if self.hovered_item.is_some_and(
|hovered_index| {
index == hovered_index
},
) {
t.cosmic().button.hover.into()
} else {
t.cosmic().button.base.into()
},
))
.border(Border::default().rounded(
t.cosmic().corner_radii.radius_m,
))
}) })
// .icon_size(cosmic::theme::spacing().space_l) .width(Length::Fill);
.class(cosmic::theme::style::Button::HeaderBar) let mouse_area = mouse_area(container)
// .spacing(cosmic::theme::spacing().space_l) .on_enter(Message::HoveredServiceItem(Some(
.height(cosmic::theme::spacing().space_xl) index,
.width(Length::Fill) )))
.on_exit(Message::HoveredServiceItem(None))
.on_press(Message::ChangeServiceItem(index)); .on_press(Message::ChangeServiceItem(index));
let tooltip = tooltip(button, // 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")
// },
// }
// })
// // .icon_size(cosmic::theme::spacing().space_l)
// .class(cosmic::theme::style::Button::HeaderBar)
// // .spacing(cosmic::theme::spacing().space_l)
// .height(cosmic::theme::spacing().space_xl)
// .width(Length::Fill)
// .on_press(Message::ChangeServiceItem(index));
let tooltip = tooltip(
mouse_area,
text::body(item.kind.to_string()), text::body(item.kind.to_string()),
TPosition::Right); TPosition::Right,
dnd_destination(tooltip, vec!["application/service-item".into()]) )
.gap(cosmic::theme::spacing().space_xs);
dnd_destination(
tooltip,
vec![
"application/service-item".into(),
"video/mp4".into(),
],
)
.data_received_for::<ServiceItem>(move |item| { .data_received_for::<ServiceItem>(move |item| {
if let Some(item) = item { if let Some(item) = item {
Message::AddServiceItem(index, item) Message::AddServiceItem(index, item)
} else { } else {
Message::None Message::None
} }
}).on_finish(move |mime, data, action, x, y| { })
.on_finish(move |mime, data, action, x, y| {
debug!(mime, ?data, ?action, x, y); debug!(mime, ?data, ?action, x, y);
let Ok(item) = ServiceItem::try_from((data, mime)) else { let Ok(item) =
ServiceItem::try_from((data, mime))
else {
return Message::None; return Message::None;
}; };
debug!(?item); debug!(?item);
@ -1469,6 +1523,7 @@ where
.width(Length::Fill), .width(Length::Fill),
iced::widget::horizontal_rule(1), iced::widget::horizontal_rule(1),
column(list).spacing(10), column(list).spacing(10),
// service::service(&self.service),
dnd_destination( dnd_destination(
vertical_space().width(Length::Fill), vertical_space().width(Length::Fill),
vec!["application/service-item".into()] vec!["application/service-item".into()]
@ -1494,9 +1549,7 @@ where
] ]
.padding(10) .padding(10)
.spacing(10); .spacing(10);
let container = Container::new(column) let container = Container::new(column).style(nav_bar_style);
// .height(Length::Fill)
.style(nav_bar_style);
container.center(Length::FillPortion(2)).into() container.center(Length::FillPortion(2)).into()
} }

View file

@ -51,7 +51,6 @@ pub struct Library {
enum MenuMessage { enum MenuMessage {
Delete((LibraryKind, i32)), Delete((LibraryKind, i32)),
Open, Open,
None,
} }
impl MenuAction for MenuMessage { impl MenuAction for MenuMessage {
@ -63,7 +62,6 @@ impl MenuAction for MenuMessage {
Message::DeleteItem((*kind, *index)) Message::DeleteItem((*kind, *index))
} }
MenuMessage::Open => todo!(), MenuMessage::Open => todo!(),
MenuMessage::None => todo!(),
} }
} }
} }
@ -827,7 +825,7 @@ async fn add_db() -> Result<SqlitePool> {
SqlitePool::connect(&db_url).await.into_diagnostic() SqlitePool::connect(&db_url).await.into_diagnostic()
} }
fn elide_text(text: impl AsRef<str>, width: f32) -> String { pub fn elide_text(text: impl AsRef<str>, width: f32) -> String {
const CHAR_SIZE: f32 = 8.0; const CHAR_SIZE: f32 = 8.0;
let text: String = text.as_ref().to_owned(); let text: String = text.as_ref().to_owned();
let text_length = text.len() as f32 * CHAR_SIZE; let text_length = text.len() as f32 * CHAR_SIZE;

View file

@ -1,5 +1,10 @@
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc}; use std::{
fs::File,
io::BufReader,
path::PathBuf,
sync::{Arc, LazyLock},
};
use cosmic::{ use cosmic::{
iced::{ iced::{
@ -31,7 +36,8 @@ use crate::{
}; };
const REFERENCE_WIDTH: f32 = 1920.0; const REFERENCE_WIDTH: f32 = 1920.0;
const REFERENCE_HEIGHT: f32 = 1080.0; static DEFAULT_SLIDE: LazyLock<Slide> =
LazyLock::new(|| Slide::default());
// #[derive(Default, Clone, Debug)] // #[derive(Default, Clone, Debug)]
pub(crate) struct Presenter { pub(crate) struct Presenter {
@ -147,14 +153,22 @@ impl Presenter {
let total_slides: usize = let total_slides: usize =
items.iter().fold(0, |a, item| a + item.slides.len()); items.iter().fold(0, |a, item| a + item.slides.len());
let slide =
items.get(0).map(|item| item.slides.get(0)).flatten();
let audio = items
.get(0)
.map(|item| item.slides.get(0).map(|slide| slide.audio()))
.flatten()
.flatten();
Self { Self {
current_slide: items[0].slides[0].clone(), current_slide: slide.unwrap_or(&DEFAULT_SLIDE).clone(),
current_item: 0, current_item: 0,
current_slide_index: 0, current_slide_index: 0,
absolute_slide_index: 0, absolute_slide_index: 0,
total_slides, total_slides,
video, video,
audio: items[0].slides[0].audio(), audio,
service: items, service: items,
video_position: 0.0, video_position: 0.0,
hovered_slide: None, hovered_slide: None,

View file

@ -1,22 +1,19 @@
use std::any::Any; use cosmic::iced::Size;
use cosmic::iced::{self, Size};
use cosmic::iced_core::window;
use cosmic::iced_core::widget::tree;
use cosmic::{ use cosmic::{
iced::{ iced::{
clipboard::dnd::{DndAction, DndEvent, SourceEvent}, clipboard::dnd::{DndEvent, SourceEvent},
event, mouse, overlay, Event, Length, Point, Rectangle, event, mouse, Event, Length, Point, Rectangle, Vector,
Vector,
}, },
iced_core::{ iced_core::{
self, layout, renderer, self, image::Renderer, layout, renderer, widget::Tree,
widget::{tree, Tree},
Clipboard, Shell, Clipboard, Shell,
}, },
widget::{container, Id, Widget}, widget::Widget,
Element, Element,
}; };
use tracing::debug;
use crate::core::service_items::ServiceItem; use crate::core::service_items::ServiceItem;
@ -133,6 +130,10 @@ impl<Message: Clone + 'static>
layout::atomic(limits, self.width, self.height) layout::atomic(limits, self.width, self.height)
} }
fn state(&self) -> iced_core::widget::tree::State {
tree::State::new(State::new())
}
// fn operate( // fn operate(
// &self, // &self,
// tree: &mut Tree, // tree: &mut Tree,
@ -231,6 +232,7 @@ impl<Message: Clone + 'static>
_ => return event::Status::Ignored, _ => return event::Status::Ignored,
}, },
Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => { Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => {
debug!("canceled");
if state.is_dragging { if state.is_dragging {
if let Some(m) = self.on_cancelled.as_ref() { if let Some(m) = self.on_cancelled.as_ref() {
shell.publish(m.clone()); shell.publish(m.clone());
@ -241,6 +243,7 @@ impl<Message: Clone + 'static>
return event::Status::Ignored; return event::Status::Ignored;
} }
Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => { Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => {
debug!("dropped");
if state.is_dragging { if state.is_dragging {
if let Some(m) = self.on_finish.as_ref() { if let Some(m) = self.on_finish.as_ref() {
shell.publish(m.clone()); shell.publish(m.clone());
@ -250,6 +253,7 @@ impl<Message: Clone + 'static>
} }
return event::Status::Ignored; return event::Status::Ignored;
} }
Event::Dnd(event) => debug!(?event),
_ => return event::Status::Ignored, _ => return event::Status::Ignored,
} }
event::Status::Ignored event::Status::Ignored
@ -286,10 +290,8 @@ impl<Message: Clone + 'static>
cursor_position: mouse::Cursor, cursor_position: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let state = tree.state.downcast_mut::<State>(); // let state = tree.state.downcast_mut::<State>();
for item in self.service { for item in self.service {}
todo!()
}
} }
// fn overlay<'b>( // fn overlay<'b>(

View file

@ -2,7 +2,7 @@ use std::{io, path::PathBuf, sync::Arc};
use cosmic::{ use cosmic::{
dialog::file_chooser::{open::Dialog, FileFilter}, dialog::file_chooser::{open::Dialog, FileFilter},
iced::{alignment::Vertical, Font, Length}, iced::{alignment::Vertical, Length},
iced_wgpu::graphics::text::cosmic_text::fontdb, iced_wgpu::graphics::text::cosmic_text::fontdb,
iced_widget::{column, row}, iced_widget::{column, row},
theme, theme,

View file

@ -354,14 +354,6 @@ impl TextSvg {
.height(Length::Fill) .height(Length::Fill)
.into() .into()
} }
fn text_spans(&self) -> Vec<String> {
self.text
.lines()
.enumerate()
.map(|(i, t)| format!("<tspan x=\"50%\">{t}</tspan>"))
.collect()
}
} }
pub fn shadow( pub fn shadow(
@ -408,27 +400,3 @@ pub fn text_svg_generator(
slide.text_svg = Some(text_svg); slide.text_svg = Some(text_svg);
} }
} }
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::TextSvg;
#[test]
fn test_text_spans() {
let mut text = TextSvg::new("yes");
text.text = "This is
multiline
text."
.into();
assert_eq!(
vec![
String::from("<tspan>This is</tspan>"),
String::from("<tspan>multiline</tspan>"),
String::from("<tspan>text.</tspan>"),
],
text.text_spans()
)
}
}