switching to a better layout for items and slides
Some checks are pending
/ test (push) Waiting to run
Some checks are pending
/ test (push) Waiting to run
This commit is contained in:
parent
f8e8ba3985
commit
fade333634
4 changed files with 221 additions and 135 deletions
|
@ -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!(
|
||||
|
|
159
src/main.rs
159
src/main.rs
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue