diff --git a/res/chad.png b/res/chad.png
new file mode 100644
index 0000000..44f21f3
Binary files /dev/null and b/res/chad.png differ
diff --git a/res/list-add-above.svg b/res/list-add-above.svg
new file mode 100644
index 0000000..27f02ba
--- /dev/null
+++ b/res/list-add-above.svg
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/list-add-below.svg b/res/list-add-below.svg
new file mode 100644
index 0000000..99adb9e
--- /dev/null
+++ b/res/list-add-below.svg
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/split-above.svg b/res/split-above.svg
new file mode 100644
index 0000000..b105436
--- /dev/null
+++ b/res/split-above.svg
@@ -0,0 +1,54 @@
+
+
+
+
diff --git a/res/split-below.svg b/res/split-below.svg
new file mode 100644
index 0000000..072feb7
--- /dev/null
+++ b/res/split-below.svg
@@ -0,0 +1,60 @@
+
+
+
+
diff --git a/src/ui/presentation_editor.rs b/src/ui/presentation_editor.rs
index 8088bea..3499cec 100644
--- a/src/ui/presentation_editor.rs
+++ b/src/ui/presentation_editor.rs
@@ -1,15 +1,20 @@
-use std::{io, path::Path, path::PathBuf};
+use std::{
+ collections::HashMap,
+ io,
+ path::{Path, PathBuf},
+};
use crate::core::presentations::Presentation;
use cosmic::{
Element, Task,
dialog::file_chooser::{FileFilter, open::Dialog},
- iced::{ContentFit, Length, alignment::Vertical},
+ iced::{Background, ContentFit, Length, alignment::Vertical},
iced_widget::{column, row},
theme,
widget::{
- self, Space, button, container, horizontal_space, icon,
- image::Handle, scrollable, text, text_input,
+ self, Space, button, container, context_menu,
+ horizontal_space, icon, image::Handle, menu, mouse_area,
+ scrollable, text, text_input,
},
};
use miette::IntoDiagnostic;
@@ -26,6 +31,8 @@ pub struct PresentationEditor {
current_slide_index: Option,
title: String,
editing: bool,
+ hovered_slide: Option,
+ context_menu_id: Option,
}
pub enum Action {
@@ -46,6 +53,28 @@ pub enum Message {
None,
ChangePresentationFile(Presentation),
AddSlides(Option>),
+ ChangeSlide(usize),
+ HoverSlide(Option),
+ ContextMenu(usize),
+ SplitBefore,
+ SplitAfter,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum MenuAction {
+ SplitBefore,
+ SplitAfter,
+}
+
+impl menu::Action for MenuAction {
+ type Message = Message;
+
+ fn message(&self) -> Self::Message {
+ match self {
+ MenuAction::SplitBefore => Message::SplitBefore,
+ MenuAction::SplitAfter => Message::SplitAfter,
+ }
+ }
}
impl PresentationEditor {
@@ -59,6 +88,8 @@ impl PresentationEditor {
current_slide_index: None,
page_count: None,
slides: None,
+ hovered_slide: None,
+ context_menu_id: None,
}
}
pub fn update(&mut self, message: Message) -> Action {
@@ -179,21 +210,15 @@ impl PresentationEditor {
doc.load_page(previous_index).ok()?;
let matrix = Matrix::IDENTITY;
let colorspace = Colorspace::device_rgb();
- let Ok(pixmap) = page
+ let pixmap = page
.to_pixmap(
&matrix,
&colorspace,
true,
true,
)
- .into_diagnostic()
- else {
- error!(
- "Can't turn this page into pixmap"
- );
- return None;
- };
- debug!(?pixmap);
+ .ok()?;
+
Some(Handle::from_rgba(
pixmap.width(),
pixmap.height(),
@@ -202,6 +227,50 @@ impl PresentationEditor {
});
self.current_slide_index = Some(previous_index);
}
+ Message::ChangeSlide(index) => {
+ self.current_slide =
+ self.document.as_ref().and_then(|doc| {
+ let page =
+ doc.load_page(index as i32).ok()?;
+ let matrix = Matrix::IDENTITY;
+ let colorspace = Colorspace::device_rgb();
+ let pixmap = page
+ .to_pixmap(
+ &matrix,
+ &colorspace,
+ true,
+ true,
+ )
+ .ok()?;
+
+ Some(Handle::from_rgba(
+ pixmap.width(),
+ pixmap.height(),
+ pixmap.samples().to_vec(),
+ ))
+ });
+ self.current_slide_index = Some(index as i32);
+ }
+ Message::HoverSlide(slide) => {
+ self.hovered_slide = slide;
+ }
+ Message::ContextMenu(index) => {
+ self.context_menu_id = Some(index as i32);
+ }
+ Message::SplitBefore => {
+ if let Some(index) = self.context_menu_id {
+ debug!("split before {index}");
+ } else {
+ error!("split before no index");
+ }
+ }
+ Message::SplitAfter => {
+ if let Some(index) = self.context_menu_id {
+ debug!("split after {index}");
+ } else {
+ error!("split after no index");
+ }
+ }
}
Action::None
}
@@ -212,32 +281,75 @@ impl PresentationEditor {
widget::image(slide)
.content_fit(ContentFit::ScaleDown),
)
+ .style(|_| {
+ container::background(Background::Color(
+ cosmic::iced::Color::WHITE,
+ ))
+ })
} else {
container(Space::new(0, 0))
};
- let pdf_pages: Vec> =
- if let Some(pages) = &self.slides {
- pages
- .iter()
- .map(|page| {
- let image = widget::image(page)
- .height(theme::spacing().space_xxxl * 3)
- .content_fit(ContentFit::ScaleDown);
- container(image).into()
- })
- .collect()
- } else {
- vec![horizontal_space().into()]
- };
- let pages_column = container(scrollable(
- column(pdf_pages)
- .spacing(theme::active().cosmic().space_xs())
- .padding(theme::spacing().space_l),
- ))
+ let pdf_pages: Vec> = if let Some(pages) =
+ &self.slides
+ {
+ pages
+ .iter()
+ .enumerate()
+ .map(|(index, page)| {
+ let image = widget::image(page)
+ .height(theme::spacing().space_xxxl * 3)
+ .content_fit(ContentFit::ScaleDown);
+ let slide = container(image).style(|_| {
+ container::background(Background::Color(
+ cosmic::iced::Color::WHITE,
+ ))
+ });
+ let clickable_slide = container(
+ mouse_area(slide)
+ .on_enter(Message::HoverSlide(Some(
+ index as i32,
+ )))
+ .on_exit(Message::HoverSlide(None))
+ .on_right_press(Message::ContextMenu(
+ index,
+ ))
+ .on_press(Message::ChangeSlide(index)),
+ )
+ .padding(theme::spacing().space_m)
+ .clip(true)
+ .class(
+ if let Some(hovered_index) =
+ self.hovered_slide
+ {
+ if index as i32 == hovered_index {
+ theme::Container::Primary
+ } else {
+ theme::Container::Card
+ }
+ } else {
+ theme::Container::Card
+ },
+ );
+ clickable_slide.into()
+ })
+ .collect()
+ } else {
+ vec![horizontal_space().into()]
+ };
+ let pages_column = container(
+ self.context_menu(
+ scrollable(
+ column(pdf_pages)
+ .spacing(theme::active().cosmic().space_xs())
+ .padding(theme::spacing().space_xs),
+ )
+ .into(),
+ ),
+ )
.class(theme::Container::Card);
let main_row = row![
pages_column,
- presentation.center(Length::FillPortion(2))
+ container(presentation).center(Length::FillPortion(2))
]
.spacing(theme::spacing().space_xxl);
let control_buttons = row![
@@ -279,6 +391,44 @@ impl PresentationEditor {
self.editing
}
+ fn context_menu<'b>(
+ &self,
+ items: Element<'b, Message>,
+ ) -> Element<'b, Message> {
+ if self.context_menu_id.is_some() {
+ let before_icon =
+ icon::from_path("./res/split-above.svg".into())
+ .symbolic(true);
+ let after_icon =
+ icon::from_path("./res/split-below.svg".into())
+ .symbolic(true);
+ let menu_items = vec![
+ menu::Item::Button(
+ "Spit Before",
+ Some(before_icon),
+ MenuAction::SplitBefore,
+ ),
+ menu::Item::Button(
+ "Split After",
+ Some(after_icon),
+ MenuAction::SplitAfter,
+ ),
+ ];
+ let context_menu = context_menu(
+ items,
+ self.context_menu_id.map_or_else(
+ || None,
+ |_| {
+ Some(menu::items(&HashMap::new(), menu_items))
+ },
+ ),
+ );
+ Element::from(context_menu)
+ } else {
+ items
+ }
+ }
+
fn update_entire_presentation(
&mut self,
presentation: &Presentation,
@@ -296,17 +446,13 @@ impl PresentationEditor {
let page = doc.load_page(0).ok()?;
let matrix = Matrix::IDENTITY;
let colorspace = Colorspace::device_rgb();
- let Ok(pixmap) = page
+ let pixmap = page
.to_pixmap(&matrix, &colorspace, true, true)
- .into_diagnostic()
- else {
- error!("Can't turn this page into pixmap");
- return None;
- };
- debug!(?pixmap);
+ .ok()?;
+
Some(Handle::from_rgba(
- pixmap.width() / 3,
- pixmap.height() / 3,
+ pixmap.width(),
+ pixmap.height(),
pixmap.samples().to_vec(),
))
});
@@ -329,16 +475,12 @@ async fn get_all_pages(
pages
.filter_map(|page| {
let page = page.ok()?;
-
let matrix = Matrix::IDENTITY;
let colorspace = Colorspace::device_rgb();
- let Ok(pixmap) = page
+ let pixmap = page
.to_pixmap(&matrix, &colorspace, true, true)
- .into_diagnostic()
- else {
- error!("Can't turn this page into pixmap");
- return None;
- };
+ .ok()?;
+
Some(Handle::from_rgba(
pixmap.width(),
pixmap.height(),