more things, but mostly starting to add library management
Some checks are pending
/ test (push) Waiting to run

This commit is contained in:
Chris Cochrun 2025-09-15 14:42:07 -05:00
parent 3fe77c93e2
commit 645411b59c
13 changed files with 341 additions and 260 deletions

View file

@ -1,3 +1,5 @@
use std::collections::HashMap;
use cosmic::{
iced::{
alignment::Vertical, clipboard::dnd::DndAction,
@ -7,9 +9,10 @@ use cosmic::{
iced_widget::{column, row as rowm, text as textm},
theme,
widget::{
button, container, horizontal_space, icon, mouse_area,
responsive, row, scrollable, text, text_input, Container,
DndSource, Space, Widget,
button, container, context_menu, horizontal_space, icon,
menu::{self, Action as MenuAction},
mouse_area, responsive, row, scrollable, text, text_input,
Container, DndSource, Space,
},
Element, Task,
};
@ -41,6 +44,29 @@ pub(crate) struct Library {
pub dragged_item: Option<(LibraryKind, i32)>,
editing_item: Option<(LibraryKind, i32)>,
db: SqlitePool,
menu_keys: std::collections::HashMap<menu::KeyBind, MenuMessage>,
context_menu: Option<i32>,
}
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
enum MenuMessage {
Delete((LibraryKind, i32)),
Open,
None,
}
impl MenuAction for MenuMessage {
type Message = Message;
fn message(&self) -> Self::Message {
match self {
MenuMessage::Delete((kind, index)) => {
Message::DeleteItem((*kind, *index))
}
MenuMessage::Open => todo!(),
MenuMessage::None => todo!(),
}
}
}
pub(crate) enum Action {
@ -53,7 +79,7 @@ pub(crate) enum Action {
#[derive(Clone, Debug)]
pub(crate) enum Message {
AddItem,
RemoveItem,
DeleteItem((LibraryKind, i32)),
OpenItem(Option<(LibraryKind, i32)>),
HoverLibrary(Option<LibraryKind>),
OpenLibrary(Option<LibraryKind>),
@ -69,6 +95,7 @@ pub(crate) enum Message {
UpdatePresentation(Presentation),
PresentationChanged,
Error(String),
OpenContext(i32),
None,
}
@ -90,6 +117,8 @@ impl<'a> Library {
dragged_item: None,
editing_item: None,
db,
menu_keys: HashMap::new(),
context_menu: None,
}
}
@ -101,7 +130,16 @@ impl<'a> Library {
match message {
Message::AddItem => (),
Message::None => (),
Message::RemoveItem => (),
Message::DeleteItem((kind, index)) => {
match kind {
LibraryKind::Song => todo!(),
LibraryKind::Video => todo!(),
LibraryKind::Image => todo!(),
LibraryKind::Presentation => {
self.presentation_library.remove_item(index);
}
};
}
Message::OpenItem(item) => {
debug!(?item);
self.editing_item = item;
@ -144,7 +182,7 @@ impl<'a> Library {
Task::future(self.db.acquire()).and_then(
move |conn| update_in_db(&song, conn),
),
)
);
}
Err(_) => todo!(),
}
@ -181,7 +219,7 @@ impl<'a> Library {
)
},
),
)
);
}
Err(_) => todo!(),
}
@ -215,7 +253,7 @@ impl<'a> Library {
)
},
),
)
);
}
Err(_) => todo!(),
}
@ -254,6 +292,9 @@ impl<'a> Library {
}
Message::PresentationChanged => (),
Message::Error(_) => (),
Message::OpenContext(index) => {
self.context_menu = Some(index);
}
}
Action::None
}
@ -264,7 +305,7 @@ impl<'a> Library {
let video_library = self.library_item(&self.video_library);
let presentation_library =
self.library_item(&self.presentation_library);
column![
text::heading("Library").center().width(Length::Fill),
cosmic::iced::widget::horizontal_rule(1),
@ -374,28 +415,46 @@ impl<'a> Library {
let visual_item = self
.single_item(index, item, model)
.map(|()| Message::None);
DndSource::<Message, ServiceItem>::new(
mouse_area(visual_item)
.on_drag(Message::DragItem(service_item.clone()))
.on_enter(Message::HoverItem(
Some((
model.kind,
index as i32,
)),
))
.on_double_click(
Message::OpenItem(Some((
model.kind,
index as i32,
))),
)
.on_exit(Message::HoverItem(None))
.on_press(Message::SelectItem(
Some((
model.kind,
index as i32,
)),
)),
DndSource::<Message, ServiceItem>::new({
let mouse_area = Element::from(mouse_area(visual_item)
.on_drag(Message::DragItem(service_item.clone()))
.on_enter(Message::HoverItem(
Some((
model.kind,
index as i32,
)),
))
.on_double_click(
Message::OpenItem(Some((
model.kind,
index as i32,
))),
)
.on_right_press(Message::OpenContext(index as i32))
.on_exit(Message::HoverItem(None))
.on_press(Message::SelectItem(
Some((
model.kind,
index as i32,
)),
)));
if let Some(context_id) = self.context_menu {
if index == context_id as usize {
let context_menu = context_menu(
mouse_area,
self.context_menu.map_or_else(|| None, |id| {
Some(menu::items(&self.menu_keys,
vec![menu::Item::Button("Delete", None, MenuMessage::Delete((model.kind, index as i32)))]))
})
);
Element::from(context_menu)
} else {
Element::from(mouse_area)
}
} else {
Element::from(mouse_area)
}
}
)
.action(DndAction::Copy)
.drag_icon({
@ -403,8 +462,8 @@ impl<'a> Library {
move |i| {
let state = State::None;
let icon = match model {
LibraryKind::Song => icon::from_name(
"folder-music-symbolic",
LibraryKind::Song => icon::from_name(
"folder-music-symbolic",
).symbolic(true)
,
LibraryKind::Video => icon::from_name("folder-videos-symbolic"),
@ -481,11 +540,10 @@ 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()
@ -563,6 +621,7 @@ impl<'a> Library {
.into()
}
#[allow(clippy::unused_async)]
pub async fn search_items(
&self,
query: String,
@ -573,14 +632,18 @@ impl<'a> Library {
.items
.iter()
.filter(|song| song.title.to_lowercase().contains(&query))
.map(super::super::core::content::Content::to_service_item)
.map(
super::super::core::content::Content::to_service_item,
)
.collect();
let videos: Vec<ServiceItem> = self
.video_library
.items
.iter()
.filter(|vid| vid.title.to_lowercase().contains(&query))
.map(super::super::core::content::Content::to_service_item)
.map(
super::super::core::content::Content::to_service_item,
)
.collect();
let images: Vec<ServiceItem> = self
.image_library
@ -589,14 +652,18 @@ impl<'a> Library {
.filter(|image| {
image.title.to_lowercase().contains(&query)
})
.map(super::super::core::content::Content::to_service_item)
.map(
super::super::core::content::Content::to_service_item,
)
.collect();
let presentations: Vec<ServiceItem> = self
.presentation_library
.items
.iter()
.filter(|pres| pres.title.to_lowercase().contains(&query))
.map(super::super::core::content::Content::to_service_item)
.map(
super::super::core::content::Content::to_service_item,
)
.collect();
items.extend(videos);
items.extend(images);

View file

@ -221,7 +221,8 @@ impl Presenter {
let offset = AbsoluteOffset {
x: {
if self.current_slide_index > 2 {
(self.current_slide_index as f32).mul_add(187.5, -187.5)
(self.current_slide_index as f32)
.mul_add(187.5, -187.5)
} else {
0.0
}

View file

@ -1,30 +1,29 @@
use std::{io, path::PathBuf, sync::Arc};
use cosmic::{
Element, Task,
dialog::file_chooser::open::Dialog,
iced::{
font::{Family, Stretch, Style, Weight},
Font, Length,
font::{Family, Stretch, Style, Weight},
},
iced_wgpu::graphics::text::cosmic_text::fontdb,
iced_widget::row,
theme,
widget::{
button, column, combo_box, container, horizontal_space, icon, text, text_editor, text_input,
button, column, combo_box, container, horizontal_space, icon,
text, text_editor, text_input,
},
Element, Task,
};
use dirs::font_dir;
use iced_video_player::Video;
use tracing::{debug, error};
use crate::{
core::{service_items::ServiceTrait, songs::Song},
Background, BackgroundKind, core::songs::Song,
ui::slide_editor::SlideEditor,
Background, BackgroundKind,
};
#[derive(Debug)]
pub struct SongEditor {
pub song: Option<Song>,
@ -244,8 +243,7 @@ impl SongEditor {
Message::ChangeBackground(Ok(path)) => {
debug!(?path);
if let Some(mut song) = self.song.clone() {
let background =
Background::try_from(path).ok();
let background = Background::try_from(path).ok();
self.background_video(&background);
song.background = background;
return self.update_song(song);
@ -258,7 +256,7 @@ impl SongEditor {
return Action::Task(Task::perform(
pick_background(),
Message::ChangeBackground,
))
));
}
_ => (),
}

View file

@ -121,15 +121,18 @@ impl From<&str> for Font {
}
impl Font {
#[must_use] pub fn get_name(&self) -> String {
#[must_use]
pub fn get_name(&self) -> String {
self.name.clone()
}
#[must_use] pub const fn get_weight(&self) -> Weight {
#[must_use]
pub const fn get_weight(&self) -> Weight {
self.weight
}
#[must_use] pub const fn get_style(&self) -> Style {
#[must_use]
pub const fn get_style(&self) -> Style {
self.style
}
@ -148,7 +151,8 @@ impl Font {
self
}
#[must_use] pub const fn size(mut self, size: u8) -> Self {
#[must_use]
pub const fn size(mut self, size: u8) -> Self {
self.size = size;
self
}
@ -236,7 +240,10 @@ impl TextSvg {
self
}
pub const fn alignment(mut self, alignment: TextAlignment) -> Self {
pub const fn alignment(
mut self,
alignment: TextAlignment,
) -> Self {
self.alignment = alignment;
self
}
@ -272,8 +279,8 @@ impl TextSvg {
let middle_position = size.height / 2.0;
let line_spacing = 10.0;
let text_and_line_spacing = font_size + line_spacing;
let starting_y_position =
half_lines.mul_add(-text_and_line_spacing, middle_position);
let starting_y_position = half_lines
.mul_add(-text_and_line_spacing, middle_position);
let text_pieces: Vec<String> = self
.text
@ -282,7 +289,10 @@ impl TextSvg {
.map(|(index, text)| {
format!(
"<tspan x=\"50%\" y=\"{}\">{}</tspan>",
(index as f32).mul_add(text_and_line_spacing, starting_y_position),
(index as f32).mul_add(
text_and_line_spacing,
starting_y_position
),
text
)
})