Merge branch 'master' of git.tfcconnection.org:chris/lumina-iced

This commit is contained in:
Chris Cochrun 2025-09-17 09:28:50 -05:00
commit ac00afbfe6
7 changed files with 395 additions and 357 deletions

563
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -20,10 +20,10 @@ strum = "0.26.3"
strum_macros = "0.26.4" strum_macros = "0.26.4"
ron = "0.8.1" ron = "0.8.1"
sqlx = { version = "0.8.2", features = ["sqlite", "runtime-tokio"] } sqlx = { version = "0.8.2", features = ["sqlite", "runtime-tokio"] }
dirs = "5.0.1" dirs = "6.0.0"
tokio = "1.41.1" tokio = "1.41.1"
crisp = { git = "https://git.tfcconnection.org/chris/crisp", version = "0.1.3" } crisp = { git = "https://git.tfcconnection.org/chris/crisp", version = "0.1.3" }
rodio = { version = "0.20.1", features = ["symphonia-all", "tracing"] } rodio = { version = "0.21.1", features = ["symphonia-all", "tracing"] }
gstreamer = "0.23" gstreamer = "0.23"
gstreamer-app = "0.23" gstreamer-app = "0.23"
# gstreamer-video = "0.23" # gstreamer-video = "0.23"

View file

@ -1,6 +1,6 @@
use std::mem::replace; use std::mem::replace;
use miette::{Result, miette}; use miette::{miette, Result};
use sqlx::{Connection, SqliteConnection}; use sqlx::{Connection, SqliteConnection};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -1,4 +1,4 @@
use clap::{Parser, command}; use clap::{command, Parser};
use core::service_items::ServiceItem; use core::service_items::ServiceItem;
use core::slide::{ use core::slide::{
Background, BackgroundKind, Slide, SlideBuilder, TextAlignment, Background, BackgroundKind, Slide, SlideBuilder, TextAlignment,
@ -9,24 +9,24 @@ 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, Length, Point, event, window}; use cosmic::iced::{self, event, window, 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;
use cosmic::widget::Container;
use cosmic::widget::dnd_destination::dnd_destination; use cosmic::widget::dnd_destination::dnd_destination;
use cosmic::widget::nav_bar::nav_bar_style; use cosmic::widget::nav_bar::nav_bar_style;
use cosmic::widget::text;
use cosmic::widget::tooltip::Position as TPosition; use cosmic::widget::tooltip::Position as TPosition;
use cosmic::widget::Container;
use cosmic::widget::{ use cosmic::widget::{
Space, button, horizontal_space, mouse_area, nav_bar, button, horizontal_space, mouse_area, nav_bar, search_input,
search_input, tooltip, vertical_space, tooltip, vertical_space, Space,
}; };
use cosmic::widget::{container, text};
use cosmic::widget::{icon, slider}; use cosmic::widget::{icon, slider};
use cosmic::{Application, ApplicationExt, Element, executor}; use cosmic::{executor, Application, ApplicationExt, Element};
use crisp::types::Value; use crisp::types::Value;
use lisp::parse_lisp; use lisp::parse_lisp;
use miette::{Result, miette}; use miette::{miette, Result};
use rayon::prelude::*; use rayon::prelude::*;
use resvg::usvg::fontdb; use resvg::usvg::fontdb;
use std::fs::read_to_string; use std::fs::read_to_string;
@ -35,10 +35,10 @@ use std::sync::Arc;
use tracing::{debug, level_filters::LevelFilter}; use tracing::{debug, level_filters::LevelFilter};
use tracing::{error, warn}; use tracing::{error, warn};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use ui::EditorMode;
use ui::library::{self, Library}; use ui::library::{self, Library};
use ui::presenter::{self, Presenter}; use ui::presenter::{self, Presenter};
use ui::song_editor::{self, SongEditor}; use ui::song_editor::{self, SongEditor};
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};
@ -1115,7 +1115,9 @@ impl cosmic::Application for App {
]; ];
if let Some(_editor) = &self.editor_mode { if let Some(_editor) = &self.editor_mode {
Element::from(song_editor) container(song_editor)
.padding(cosmic::theme::spacing().space_xxl)
.into()
} else { } else {
Element::from(column) Element::from(column)
} }

View file

@ -19,7 +19,7 @@ use cosmic::{
}; };
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use rapidfuzz::distance::levenshtein; use rapidfuzz::distance::levenshtein;
use sqlx::{pool::PoolConnection, Sqlite, SqlitePool}; use sqlx::{migrate, pool::PoolConnection, Sqlite, SqlitePool};
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
use crate::core::{ use crate::core::{
@ -33,7 +33,7 @@ use crate::core::{
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Library { pub struct Library {
song_library: Model<Song>, song_library: Model<Song>,
image_library: Model<Image>, image_library: Model<Image>,
video_library: Model<Video>, video_library: Model<Video>,
@ -42,7 +42,6 @@ pub(crate) struct Library {
library_hovered: Option<LibraryKind>, library_hovered: Option<LibraryKind>,
selected_item: Option<(LibraryKind, i32)>, selected_item: Option<(LibraryKind, i32)>,
hovered_item: Option<(LibraryKind, i32)>, hovered_item: Option<(LibraryKind, i32)>,
pub dragged_item: Option<(LibraryKind, i32)>,
editing_item: Option<(LibraryKind, i32)>, editing_item: Option<(LibraryKind, i32)>,
db: SqlitePool, db: SqlitePool,
menu_keys: std::collections::HashMap<menu::KeyBind, MenuMessage>, menu_keys: std::collections::HashMap<menu::KeyBind, MenuMessage>,
@ -70,7 +69,7 @@ impl MenuAction for MenuMessage {
} }
} }
pub(crate) enum Action { pub enum Action {
OpenItem(Option<(LibraryKind, i32)>), OpenItem(Option<(LibraryKind, i32)>),
DraggedItem(ServiceItem), DraggedItem(ServiceItem),
Task(Task<Message>), Task(Task<Message>),
@ -103,6 +102,9 @@ pub(crate) enum Message {
impl<'a> Library { impl<'a> Library {
pub async fn new() -> Self { pub async fn new() -> Self {
let mut db = add_db().await.expect("probs"); let mut db = add_db().await.expect("probs");
if let Err(e) = migrate!("./migrations").run(&db).await {
error!(?e)
}
Self { Self {
song_library: Model::new_song_model(&mut db).await, song_library: Model::new_song_model(&mut db).await,
image_library: Model::new_image_model(&mut db).await, image_library: Model::new_image_model(&mut db).await,
@ -115,7 +117,6 @@ impl<'a> Library {
library_hovered: None, library_hovered: None,
selected_item: None, selected_item: None,
hovered_item: None, hovered_item: None,
dragged_item: None,
editing_item: None, editing_item: None,
db, db,
menu_keys: HashMap::new(), menu_keys: HashMap::new(),
@ -298,6 +299,27 @@ impl<'a> Library {
} }
}; };
} }
Message::AddItem => {
let kind =
self.library_open.unwrap_or(LibraryKind::Song);
let item = match kind {
LibraryKind::Song => {
let song = Song::default();
self.song_library
.add_item(song)
.map(|_| {
let index =
self.song_library.items.len();
(LibraryKind::Song, index as i32)
})
.ok()
}
LibraryKind::Video => todo!(),
LibraryKind::Image => todo!(),
LibraryKind::Presentation => todo!(),
};
return self.update(Message::OpenItem(item));
}
Message::OpenItem(item) => { Message::OpenItem(item) => {
debug!(?item); debug!(?item);
self.editing_item = item; self.editing_item = item;
@ -600,7 +622,7 @@ impl<'a> Library {
if index == context_id as usize { if index == context_id as usize {
let context_menu = context_menu( let context_menu = context_menu(
mouse_area, mouse_area,
self.context_menu.map_or_else(|| None, |id| { self.context_menu.map_or_else(|| None, |_| {
Some(menu::items(&self.menu_keys, Some(menu::items(&self.menu_keys,
vec![menu::Item::Button("Delete", None, MenuMessage::Delete((model.kind, index as i32)))])) vec![menu::Item::Button("Delete", None, MenuMessage::Delete((model.kind, index as i32)))]))
}) })

View file

@ -21,7 +21,7 @@ use cosmic::{
Task, Task,
}; };
use iced_video_player::{gst_pbutils, Position, Video, VideoPlayer}; use iced_video_player::{gst_pbutils, Position, Video, VideoPlayer};
use rodio::{Decoder, OutputStream, Sink}; use rodio::{Decoder, OutputStream, OutputStreamBuilder, Sink};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use url::Url; use url::Url;
@ -44,7 +44,7 @@ pub(crate) struct Presenter {
pub video: Option<Video>, pub video: Option<Video>,
pub video_position: f32, pub video_position: f32,
pub audio: Option<PathBuf>, pub audio: Option<PathBuf>,
sink: (OutputStream, Arc<Sink>), sink: (Arc<Sink>, OutputStream),
hovered_slide: Option<(usize, usize)>, hovered_slide: Option<(usize, usize)>,
scroll_id: Id, scroll_id: Id,
current_font: Font, current_font: Font,
@ -159,11 +159,14 @@ impl Presenter {
video_position: 0.0, video_position: 0.0,
hovered_slide: None, hovered_slide: None,
sink: { sink: {
let (stream, stream_handle) = let stream_handle =
OutputStream::try_default().unwrap(); OutputStreamBuilder::open_default_stream()
.expect("Can't open default rodio stream");
( (
stream, Arc::new(Sink::connect_new(
Arc::new(Sink::try_new(&stream_handle).unwrap()), &stream_handle.mixer(),
)),
stream_handle,
) )
}, },
scroll_id: Id::unique(), scroll_id: Id::unique(),
@ -385,7 +388,7 @@ impl Presenter {
return Action::Task(self.start_audio()); return Action::Task(self.start_audio());
} }
Message::EndAudio => { Message::EndAudio => {
self.sink.1.stop(); self.sink.0.stop();
} }
Message::None => debug!("Presenter Message::None"), Message::None => debug!("Presenter Message::None"),
Message::Error(error) => { Message::Error(error) => {
@ -397,9 +400,8 @@ impl Presenter {
pub fn view(&self) -> Element<Message> { pub fn view(&self) -> Element<Message> {
slide_view( slide_view(
&self.current_slide, self.current_slide.clone(),
&self.video, &self.video,
self.current_font,
false, false,
true, true,
) )
@ -407,9 +409,8 @@ impl Presenter {
pub fn view_preview(&self) -> Element<Message> { pub fn view_preview(&self) -> Element<Message> {
slide_view( slide_view(
&self.current_slide, self.current_slide.clone(),
&self.video, &self.video,
self.current_font,
false, false,
false, false,
) )
@ -443,9 +444,8 @@ impl Presenter {
); );
let container = slide_view( let container = slide_view(
slide, slide.clone(),
&self.video, &self.video,
font,
true, true,
false, false,
); );
@ -663,7 +663,7 @@ impl Presenter {
debug!(?audio, "This is where audio should be changing"); debug!(?audio, "This is where audio should be changing");
let audio = audio.clone(); let audio = audio.clone();
Task::perform( Task::perform(
start_audio(Arc::clone(&self.sink.1), audio), start_audio(Arc::clone(&self.sink.0), audio),
|()| Message::None, |()| Message::None,
) )
} else { } else {
@ -705,9 +705,8 @@ 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: Slide,
video: &'a Option<Video>, video: &'a Option<Video>,
font: Font,
delegate: bool, delegate: bool,
hide_mouse: bool, hide_mouse: bool,
) -> Element<'a, Message> { ) -> Element<'a, Message> {

View file

@ -1,27 +1,28 @@
use std::{io, path::PathBuf, sync::Arc}; use std::{io, path::PathBuf, sync::Arc};
use cosmic::{ use cosmic::{
Element, Task,
dialog::file_chooser::open::Dialog, dialog::file_chooser::open::Dialog,
iced::{ iced::{
Font, Length,
font::{Family, Stretch, Style, Weight}, font::{Family, Stretch, Style, Weight},
Font, Length,
}, },
iced_wgpu::graphics::text::cosmic_text::fontdb, iced_wgpu::graphics::text::cosmic_text::fontdb,
iced_widget::row, iced_widget::row,
theme, theme,
widget::{ widget::{
button, column, combo_box, container, horizontal_space, icon, button, column, combo_box, container, horizontal_space, icon,
text, text_editor, text_input, scrollable, text, text_editor, text_input,
}, },
Element, Task,
}; };
use dirs::font_dir; use dirs::font_dir;
use iced_video_player::Video; use iced_video_player::Video;
use tracing::{debug, error}; use tracing::{debug, error};
use crate::{ use crate::{
Background, BackgroundKind, core::songs::Song, core::{service_items::ServiceTrait, songs::Song},
ui::slide_editor::SlideEditor, ui::{presenter::slide_view, slide_editor::SlideEditor},
Background, BackgroundKind,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -282,49 +283,48 @@ impl SongEditor {
} }
fn slide_preview(&self) -> Element<Message> { fn slide_preview(&self) -> Element<Message> {
// if let Some(song) = &self.song { if let Some(song) = &self.song {
// if let Ok(slides) = song.to_slides() { if let Ok(slides) = song.to_slides() {
// let slides = slides let slides: Vec<Element<Message>> = slides
// .iter() .into_iter()
// .enumerate() .enumerate()
// .map(|(index, slide)| { .map(|(index, slide)| {
// container( container(
// slide_view( slide_view(
// slide.clone(), slide,
// if index == 0 { if index == 0 {
// &self.video &self.video
// } else { } else {
// &None &None
// }, },
// self.current_font, false,
// false, false,
// false, )
// ) .map(|_| Message::None),
// .map(|_| Message::None), )
// ) .height(250)
// .height(250) .center_x(Length::Fill)
// .center_x(Length::Fill) .padding([0, 20])
// .padding([0, 20]) .clip(true)
// .clip(true) .into()
// .into() })
// }) .collect();
// .collect(); scrollable(
// scrollable( column::with_children(slides)
// column::with_children(slides) .spacing(theme::active().cosmic().space_l()),
// .spacing(theme::active().cosmic().space_l()), )
// ) .height(Length::Fill)
// .height(Length::Fill) .width(Length::Fill)
// .width(Length::Fill) .into()
// .into() } else {
// } else { horizontal_space().into()
// horizontal_space().into() }
// } } else {
// } else { horizontal_space().into()
// horizontal_space().into() }
// } // 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)
} }
fn left_column(&self) -> Element<Message> { fn left_column(&self) -> Element<Message> {