Compare commits
No commits in common. "2413b96791dcf8a36b7cc0f37ed45c04e70b857f" and "ad14135ddf32736a8a230d641cfc0e39db7d053a" have entirely different histories.
2413b96791
...
ad14135ddf
11 changed files with 256 additions and 107 deletions
|
@ -1,13 +1,12 @@
|
||||||
use cosmic::widget::image::Handle;
|
|
||||||
use crisp::types::{Keyword, Symbol, Value};
|
use crisp::types::{Keyword, Symbol, Value};
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use mupdf::{Colorspace, Document, Matrix};
|
use mupdf::{Document, Page};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
pool::PoolConnection, prelude::FromRow, query, sqlite::SqliteRow,
|
pool::PoolConnection, prelude::FromRow, query, sqlite::SqliteRow,
|
||||||
Row, Sqlite, SqliteConnection, SqlitePool,
|
Row, Sqlite, SqliteConnection, SqlitePool,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::{path::PathBuf, sync::Arc};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::{Background, Slide, SlideBuilder, TextAlignment};
|
use crate::{Background, Slide, SlideBuilder, TextAlignment};
|
||||||
|
@ -132,36 +131,13 @@ impl ServiceTrait for Presentation {
|
||||||
let background = Background::try_from(self.path.clone())
|
let background = Background::try_from(self.path.clone())
|
||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
debug!(?background);
|
debug!(?background);
|
||||||
let document = Document::open(background.path.as_path())
|
let doc = Document::open(background.path.as_path())
|
||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
debug!(?document);
|
debug!(?doc);
|
||||||
let pages = document.pages().into_diagnostic()?;
|
let pages = doc.page_count().into_diagnostic()?;
|
||||||
debug!(?pages);
|
debug!(?pages);
|
||||||
let pages: Vec<Handle> = pages
|
|
||||||
.filter_map(|page| {
|
|
||||||
let Some(page) = page.ok() else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let matrix = Matrix::IDENTITY;
|
|
||||||
let colorspace = Colorspace::device_rgb();
|
|
||||||
let Ok(pixmap) = page
|
|
||||||
.to_pixmap(&matrix, &colorspace, true, true)
|
|
||||||
.into_diagnostic()
|
|
||||||
else {
|
|
||||||
error!("Can't turn this page into pixmap");
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
debug!(?pixmap);
|
|
||||||
Some(Handle::from_rgba(
|
|
||||||
pixmap.width(),
|
|
||||||
pixmap.height(),
|
|
||||||
pixmap.samples().to_vec(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut slides: Vec<Slide> = vec![];
|
let mut slides: Vec<Slide> = vec![];
|
||||||
for (index, page) in pages.into_iter().enumerate() {
|
for page in 0..pages {
|
||||||
let slide = SlideBuilder::new()
|
let slide = SlideBuilder::new()
|
||||||
.background(
|
.background(
|
||||||
Background::try_from(self.path.clone())
|
Background::try_from(self.path.clone())
|
||||||
|
@ -175,8 +151,7 @@ impl ServiceTrait for Presentation {
|
||||||
.video_loop(false)
|
.video_loop(false)
|
||||||
.video_start_time(0.0)
|
.video_start_time(0.0)
|
||||||
.video_end_time(0.0)
|
.video_end_time(0.0)
|
||||||
.pdf_index(index as u32)
|
.pdf_index(page as u32)
|
||||||
.pdf_page(page)
|
|
||||||
.build()?;
|
.build()?;
|
||||||
slides.push(slide);
|
slides.push(slide);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
|
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
|
||||||
use crisp::types::{Keyword, Symbol, Value};
|
use crisp::types::{Keyword, Symbol, Value};
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
|
use resvg::usvg::fontdb;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
|
@ -258,7 +260,7 @@ impl From<&Song> for ServiceItem {
|
||||||
kind: ServiceItemKind::Song(song.clone()),
|
kind: ServiceItemKind::Song(song.clone()),
|
||||||
database_id: song.id,
|
database_id: song.id,
|
||||||
title: song.title.clone(),
|
title: song.title.clone(),
|
||||||
slides: slides,
|
slides: slides.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -279,7 +281,7 @@ impl From<&Video> for ServiceItem {
|
||||||
kind: ServiceItemKind::Video(video.clone()),
|
kind: ServiceItemKind::Video(video.clone()),
|
||||||
database_id: video.id,
|
database_id: video.id,
|
||||||
title: video.title.clone(),
|
title: video.title.clone(),
|
||||||
slides: slides,
|
slides: slides.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -300,7 +302,7 @@ impl From<&Image> for ServiceItem {
|
||||||
kind: ServiceItemKind::Image(image.clone()),
|
kind: ServiceItemKind::Image(image.clone()),
|
||||||
database_id: image.id,
|
database_id: image.id,
|
||||||
title: image.title.clone(),
|
title: image.title.clone(),
|
||||||
slides: slides,
|
slides: slides.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -323,7 +325,7 @@ impl From<&Presentation> for ServiceItem {
|
||||||
),
|
),
|
||||||
database_id: presentation.id,
|
database_id: presentation.id,
|
||||||
title: presentation.title.clone(),
|
title: presentation.title.clone(),
|
||||||
slides: slides,
|
slides: slides.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
use cosmic::widget::image::Handle;
|
|
||||||
// use cosmic::dialog::ashpd::url::Url;
|
// use cosmic::dialog::ashpd::url::Url;
|
||||||
use crisp::types::{Keyword, Symbol, Value};
|
use crisp::types::{Keyword, Symbol, Value};
|
||||||
use iced_video_player::Video;
|
use iced_video_player::Video;
|
||||||
use miette::{miette, Result};
|
use miette::{miette, Result};
|
||||||
|
use resvg::usvg::fontdb;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::ui::text_svg::TextSvg;
|
use crate::ui::{
|
||||||
|
pdf::PdfViewer,
|
||||||
|
text_svg::{self, TextSvg},
|
||||||
|
};
|
||||||
|
|
||||||
use super::songs::Song;
|
use super::songs::Song;
|
||||||
|
|
||||||
|
@ -30,8 +34,6 @@ pub struct Slide {
|
||||||
video_end_time: f32,
|
video_end_time: f32,
|
||||||
pdf_index: u32,
|
pdf_index: u32,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pdf_page: Option<Handle>,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub text_svg: Option<TextSvg>,
|
pub text_svg: Option<TextSvg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,7 +307,7 @@ impl Slide {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_alignment(&self) -> TextAlignment {
|
pub fn text_alignment(&self) -> TextAlignment {
|
||||||
self.text_alignment
|
self.text_alignment.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font_size(&self) -> i32 {
|
pub fn font_size(&self) -> i32 {
|
||||||
|
@ -324,10 +326,6 @@ impl Slide {
|
||||||
self.audio.clone()
|
self.audio.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pdf_page(&self) -> Option<Handle> {
|
|
||||||
self.pdf_page.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pdf_index(&self) -> u32 {
|
pub fn pdf_index(&self) -> u32 {
|
||||||
self.pdf_index
|
self.pdf_index
|
||||||
}
|
}
|
||||||
|
@ -549,8 +547,6 @@ pub struct SlideBuilder {
|
||||||
video_end_time: Option<f32>,
|
video_end_time: Option<f32>,
|
||||||
pdf_index: Option<u32>,
|
pdf_index: Option<u32>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pdf_page: Option<Handle>,
|
|
||||||
#[serde(skip)]
|
|
||||||
text_svg: Option<TextSvg>,
|
text_svg: Option<TextSvg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,11 +629,6 @@ impl SlideBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn pdf_page(mut self, pdf_page: Handle) -> Self {
|
|
||||||
let _ = self.pdf_page.insert(pdf_page);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn pdf_index(
|
pub(crate) fn pdf_index(
|
||||||
mut self,
|
mut self,
|
||||||
pdf_index: impl Into<u32>,
|
pdf_index: impl Into<u32>,
|
||||||
|
@ -683,7 +674,6 @@ impl SlideBuilder {
|
||||||
video_end_time,
|
video_end_time,
|
||||||
text_svg: self.text_svg,
|
text_svg: self.text_svg,
|
||||||
pdf_index: self.pdf_index.unwrap_or_default(),
|
pdf_index: self.pdf_index.unwrap_or_default(),
|
||||||
pdf_page: self.pdf_page,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -7,7 +7,7 @@ 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, Length, Point};
|
use cosmic::iced::{self, event, window, Color, Length, Point};
|
||||||
use cosmic::iced_futures::Subscription;
|
use cosmic::iced_futures::Subscription;
|
||||||
use cosmic::iced_widget::{column, row, stack};
|
use cosmic::iced_widget::{column, row, stack};
|
||||||
use cosmic::theme;
|
use cosmic::theme;
|
||||||
|
@ -40,7 +40,7 @@ use ui::song_editor::{self, SongEditor};
|
||||||
use ui::EditorMode;
|
use ui::EditorMode;
|
||||||
|
|
||||||
use crate::core::kinds::ServiceItemKind;
|
use crate::core::kinds::ServiceItemKind;
|
||||||
use crate::ui::text_svg::{self};
|
use crate::ui::text_svg::{self, shadow, stroke, TextSvg};
|
||||||
|
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod lisp;
|
pub mod lisp;
|
||||||
|
@ -985,7 +985,7 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
Message::Search(query) => {
|
Message::Search(query) => {
|
||||||
self.search_query = query.clone();
|
self.search_query = query.clone();
|
||||||
self.search(query)
|
return self.search(query);
|
||||||
}
|
}
|
||||||
Message::UpdateSearchResults(items) => {
|
Message::UpdateSearchResults(items) => {
|
||||||
self.search_results = items;
|
self.search_results = items;
|
||||||
|
@ -1206,14 +1206,14 @@ where
|
||||||
|
|
||||||
fn search(&self, query: String) -> Task<Message> {
|
fn search(&self, query: String) -> Task<Message> {
|
||||||
if let Some(library) = self.library.clone() {
|
if let Some(library) = self.library.clone() {
|
||||||
Task::perform(
|
return Task::perform(
|
||||||
async move { library.search_items(query).await },
|
async move { library.search_items(query).await },
|
||||||
|items| {
|
|items| {
|
||||||
cosmic::Action::App(Message::UpdateSearchResults(
|
cosmic::Action::App(Message::UpdateSearchResults(
|
||||||
items,
|
items,
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
} else {
|
} else {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
@ -1378,7 +1378,7 @@ where
|
||||||
.data_received_for::<ServiceItem>(|item| {
|
.data_received_for::<ServiceItem>(|item| {
|
||||||
item.map_or_else(
|
item.map_or_else(
|
||||||
|| Message::None,
|
|| Message::None,
|
||||||
Message::AppendServiceItem,
|
|item| Message::AppendServiceItem(item),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_finish(
|
.on_finish(
|
||||||
|
@ -1396,7 +1396,7 @@ where
|
||||||
]
|
]
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.spacing(10);
|
.spacing(10);
|
||||||
let container = Container::new(column)
|
let mut container = Container::new(column)
|
||||||
// .height(Length::Fill)
|
// .height(Length::Fill)
|
||||||
.style(nav_bar_style);
|
.style(nav_bar_style);
|
||||||
|
|
||||||
|
|
|
@ -264,8 +264,7 @@ impl<'a> Library {
|
||||||
let video_library = self.library_item(&self.video_library);
|
let video_library = self.library_item(&self.video_library);
|
||||||
let presentation_library =
|
let presentation_library =
|
||||||
self.library_item(&self.presentation_library);
|
self.library_item(&self.presentation_library);
|
||||||
|
let column = column![
|
||||||
column![
|
|
||||||
text::heading("Library").center().width(Length::Fill),
|
text::heading("Library").center().width(Length::Fill),
|
||||||
cosmic::iced::widget::horizontal_rule(1),
|
cosmic::iced::widget::horizontal_rule(1),
|
||||||
song_library,
|
song_library,
|
||||||
|
@ -276,7 +275,8 @@ impl<'a> Library {
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.into()
|
.into();
|
||||||
|
column
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn library_item<T>(
|
pub fn library_item<T>(
|
||||||
|
@ -481,7 +481,8 @@ impl<'a> Library {
|
||||||
.accent_text_color()
|
.accent_text_color()
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
} else if let Some((library, selected)) = self.selected_item
|
} else {
|
||||||
|
if let Some((library, selected)) = self.selected_item
|
||||||
{
|
{
|
||||||
if model.kind == library
|
if model.kind == library
|
||||||
&& selected == index as i32
|
&& selected == index as i32
|
||||||
|
@ -498,6 +499,7 @@ impl<'a> Library {
|
||||||
.cosmic()
|
.cosmic()
|
||||||
.destructive_text_color()
|
.destructive_text_color()
|
||||||
.into()
|
.into()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
text::body(elide_text(item.subtext(), size.width))
|
text::body(elide_text(item.subtext(), size.width))
|
||||||
.center()
|
.center()
|
||||||
|
@ -527,7 +529,8 @@ impl<'a> Library {
|
||||||
&& selected == index as i32
|
&& selected == index as i32
|
||||||
{
|
{
|
||||||
t.cosmic().accent.selected.into()
|
t.cosmic().accent.selected.into()
|
||||||
} else if let Some((library, hovered)) =
|
} else {
|
||||||
|
if let Some((library, hovered)) =
|
||||||
self.hovered_item
|
self.hovered_item
|
||||||
{
|
{
|
||||||
if model.kind == library
|
if model.kind == library
|
||||||
|
@ -540,6 +543,7 @@ impl<'a> Library {
|
||||||
} else {
|
} else {
|
||||||
t.cosmic().button.base.into()
|
t.cosmic().button.base.into()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if let Some((library, hovered)) =
|
} else if let Some((library, hovered)) =
|
||||||
self.hovered_item
|
self.hovered_item
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::core::model::LibraryKind;
|
||||||
|
|
||||||
pub mod double_ended_slider;
|
pub mod double_ended_slider;
|
||||||
pub mod library;
|
pub mod library;
|
||||||
|
pub mod pdf;
|
||||||
pub mod presenter;
|
pub mod presenter;
|
||||||
pub mod slide_editor;
|
pub mod slide_editor;
|
||||||
pub mod song_editor;
|
pub mod song_editor;
|
||||||
|
|
161
src/ui/pdf.rs
Normal file
161
src/ui/pdf.rs
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use cosmic::iced::ContentFit;
|
||||||
|
use cosmic::iced::Length;
|
||||||
|
use cosmic::widget::image;
|
||||||
|
use cosmic::widget::image::Handle;
|
||||||
|
use cosmic::Element;
|
||||||
|
use miette::IntoDiagnostic;
|
||||||
|
use miette::Result;
|
||||||
|
use miette::Severity;
|
||||||
|
use mupdf::Colorspace;
|
||||||
|
use mupdf::Document;
|
||||||
|
use mupdf::Matrix;
|
||||||
|
use tracing::debug;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct PdfViewer {
|
||||||
|
document: Option<Document>,
|
||||||
|
pages: Option<Vec<Handle>>,
|
||||||
|
current_page: Option<Handle>,
|
||||||
|
current_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Message {}
|
||||||
|
|
||||||
|
impl PdfViewer {
|
||||||
|
pub fn with_pdf(pdf: impl AsRef<Path>) -> Result<Self> {
|
||||||
|
let pdf_path = pdf.as_ref();
|
||||||
|
let document = Document::open(pdf_path).into_diagnostic()?;
|
||||||
|
let pages = document.pages().into_diagnostic()?;
|
||||||
|
let pages: Vec<Handle> = pages
|
||||||
|
.filter_map(|page| {
|
||||||
|
let Some(page) = page.ok() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let matrix = Matrix::IDENTITY;
|
||||||
|
let colorspace = Colorspace::device_rgb();
|
||||||
|
let Ok(pixmap) = page
|
||||||
|
.to_pixmap(&matrix, &colorspace, true, true)
|
||||||
|
.into_diagnostic()
|
||||||
|
else {
|
||||||
|
error!("Can't turn this page into pixmap");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let handle = pixmap.samples().to_vec();
|
||||||
|
Some(Handle::from_bytes(handle))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let Some(page) = document.pages().into_diagnostic()?.next()
|
||||||
|
else {
|
||||||
|
return Err(miette::miette!(
|
||||||
|
severity = Severity::Warning,
|
||||||
|
"There isn't a first page here"
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let page = page.into_diagnostic()?;
|
||||||
|
let matrix = Matrix::IDENTITY;
|
||||||
|
let colorspace = Colorspace::device_rgb();
|
||||||
|
let pixmap = page
|
||||||
|
.to_pixmap(&matrix, &colorspace, true, true)
|
||||||
|
.into_diagnostic()?;
|
||||||
|
let handle = pixmap.samples().to_vec();
|
||||||
|
Ok(Self {
|
||||||
|
document: Some(document),
|
||||||
|
pages: Some(pages),
|
||||||
|
current_index: 0,
|
||||||
|
current_page: Some(Handle::from_bytes(handle)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_pdf(
|
||||||
|
&mut self,
|
||||||
|
pdf: impl AsRef<Path>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let pdf_path = pdf.as_ref();
|
||||||
|
let document = Document::open(pdf_path).into_diagnostic()?;
|
||||||
|
let pages = document.pages().into_diagnostic()?;
|
||||||
|
|
||||||
|
let pages: Vec<Handle> = pages
|
||||||
|
.filter_map(|page| {
|
||||||
|
let Some(page) = page.ok() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let matrix = Matrix::IDENTITY;
|
||||||
|
let colorspace = Colorspace::device_rgb();
|
||||||
|
let Ok(pixmap) = page
|
||||||
|
.to_pixmap(&matrix, &colorspace, true, true)
|
||||||
|
.into_diagnostic()
|
||||||
|
else {
|
||||||
|
error!("Can't turn this page into pixmap");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
debug!(?pixmap);
|
||||||
|
Some(Handle::from_rgba(
|
||||||
|
pixmap.width(),
|
||||||
|
pixmap.height(),
|
||||||
|
pixmap.samples().to_vec(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let Some(page) = document.pages().into_diagnostic()?.next()
|
||||||
|
else {
|
||||||
|
return Err(miette::miette!(
|
||||||
|
severity = Severity::Warning,
|
||||||
|
"There isn't a first page here"
|
||||||
|
));
|
||||||
|
};
|
||||||
|
self.current_page = pages.get(0).map(|h| h.to_owned());
|
||||||
|
self.document = Some(document);
|
||||||
|
self.pages = Some(pages);
|
||||||
|
self.current_index = 0;
|
||||||
|
debug!(?self);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_page(&mut self) -> Result<()> {
|
||||||
|
let Some(ref pages) = self.pages else {
|
||||||
|
return Err(miette::miette!("No pages in doc"));
|
||||||
|
};
|
||||||
|
let Some(page) = pages.get(self.current_index + 1) else {
|
||||||
|
return Err(miette::miette!("There isn't a next page"));
|
||||||
|
};
|
||||||
|
self.current_page = Some(page.to_owned());
|
||||||
|
self.current_index += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous_page(&mut self) -> Result<()> {
|
||||||
|
if self.current_index == 0 {
|
||||||
|
return Err(miette::miette!("You are at the first page"));
|
||||||
|
}
|
||||||
|
let Some(ref pages) = self.pages else {
|
||||||
|
return Err(miette::miette!("No pages in doc"));
|
||||||
|
};
|
||||||
|
let Some(page) = pages.get(self.current_index - 1) else {
|
||||||
|
return Err(miette::miette!(
|
||||||
|
"There isn't a previous page"
|
||||||
|
));
|
||||||
|
};
|
||||||
|
self.current_page = Some(page.to_owned());
|
||||||
|
self.current_index -= 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self, index: u32) -> Option<Element<Message>> {
|
||||||
|
let Some(pages) = &self.pages else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Some(handle) = pages.get(index as usize) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(
|
||||||
|
image(handle)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.content_fit(ContentFit::Contain)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,18 +3,23 @@ use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced::{
|
iced::{
|
||||||
|
alignment::Horizontal,
|
||||||
|
border,
|
||||||
font::{Family, Stretch, Style, Weight},
|
font::{Family, Stretch, Style, Weight},
|
||||||
Background, Border, Color, ContentFit, Font, Length, Shadow,
|
Background, Border, Color, ContentFit, Font, Length, Shadow,
|
||||||
Vector,
|
Vector,
|
||||||
},
|
},
|
||||||
iced_widget::{
|
iced_widget::{
|
||||||
|
rich_text,
|
||||||
scrollable::{
|
scrollable::{
|
||||||
scroll_to, AbsoluteOffset, Direction, Scrollbar,
|
scroll_to, AbsoluteOffset, Direction, Scrollbar,
|
||||||
}, stack, vertical_rule,
|
},
|
||||||
|
span, stack, vertical_rule,
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widget::{
|
widget::{
|
||||||
container, image, mouse_area, responsive, scrollable, text, Container, Id, Row, Space,
|
container, image, mouse_area, responsive, scrollable, text,
|
||||||
|
Column, Container, Id, Image, Row, Space,
|
||||||
},
|
},
|
||||||
Task,
|
Task,
|
||||||
};
|
};
|
||||||
|
@ -28,6 +33,8 @@ use crate::{
|
||||||
BackgroundKind,
|
BackgroundKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::ui::pdf::PdfViewer;
|
||||||
|
|
||||||
const REFERENCE_WIDTH: f32 = 1920.0;
|
const REFERENCE_WIDTH: f32 = 1920.0;
|
||||||
const REFERENCE_HEIGHT: f32 = 1080.0;
|
const REFERENCE_HEIGHT: f32 = 1080.0;
|
||||||
|
|
||||||
|
@ -46,6 +53,7 @@ pub(crate) struct Presenter {
|
||||||
hovered_slide: Option<(usize, usize)>,
|
hovered_slide: Option<(usize, usize)>,
|
||||||
scroll_id: Id,
|
scroll_id: Id,
|
||||||
current_font: Font,
|
current_font: Font,
|
||||||
|
pdf_viewer: PdfViewer,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum Action {
|
pub(crate) enum Action {
|
||||||
|
@ -166,6 +174,7 @@ impl Presenter {
|
||||||
},
|
},
|
||||||
scroll_id: Id::unique(),
|
scroll_id: Id::unique(),
|
||||||
current_font: cosmic::font::default(),
|
current_font: cosmic::font::default(),
|
||||||
|
pdf_viewer: PdfViewer::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +225,11 @@ impl Presenter {
|
||||||
self.reset_video();
|
self.reset_video();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if slide.background().kind == BackgroundKind::Pdf {
|
||||||
|
self.pdf_viewer
|
||||||
|
.insert_pdf(&slide.background().path);
|
||||||
|
}
|
||||||
|
|
||||||
let offset = AbsoluteOffset {
|
let offset = AbsoluteOffset {
|
||||||
x: {
|
x: {
|
||||||
if self.current_slide_index > 2 {
|
if self.current_slide_index > 2 {
|
||||||
|
@ -397,6 +411,7 @@ impl Presenter {
|
||||||
slide_view(
|
slide_view(
|
||||||
&self.current_slide,
|
&self.current_slide,
|
||||||
&self.video,
|
&self.video,
|
||||||
|
&self.pdf_viewer,
|
||||||
self.current_font,
|
self.current_font,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
@ -407,6 +422,7 @@ impl Presenter {
|
||||||
slide_view(
|
slide_view(
|
||||||
&self.current_slide,
|
&self.current_slide,
|
||||||
&self.video,
|
&self.video,
|
||||||
|
&self.pdf_viewer,
|
||||||
self.current_font,
|
self.current_font,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
@ -441,8 +457,9 @@ impl Presenter {
|
||||||
);
|
);
|
||||||
|
|
||||||
let container = slide_view(
|
let container = slide_view(
|
||||||
slide,
|
&slide,
|
||||||
&self.video,
|
&self.video,
|
||||||
|
&self.pdf_viewer,
|
||||||
font,
|
font,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
@ -705,6 +722,7 @@ fn scale_font(font_size: f32, width: f32) -> f32 {
|
||||||
pub(crate) fn slide_view<'a>(
|
pub(crate) fn slide_view<'a>(
|
||||||
slide: &'a Slide,
|
slide: &'a Slide,
|
||||||
video: &'a Option<Video>,
|
video: &'a Option<Video>,
|
||||||
|
pdf_viewer: &'a PdfViewer,
|
||||||
font: Font,
|
font: Font,
|
||||||
delegate: bool,
|
delegate: bool,
|
||||||
hide_mouse: bool,
|
hide_mouse: bool,
|
||||||
|
@ -781,24 +799,17 @@ pub(crate) fn slide_view<'a>(
|
||||||
Container::new(Space::new(0, 0))
|
Container::new(Space::new(0, 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BackgroundKind::Pdf => {
|
BackgroundKind::Pdf => Container::new(
|
||||||
if let Some(pdf) = slide.pdf_page() {
|
if let Some(pdf) = pdf_viewer.view(slide.pdf_index())
|
||||||
Container::new(
|
{
|
||||||
image(pdf)
|
pdf.map(|_| Message::None)
|
||||||
.content_fit(ContentFit::Cover)
|
} else {
|
||||||
.width(width)
|
Space::new(0.0, 0.0).into()
|
||||||
.height(size.height),
|
},
|
||||||
)
|
)
|
||||||
.center_x(width)
|
.center_x(width)
|
||||||
.center_y(size.height)
|
.center_y(size.height)
|
||||||
.clip(true)
|
.clip(true),
|
||||||
} else {
|
|
||||||
Container::new(Space::new(0.0, 0.0))
|
|
||||||
.center_x(width)
|
|
||||||
.center_y(size.height)
|
|
||||||
.clip(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BackgroundKind::Html => todo!(),
|
BackgroundKind::Html => todo!(),
|
||||||
};
|
};
|
||||||
let stack = stack!(
|
let stack = stack!(
|
||||||
|
|
|
@ -2,10 +2,11 @@ use std::{io, path::PathBuf};
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced::{Color, Font, Length, Size},
|
iced::{Color, Font, Length, Size},
|
||||||
|
prelude::*,
|
||||||
widget::{
|
widget::{
|
||||||
self,
|
self,
|
||||||
canvas::{self, Program, Stroke},
|
canvas::{self, Program, Stroke},
|
||||||
container,
|
container, Canvas,
|
||||||
},
|
},
|
||||||
Renderer,
|
Renderer,
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,8 @@ use cosmic::{
|
||||||
iced_widget::row,
|
iced_widget::row,
|
||||||
theme,
|
theme,
|
||||||
widget::{
|
widget::{
|
||||||
button, column, combo_box, container, horizontal_space, icon, text, text_editor, text_input,
|
button, column, combo_box, container, horizontal_space, icon,
|
||||||
|
scrollable, text, text_editor, text_input,
|
||||||
},
|
},
|
||||||
Element, Task,
|
Element, Task,
|
||||||
};
|
};
|
||||||
|
@ -20,10 +21,11 @@ use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{service_items::ServiceTrait, songs::Song},
|
core::{service_items::ServiceTrait, songs::Song},
|
||||||
ui::slide_editor::SlideEditor,
|
ui::slide_editor::{self, SlideEditor},
|
||||||
Background, BackgroundKind,
|
Background, BackgroundKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::presenter::slide_view;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SongEditor {
|
pub struct SongEditor {
|
||||||
|
@ -327,6 +329,7 @@ impl SongEditor {
|
||||||
self.slide_state
|
self.slide_state
|
||||||
.view(Font::with_name("Quicksand Bold"))
|
.view(Font::with_name("Quicksand Bold"))
|
||||||
.map(|_s| Message::None)
|
.map(|_s| Message::None)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn left_column(&self) -> Element<Message> {
|
fn left_column(&self) -> Element<Message> {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
|
io::Read,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
@ -12,7 +13,7 @@ use cosmic::{
|
||||||
ContentFit, Length, Size,
|
ContentFit, Length, Size,
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widget::{image::Handle, Image},
|
widget::{container, image::Handle, Image},
|
||||||
};
|
};
|
||||||
use rapidhash::v3::rapidhash_v3;
|
use rapidhash::v3::rapidhash_v3;
|
||||||
use resvg::{
|
use resvg::{
|
||||||
|
@ -311,7 +312,7 @@ impl TextSvg {
|
||||||
|
|
||||||
debug!("text string built...");
|
debug!("text string built...");
|
||||||
let resvg_tree = Tree::from_data(
|
let resvg_tree = Tree::from_data(
|
||||||
final_svg.as_bytes(),
|
&final_svg.as_bytes(),
|
||||||
&resvg::usvg::Options {
|
&resvg::usvg::Options {
|
||||||
fontdb: Arc::clone(&self.fontdb),
|
fontdb: Arc::clone(&self.fontdb),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -384,7 +385,7 @@ pub fn text_svg_generator(
|
||||||
slide: &mut crate::core::slide::Slide,
|
slide: &mut crate::core::slide::Slide,
|
||||||
fontdb: Arc<fontdb::Database>,
|
fontdb: Arc<fontdb::Database>,
|
||||||
) {
|
) {
|
||||||
if !slide.text().is_empty() {
|
if slide.text().len() > 0 {
|
||||||
let text_svg = TextSvg::new(slide.text())
|
let text_svg = TextSvg::new(slide.text())
|
||||||
.alignment(slide.text_alignment())
|
.alignment(slide.text_alignment())
|
||||||
.fill("#fff")
|
.fill("#fff")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue