feat: Presentation editor now shows all pages made asynchronously
Some checks are pending
/ test (push) Waiting to run

Still needs some work to make sure that they are clickable to go to
the correct page and a right click menu to enable extra editing of the
presentation.

Eventually we can add splitting the presentation or removing/skipping
pages when actually presenting.
This commit is contained in:
Chris Cochrun 2025-10-17 10:34:51 -05:00
parent 67a984a761
commit d1ec3a8585

View file

@ -1,4 +1,4 @@
use std::{io, path::PathBuf}; use std::{io, path::Path, path::PathBuf};
use crate::core::presentations::Presentation; use crate::core::presentations::Presentation;
use cosmic::{ use cosmic::{
@ -9,7 +9,7 @@ use cosmic::{
theme, theme,
widget::{ widget::{
self, Space, button, container, horizontal_space, icon, self, Space, button, container, horizontal_space, icon,
image::Handle, text, text_input, image::Handle, scrollable, text, text_input,
}, },
}; };
use miette::IntoDiagnostic; use miette::IntoDiagnostic;
@ -45,6 +45,7 @@ pub enum Message {
PrevPage, PrevPage,
None, None,
ChangePresentationFile(Presentation), ChangePresentationFile(Presentation),
AddSlides(Option<Vec<Handle>>),
} }
impl PresentationEditor { impl PresentationEditor {
@ -64,6 +65,14 @@ impl PresentationEditor {
match message { match message {
Message::ChangePresentation(presentation) => { Message::ChangePresentation(presentation) => {
self.update_entire_presentation(&presentation); self.update_entire_presentation(&presentation);
if let Some(presentation) = self.presentation.clone()
{
let task = Task::perform(
get_all_pages(presentation.path.clone()),
|pages| Message::AddSlides(pages),
);
return Action::Task(task);
}
} }
Message::ChangeTitle(title) => { Message::ChangeTitle(title) => {
self.title = title.clone(); self.title = title.clone();
@ -108,7 +117,20 @@ impl PresentationEditor {
} }
Message::ChangePresentationFile(presentation) => { Message::ChangePresentationFile(presentation) => {
self.update_entire_presentation(&presentation); self.update_entire_presentation(&presentation);
return self.update(Message::Update(presentation)); if let Some(presentation) = self.presentation.clone()
{
let task = Task::perform(
get_all_pages(presentation.path.clone()),
|pages| Message::AddSlides(pages),
)
.chain(Task::done(Message::Update(
presentation.clone(),
)));
return Action::Task(task);
}
}
Message::AddSlides(slides) => {
self.slides = slides;
} }
Message::None => (), Message::None => (),
Message::NextPage => { Message::NextPage => {
@ -199,6 +221,7 @@ impl PresentationEditor {
.iter() .iter()
.map(|page| { .map(|page| {
let image = widget::image(page) let image = widget::image(page)
.height(theme::spacing().space_xxxl * 3)
.content_fit(ContentFit::ScaleDown); .content_fit(ContentFit::ScaleDown);
container(image).into() container(image).into()
}) })
@ -206,14 +229,14 @@ impl PresentationEditor {
} else { } else {
vec![horizontal_space().into()] vec![horizontal_space().into()]
}; };
let pages_column = container( let pages_column = container(scrollable(
column(pdf_pages) column(pdf_pages)
.spacing(theme::active().cosmic().space_xs()) .spacing(theme::active().cosmic().space_xs())
.padding(theme::spacing().space_l), .padding(theme::spacing().space_l),
) ))
.class(theme::Container::Card); .class(theme::Container::Card);
let main_row = row![ let main_row = row![
pages_column.width(Length::FillPortion(1)), pages_column,
presentation.center(Length::FillPortion(2)) presentation.center(Length::FillPortion(2))
] ]
.spacing(theme::spacing().space_xxl); .spacing(theme::spacing().space_xxl);
@ -282,8 +305,8 @@ impl PresentationEditor {
}; };
debug!(?pixmap); debug!(?pixmap);
Some(Handle::from_rgba( Some(Handle::from_rgba(
pixmap.width(), pixmap.width() / 3,
pixmap.height(), pixmap.height() / 3,
pixmap.samples().to_vec(), pixmap.samples().to_vec(),
)) ))
}); });
@ -297,6 +320,35 @@ impl Default for PresentationEditor {
} }
} }
async fn get_all_pages(
presentation_path: impl AsRef<Path>,
) -> Option<Vec<Handle>> {
let document = Document::open(presentation_path.as_ref()).ok()?;
let pages = document.pages().ok()?;
Some(
pages
.filter_map(|page| {
let page = page.ok()?;
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;
};
Some(Handle::from_rgba(
pixmap.width(),
pixmap.height(),
pixmap.samples().to_vec(),
))
})
.collect(),
)
}
async fn pick_presentation() -> Result<PathBuf, PresentationError> { async fn pick_presentation() -> Result<PathBuf, PresentationError> {
let dialog = Dialog::new().title("Choose a presentation..."); let dialog = Dialog::new().title("Choose a presentation...");
let bg_filter = FileFilter::new("Presentations") let bg_filter = FileFilter::new("Presentations")