switching to a better layout for items and slides
Some checks are pending
/ test (push) Waiting to run

This commit is contained in:
Chris Cochrun 2025-08-16 14:37:49 -05:00
parent f8e8ba3985
commit fade333634
4 changed files with 221 additions and 135 deletions

View file

@ -22,6 +22,7 @@ pub struct ServiceItem {
pub title: String,
pub database_id: i32,
pub kind: ServiceItemKind,
pub slides: Vec<Slide>,
// pub item: Box<dyn ServiceTrait>,
}
@ -120,6 +121,7 @@ impl Default for ServiceItem {
title: String::default(),
database_id: 0,
kind: ServiceItemKind::Content(Slide::default()),
slides: vec![],
// item: Box::new(Image::default()),
}
}
@ -166,7 +168,10 @@ impl From<&Value> for ServiceItem {
id: 0,
title,
database_id: 0,
kind: ServiceItemKind::Content(slide),
kind: ServiceItemKind::Content(
slide.clone(),
),
slides: vec![slide],
}
} else if let Some(background) =
list.get(background_pos)
@ -224,11 +229,11 @@ impl From<&Value> for ServiceItem {
}
#[derive(Debug, Default, Clone)]
pub struct ServiceItemModel {
pub struct Service {
items: Vec<ServiceItem>,
}
impl Deref for ServiceItemModel {
impl Deref for Service {
type Target = Vec<ServiceItem>;
fn deref(&self) -> &Self::Target {
@ -244,7 +249,7 @@ impl Deref for ServiceItemModel {
// }
// }
impl From<Vec<ServiceItem>> for ServiceItemModel {
impl From<Vec<ServiceItem>> for Service {
fn from(items: Vec<ServiceItem>) -> Self {
Self { items }
}
@ -252,49 +257,93 @@ impl From<Vec<ServiceItem>> for ServiceItemModel {
impl From<&Song> for ServiceItem {
fn from(song: &Song) -> Self {
Self {
kind: ServiceItemKind::Song(song.clone()),
database_id: song.id,
title: song.title.clone(),
..Default::default()
if let Ok(slides) = song.to_slides() {
Self {
kind: ServiceItemKind::Song(song.clone()),
database_id: song.id,
title: song.title.clone(),
slides,
..Default::default()
}
} else {
Self {
kind: ServiceItemKind::Song(song.clone()),
database_id: song.id,
title: song.title.clone(),
..Default::default()
}
}
}
}
impl From<&Video> for ServiceItem {
fn from(video: &Video) -> Self {
Self {
kind: ServiceItemKind::Video(video.clone()),
database_id: video.id,
title: video.title.clone(),
..Default::default()
if let Ok(slides) = video.to_slides() {
Self {
kind: ServiceItemKind::Video(video.clone()),
database_id: video.id,
title: video.title.clone(),
slides,
..Default::default()
}
} else {
Self {
kind: ServiceItemKind::Video(video.clone()),
database_id: video.id,
title: video.title.clone(),
..Default::default()
}
}
}
}
impl From<&Image> for ServiceItem {
fn from(image: &Image) -> Self {
Self {
kind: ServiceItemKind::Image(image.clone()),
database_id: image.id,
title: image.title.clone(),
..Default::default()
if let Ok(slides) = image.to_slides() {
Self {
kind: ServiceItemKind::Image(image.clone()),
database_id: image.id,
title: image.title.clone(),
slides,
..Default::default()
}
} else {
Self {
kind: ServiceItemKind::Image(image.clone()),
database_id: image.id,
title: image.title.clone(),
..Default::default()
}
}
}
}
impl From<&Presentation> for ServiceItem {
fn from(presentation: &Presentation) -> Self {
Self {
kind: ServiceItemKind::Presentation(presentation.clone()),
database_id: presentation.id,
title: presentation.title.clone(),
..Default::default()
if let Ok(slides) = presentation.to_slides() {
Self {
kind: ServiceItemKind::Presentation(
presentation.clone(),
),
database_id: presentation.id,
title: presentation.title.clone(),
slides,
..Default::default()
}
} else {
Self {
kind: ServiceItemKind::Presentation(
presentation.clone(),
),
database_id: presentation.id,
title: presentation.title.clone(),
..Default::default()
}
}
}
}
impl ServiceItemModel {
impl Service {
fn add_item(
&mut self,
item: impl Into<ServiceItem>,
@ -379,7 +428,7 @@ mod test {
let service_item = ServiceItem::from(&song);
let pres = test_presentation();
let pres_item = ServiceItem::from(&pres);
let mut service_model = ServiceItemModel::default();
let mut service_model = Service::default();
match service_model.add_item(&song) {
Ok(_) => {
assert_eq!(

View file

@ -1,5 +1,5 @@
use clap::{command, Parser};
use core::service_items::{ServiceItem, ServiceItemModel};
use core::service_items::{Service, ServiceItem};
use core::slide::*;
use core::songs::Song;
use cosmic::app::context_drawer::ContextDrawer;
@ -102,9 +102,7 @@ struct App {
file: PathBuf,
presenter: Presenter,
windows: Vec<window::Id>,
service: BTreeMap<ServiceItem, Vec<Slide>>,
slides: Vec<Slide>,
current_slide: Slide,
service: Vec<ServiceItem>,
presentation_open: bool,
cli_mode: bool,
library: Option<Library>,
@ -136,7 +134,8 @@ enum Message {
EditorToggle(bool),
SearchFocus,
ChangeServiceItem(usize),
AddServiceItem(Option<ServiceItem>),
AddServiceItem(usize, ServiceItem),
AppendServiceItem(ServiceItem),
}
const HEADER_SPACE: u16 = 6;
@ -191,27 +190,22 @@ impl cosmic::Application for App {
}
};
let items = ServiceItemModel::from(items);
let presenter = Presenter::with_items(items.clone());
let slides = items.to_slides().unwrap_or_default();
let current_slide = slides[0].clone();
let song_editor = SongEditor::new();
for item in items.iter() {
nav_model.insert().text(item.title()).data(item.clone());
}
// for item in items.iter() {
// nav_model.insert().text(item.title()).data(item.clone());
// }
nav_model.activate_position(0);
// nav_model.activate_position(0);
let mut app = App {
presenter,
core,
nav_model,
service: BTreeMap::new(),
service: items,
file: PathBuf::default(),
windows,
slides,
current_slide,
presentation_open: false,
cli_mode: !input.ui,
library: None,
@ -233,6 +227,7 @@ impl cosmic::Application for App {
};
batch.push(app.add_library());
// batch.push(app.add_service(items));
let batch = Task::batch(batch);
(app, batch)
}
@ -282,28 +277,37 @@ impl cosmic::Application for App {
.service
.iter()
.enumerate()
.map(|(index, (item, slides))| {
.map(|(index, item)| {
dnd_destination(tooltip( 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(_) => todo!(),
}
})
.class(cosmic::theme::style::Button::HeaderBar)
.on_press(cosmic::Action::App(Message::ChangeServiceItem(index))), text::body(item.kind.to_string()), TPosition::Right), vec!["application/service-item".into()]).data_received_for::<ServiceItem>(|item| {
cosmic::Action::App(Message::AddServiceItem(item))
.leading_icon({
match item.kind {
core::kinds::ServiceItemKind::Song(_) => {
icon::from_name("folder-music-symbolic")
},
core::kinds::ServiceItemKind::Video(_) => {
icon::from_name("folder-videos-symbolic")
},
core::kinds::ServiceItemKind::Image(_) => {
icon::from_name("folder-pictures-symbolic")
},
core::kinds::ServiceItemKind::Presentation(_) => {
icon::from_name("x-office-presentation-symbolic")
},
core::kinds::ServiceItemKind::Content(_) => {
icon::from_name("x-office-presentation-symbolic")
},
}
})
.class(cosmic::theme::style::Button::HeaderBar)
.padding(5)
.width(Length::Fill)
.on_press(cosmic::Action::App(Message::ChangeServiceItem(index))),
text::body(item.kind.to_string()), TPosition::Right), vec!["application/service-item".into()]).data_received_for::<ServiceItem>( move |item| {
if let Some(item) = item {
cosmic::Action::App(Message::AddServiceItem(index, item))
} else {
cosmic::Action::None
}
})
.into()
});
@ -311,18 +315,23 @@ impl cosmic::Application for App {
let column = column![
text::heading("Service List").center().width(280),
column(list).spacing(10),
text::heading("Service List").center().width(280),
dnd_destination_for_data::<
ServiceItem,
cosmic::Action<Message>,
>(
Container::new(vertical_space()),
|item, _| {
debug!("helloooooo");
cosmic::Action::App(Message::AddServiceItem(item))
if let Some(item) = item {
cosmic::Action::App(
Message::AppendServiceItem(item),
)
} else {
cosmic::Action::None
}
}
)
]
.padding(10)
.spacing(10);
let padding = Padding::new(0.0).top(20);
let mut container = Container::new(column)
@ -655,16 +664,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}");
@ -741,22 +750,23 @@ impl cosmic::Application for App {
Task::none()
}
Message::ChangeServiceItem(index) => {
self.presenter.update(
presenter::Message::SlideChange(index as u16),
);
if let Some(item) = self.service.get(index) {
if let Some(slide) = item.slides.first() {
self.presenter.update(
presenter::Message::SlideChange(
slide.clone(),
),
);
}
}
Task::none()
}
Message::AddServiceItem(item) => {
if let Some(item) = item {
let slides = match item.to_slides() {
Ok(s) => s,
Err(e) => {
error!("{e}");
return Task::none();
}
};
self.service.insert(item, slides);
};
Message::AddServiceItem(index, item) => {
self.service.insert(index, item);
Task::none()
}
Message::AppendServiceItem(item) => {
self.service.push(item);
Task::none()
}
}
@ -939,6 +949,29 @@ where
})
}
// fn add_service(
// &mut self,
// items: Vec<ServiceItem>,
// ) -> Task<Message> {
// Task::perform(
// async move {
// for item in items {
// debug!(?item, "Item to be appended");
// let slides = item.to_slides().unwrap_or(vec![]);
// map.insert(item, slides);
// }
// let len = map.len();
// debug!(len, "to be append: ");
// map
// },
// |x| {
// let len = x.len();
// debug!(len, "to append: ");
// cosmic::Action::App(Message::AppendService(x))
// },
// )
// }
fn process_key_press(
&mut self,
key: Key,

View file

@ -31,7 +31,10 @@ use tracing::{debug, error, info, warn};
use url::Url;
use crate::{
core::{service_items::ServiceItemModel, slide::Slide},
core::{
service_items::{Service, ServiceItem},
slide::Slide,
},
ui::text_svg::{self, Font as SvgFont},
// ui::widgets::slide_text,
BackgroundKind,
@ -43,8 +46,9 @@ const REFERENCE_HEIGHT: f32 = 1080.0;
// #[derive(Default, Clone, Debug)]
pub(crate) struct Presenter {
pub slides: Vec<Slide>,
pub items: ServiceItemModel,
pub service: Vec<ServiceItem>,
pub current_slide: Slide,
pub current_item: usize,
pub current_slide_index: u16,
pub video: Option<Video>,
pub video_position: f32,
@ -64,7 +68,7 @@ pub(crate) enum Action {
pub(crate) enum Message {
NextSlide,
PrevSlide,
SlideChange(u16),
SlideChange(Slide),
EndVideo,
StartVideo,
StartAudio,
@ -117,8 +121,13 @@ impl Presenter {
result.into_diagnostic()
}
pub fn with_items(items: ServiceItemModel) -> Self {
let slides = items.to_slides().unwrap_or_default();
pub fn with_items(items: Vec<ServiceItem>) -> Self {
let mut slides = vec![];
for item in &items {
for slide in item.to_slides().unwrap_or_default() {
slides.push(slide);
}
}
let video = {
if let Some(slide) = slides.first() {
let path = slide.background().path.clone();
@ -144,8 +153,9 @@ impl Presenter {
};
Self {
slides: slides.clone(),
items,
service: items,
current_slide: slides[0].clone(),
current_item: 0,
current_slide_index: 0,
video,
audio: slides[0].audio(),
@ -174,9 +184,9 @@ impl Presenter {
debug!("no more slides");
return Action::None;
}
return self.update(Message::SlideChange(
self.current_slide_index + 1,
));
// return self.update(Message::SlideChange(
// self.current_slide_index + 1,
// ));
}
Message::PrevSlide => {
debug!("prev slide");
@ -184,20 +194,18 @@ impl Presenter {
debug!("beginning slides");
return Action::None;
}
return self.update(Message::SlideChange(
self.current_slide_index - 1,
));
// return self.update(Message::SlideChange(
// self.current_slide_index - 1,
// ));
}
Message::SlideChange(id) => {
debug!(id, "slide changed");
Message::SlideChange(slide) => {
debug!(?slide, "slide changed");
let old_background =
self.current_slide.background().clone();
self.current_slide_index = id;
if let Some(slide) = self.slides.get(id as usize) {
self.current_slide = slide.clone();
let _ = self
.update(Message::ChangeFont(slide.font()));
}
// self.current_slide_index = slide;
self.current_slide = slide.clone();
let _ =
self.update(Message::ChangeFont(slide.font()));
if self.current_slide.background() != &old_background
{
if let Some(video) = &mut self.video {
@ -480,36 +488,32 @@ impl Presenter {
.interaction(cosmic::iced::mouse::Interaction::Pointer)
.on_move(move |_| Message::HoveredSlide(slide_id))
.on_exit(Message::HoveredSlide(-1))
.on_press(Message::SlideChange(slide_id as u16));
.on_press(Message::SlideChange(slide.clone()));
delegate.into()
}
fn reset_video(&mut self) {
if let Some(slide) =
self.slides.get(self.current_slide_index as usize)
{
match slide.background().kind {
BackgroundKind::Image => self.video = None,
BackgroundKind::Video => {
let path = slide.background().path.clone();
if path.exists() {
let url = Url::from_file_path(path).unwrap();
let result = Self::create_video(url);
match result {
Ok(mut v) => {
v.set_looping(
self.current_slide.video_loop(),
);
self.video = Some(v)
}
Err(e) => {
error!("Had an error creating the video object: {e}");
self.video = None;
}
match self.current_slide.background().kind {
BackgroundKind::Image => self.video = None,
BackgroundKind::Video => {
let path = &self.current_slide.background().path;
if path.exists() {
let url = Url::from_file_path(path).unwrap();
let result = Self::create_video(url);
match result {
Ok(mut v) => {
v.set_looping(
self.current_slide.video_loop(),
);
self.video = Some(v)
}
Err(e) => {
error!("Had an error creating the video object: {e}");
self.video = None;
}
} else {
self.video = None;
}
} else {
self.video = None;
}
}
}

View file

@ -1,8 +1,8 @@
(slide :background (image :source "~/pics/frodo.jpg" :fit fill)
(text "This is frodo" :font-size 90))
(slide (video :source "~/vids/test/camprules2024.mp4" :fit contain))
(slide (video :source "~/vids/The Basics of Hanging Drywall.mkv" :fit contain))
(slide (video :source "~/vids/Ladybird Is The Future Of Web Browsers.webm" :fit contain))
(slide (video :source "~/vids/never give up.mkv" :fit contain))
(slide (video :source "~/vids/The promise of Rust.mkv" :fit contain))
(song :id 7 :author "North Point Worship"
:font "Quicksand Bold" :font-size 60
:shadow "" :stroke ""