Compare commits

...

2 commits

Author SHA1 Message Date
2413b96791 cargo clippy fix
Some checks are pending
/ test (push) Waiting to run
2025-09-15 11:04:49 -05:00
191dd0255d remove pdf module for inserting the handles as a slide field
This ensures that there isn't a need to duplicate tracking which index
we have and which one is currently active, instead we pre create all
the handles.
2025-09-15 11:00:38 -05:00
11 changed files with 107 additions and 256 deletions

View file

@ -1,12 +1,13 @@
use cosmic::widget::image::Handle;
use crisp::types::{Keyword, Symbol, Value};
use miette::{IntoDiagnostic, Result};
use mupdf::{Document, Page};
use mupdf::{Colorspace, Document, Matrix};
use serde::{Deserialize, Serialize};
use sqlx::{
pool::PoolConnection, prelude::FromRow, query, sqlite::SqliteRow,
Row, Sqlite, SqliteConnection, SqlitePool,
};
use std::{path::PathBuf, sync::Arc};
use std::path::PathBuf;
use tracing::{debug, error};
use crate::{Background, Slide, SlideBuilder, TextAlignment};
@ -131,13 +132,36 @@ impl ServiceTrait for Presentation {
let background = Background::try_from(self.path.clone())
.into_diagnostic()?;
debug!(?background);
let doc = Document::open(background.path.as_path())
let document = Document::open(background.path.as_path())
.into_diagnostic()?;
debug!(?doc);
let pages = doc.page_count().into_diagnostic()?;
debug!(?document);
let pages = document.pages().into_diagnostic()?;
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![];
for page in 0..pages {
for (index, page) in pages.into_iter().enumerate() {
let slide = SlideBuilder::new()
.background(
Background::try_from(self.path.clone())
@ -151,7 +175,8 @@ impl ServiceTrait for Presentation {
.video_loop(false)
.video_start_time(0.0)
.video_end_time(0.0)
.pdf_index(page as u32)
.pdf_index(index as u32)
.pdf_page(page)
.build()?;
slides.push(slide);
}

View file

@ -1,12 +1,10 @@
use std::borrow::Cow;
use std::cmp::Ordering;
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
use crisp::types::{Keyword, Symbol, Value};
use miette::{IntoDiagnostic, Result};
use resvg::usvg::fontdb;
use serde::{Deserialize, Serialize};
use tracing::{debug, error};
@ -260,7 +258,7 @@ impl From<&Song> for ServiceItem {
kind: ServiceItemKind::Song(song.clone()),
database_id: song.id,
title: song.title.clone(),
slides: slides.into(),
slides: slides,
..Default::default()
}
} else {
@ -281,7 +279,7 @@ impl From<&Video> for ServiceItem {
kind: ServiceItemKind::Video(video.clone()),
database_id: video.id,
title: video.title.clone(),
slides: slides.into(),
slides: slides,
..Default::default()
}
} else {
@ -302,7 +300,7 @@ impl From<&Image> for ServiceItem {
kind: ServiceItemKind::Image(image.clone()),
database_id: image.id,
title: image.title.clone(),
slides: slides.into(),
slides: slides,
..Default::default()
}
} else {
@ -325,7 +323,7 @@ impl From<&Presentation> for ServiceItem {
),
database_id: presentation.id,
title: presentation.title.clone(),
slides: slides.into(),
slides: slides,
..Default::default()
},
Err(e) => {

View file

@ -1,20 +1,16 @@
use cosmic::widget::image::Handle;
// use cosmic::dialog::ashpd::url::Url;
use crisp::types::{Keyword, Symbol, Value};
use iced_video_player::Video;
use miette::{miette, Result};
use resvg::usvg::fontdb;
use serde::{Deserialize, Serialize};
use std::{
fmt::Display,
path::{Path, PathBuf},
sync::Arc,
};
use tracing::error;
use crate::ui::{
pdf::PdfViewer,
text_svg::{self, TextSvg},
};
use crate::ui::text_svg::TextSvg;
use super::songs::Song;
@ -34,6 +30,8 @@ pub struct Slide {
video_end_time: f32,
pdf_index: u32,
#[serde(skip)]
pdf_page: Option<Handle>,
#[serde(skip)]
pub text_svg: Option<TextSvg>,
}
@ -307,7 +305,7 @@ impl Slide {
}
pub fn text_alignment(&self) -> TextAlignment {
self.text_alignment.clone()
self.text_alignment
}
pub fn font_size(&self) -> i32 {
@ -326,6 +324,10 @@ impl Slide {
self.audio.clone()
}
pub fn pdf_page(&self) -> Option<Handle> {
self.pdf_page.clone()
}
pub fn pdf_index(&self) -> u32 {
self.pdf_index
}
@ -547,6 +549,8 @@ pub struct SlideBuilder {
video_end_time: Option<f32>,
pdf_index: Option<u32>,
#[serde(skip)]
pdf_page: Option<Handle>,
#[serde(skip)]
text_svg: Option<TextSvg>,
}
@ -629,6 +633,11 @@ impl SlideBuilder {
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(
mut self,
pdf_index: impl Into<u32>,
@ -674,6 +683,7 @@ impl SlideBuilder {
video_end_time,
text_svg: self.text_svg,
pdf_index: self.pdf_index.unwrap_or_default(),
pdf_page: self.pdf_page,
..Default::default()
})
}

View file

@ -7,7 +7,7 @@ use cosmic::app::{Core, Settings, Task};
use cosmic::iced::alignment::Vertical;
use cosmic::iced::keyboard::{Key, Modifiers};
use cosmic::iced::window::{Mode, Position};
use cosmic::iced::{self, event, window, Color, Length, Point};
use cosmic::iced::{self, event, window, Length, Point};
use cosmic::iced_futures::Subscription;
use cosmic::iced_widget::{column, row, stack};
use cosmic::theme;
@ -40,7 +40,7 @@ use ui::song_editor::{self, SongEditor};
use ui::EditorMode;
use crate::core::kinds::ServiceItemKind;
use crate::ui::text_svg::{self, shadow, stroke, TextSvg};
use crate::ui::text_svg::{self};
pub mod core;
pub mod lisp;
@ -985,7 +985,7 @@ impl cosmic::Application for App {
}
Message::Search(query) => {
self.search_query = query.clone();
return self.search(query);
self.search(query)
}
Message::UpdateSearchResults(items) => {
self.search_results = items;
@ -1206,14 +1206,14 @@ where
fn search(&self, query: String) -> Task<Message> {
if let Some(library) = self.library.clone() {
return Task::perform(
Task::perform(
async move { library.search_items(query).await },
|items| {
cosmic::Action::App(Message::UpdateSearchResults(
items,
))
},
);
)
} else {
Task::none()
}
@ -1378,7 +1378,7 @@ where
.data_received_for::<ServiceItem>(|item| {
item.map_or_else(
|| Message::None,
|item| Message::AppendServiceItem(item),
Message::AppendServiceItem,
)
})
.on_finish(
@ -1396,7 +1396,7 @@ where
]
.padding(10)
.spacing(10);
let mut container = Container::new(column)
let container = Container::new(column)
// .height(Length::Fill)
.style(nav_bar_style);

View file

@ -264,7 +264,8 @@ impl<'a> Library {
let video_library = self.library_item(&self.video_library);
let presentation_library =
self.library_item(&self.presentation_library);
let column = column![
column![
text::heading("Library").center().width(Length::Fill),
cosmic::iced::widget::horizontal_rule(1),
song_library,
@ -275,8 +276,7 @@ impl<'a> Library {
.height(Length::Fill)
.padding(10)
.spacing(10)
.into();
column
.into()
}
pub fn library_item<T>(
@ -481,25 +481,23 @@ impl<'a> Library {
.accent_text_color()
.into()
}
} else {
if let Some((library, selected)) = self.selected_item
} else if let Some((library, selected)) = self.selected_item
{
if model.kind == library
&& selected == index as i32
{
if model.kind == library
&& selected == index as i32
{
theme::active().cosmic().control_0().into()
} else {
theme::active()
.cosmic()
.destructive_text_color()
.into()
}
theme::active().cosmic().control_0().into()
} else {
theme::active()
.cosmic()
.destructive_text_color()
.into()
}
} else {
theme::active()
.cosmic()
.destructive_text_color()
.into()
};
text::body(elide_text(item.subtext(), size.width))
.center()
@ -529,20 +527,18 @@ impl<'a> Library {
&& selected == index as i32
{
t.cosmic().accent.selected.into()
} else {
if let Some((library, hovered)) =
self.hovered_item
} else if let Some((library, hovered)) =
self.hovered_item
{
if model.kind == library
&& hovered == index as i32
{
if model.kind == library
&& hovered == index as i32
{
t.cosmic().button.hover.into()
} else {
t.cosmic().button.base.into()
}
t.cosmic().button.hover.into()
} else {
t.cosmic().button.base.into()
}
} else {
t.cosmic().button.base.into()
}
} else if let Some((library, hovered)) =
self.hovered_item

View file

@ -2,7 +2,6 @@ use crate::core::model::LibraryKind;
pub mod double_ended_slider;
pub mod library;
pub mod pdf;
pub mod presenter;
pub mod slide_editor;
pub mod song_editor;

View file

@ -1,161 +0,0 @@
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(),
)
}
}

View file

@ -3,23 +3,18 @@ use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc};
use cosmic::{
iced::{
alignment::Horizontal,
border,
font::{Family, Stretch, Style, Weight},
Background, Border, Color, ContentFit, Font, Length, Shadow,
Vector,
},
iced_widget::{
rich_text,
scrollable::{
scroll_to, AbsoluteOffset, Direction, Scrollbar,
},
span, stack, vertical_rule,
}, stack, vertical_rule,
},
prelude::*,
widget::{
container, image, mouse_area, responsive, scrollable, text,
Column, Container, Id, Image, Row, Space,
container, image, mouse_area, responsive, scrollable, text, Container, Id, Row, Space,
},
Task,
};
@ -33,8 +28,6 @@ use crate::{
BackgroundKind,
};
use crate::ui::pdf::PdfViewer;
const REFERENCE_WIDTH: f32 = 1920.0;
const REFERENCE_HEIGHT: f32 = 1080.0;
@ -53,7 +46,6 @@ pub(crate) struct Presenter {
hovered_slide: Option<(usize, usize)>,
scroll_id: Id,
current_font: Font,
pdf_viewer: PdfViewer,
}
pub(crate) enum Action {
@ -174,7 +166,6 @@ impl Presenter {
},
scroll_id: Id::unique(),
current_font: cosmic::font::default(),
pdf_viewer: PdfViewer::default(),
}
}
@ -225,11 +216,6 @@ impl Presenter {
self.reset_video();
}
if slide.background().kind == BackgroundKind::Pdf {
self.pdf_viewer
.insert_pdf(&slide.background().path);
}
let offset = AbsoluteOffset {
x: {
if self.current_slide_index > 2 {
@ -411,7 +397,6 @@ impl Presenter {
slide_view(
&self.current_slide,
&self.video,
&self.pdf_viewer,
self.current_font,
false,
true,
@ -422,7 +407,6 @@ impl Presenter {
slide_view(
&self.current_slide,
&self.video,
&self.pdf_viewer,
self.current_font,
false,
false,
@ -457,9 +441,8 @@ impl Presenter {
);
let container = slide_view(
&slide,
slide,
&self.video,
&self.pdf_viewer,
font,
true,
false,
@ -722,7 +705,6 @@ fn scale_font(font_size: f32, width: f32) -> f32 {
pub(crate) fn slide_view<'a>(
slide: &'a Slide,
video: &'a Option<Video>,
pdf_viewer: &'a PdfViewer,
font: Font,
delegate: bool,
hide_mouse: bool,
@ -799,17 +781,24 @@ pub(crate) fn slide_view<'a>(
Container::new(Space::new(0, 0))
}
}
BackgroundKind::Pdf => Container::new(
if let Some(pdf) = pdf_viewer.view(slide.pdf_index())
{
pdf.map(|_| Message::None)
BackgroundKind::Pdf => {
if let Some(pdf) = slide.pdf_page() {
Container::new(
image(pdf)
.content_fit(ContentFit::Cover)
.width(width)
.height(size.height),
)
.center_x(width)
.center_y(size.height)
.clip(true)
} else {
Space::new(0.0, 0.0).into()
},
)
.center_x(width)
.center_y(size.height)
.clip(true),
Container::new(Space::new(0.0, 0.0))
.center_x(width)
.center_y(size.height)
.clip(true)
}
}
BackgroundKind::Html => todo!(),
};
let stack = stack!(

View file

@ -2,11 +2,10 @@ use std::{io, path::PathBuf};
use cosmic::{
iced::{Color, Font, Length, Size},
prelude::*,
widget::{
self,
canvas::{self, Program, Stroke},
container, Canvas,
container,
},
Renderer,
};

View file

@ -10,8 +10,7 @@ use cosmic::{
iced_widget::row,
theme,
widget::{
button, column, combo_box, container, horizontal_space, icon,
scrollable, text, text_editor, text_input,
button, column, combo_box, container, horizontal_space, icon, text, text_editor, text_input,
},
Element, Task,
};
@ -21,11 +20,10 @@ use tracing::{debug, error};
use crate::{
core::{service_items::ServiceTrait, songs::Song},
ui::slide_editor::{self, SlideEditor},
ui::slide_editor::SlideEditor,
Background, BackgroundKind,
};
use super::presenter::slide_view;
#[derive(Debug)]
pub struct SongEditor {
@ -329,7 +327,6 @@ impl SongEditor {
self.slide_state
.view(Font::with_name("Quicksand Bold"))
.map(|_s| Message::None)
.into()
}
fn left_column(&self) -> Element<Message> {

View file

@ -1,7 +1,6 @@
use std::{
fmt::Display,
hash::{Hash, Hasher},
io::Read,
path::PathBuf,
sync::Arc,
};
@ -13,7 +12,7 @@ use cosmic::{
ContentFit, Length, Size,
},
prelude::*,
widget::{container, image::Handle, Image},
widget::{image::Handle, Image},
};
use rapidhash::v3::rapidhash_v3;
use resvg::{
@ -312,7 +311,7 @@ impl TextSvg {
debug!("text string built...");
let resvg_tree = Tree::from_data(
&final_svg.as_bytes(),
final_svg.as_bytes(),
&resvg::usvg::Options {
fontdb: Arc::clone(&self.fontdb),
..Default::default()
@ -385,7 +384,7 @@ pub fn text_svg_generator(
slide: &mut crate::core::slide::Slide,
fontdb: Arc<fontdb::Database>,
) {
if slide.text().len() > 0 {
if !slide.text().is_empty() {
let text_svg = TextSvg::new(slide.text())
.alignment(slide.text_alignment())
.fill("#fff")