Compare commits

..

10 commits

15 changed files with 260 additions and 250 deletions

View file

@ -3,7 +3,7 @@ use crate::{Background, Slide, SlideBuilder, TextAlignment};
use super::{ use super::{
content::Content, content::Content,
kinds::ServiceItemKind, kinds::ServiceItemKind,
model::{get_db, LibraryKind, Model}, model::{LibraryKind, Model},
service_items::ServiceTrait, service_items::ServiceTrait,
}; };
use crisp::types::{Keyword, Symbol, Value}; use crisp::types::{Keyword, Symbol, Value};
@ -45,13 +45,7 @@ impl Content for Image {
} }
fn background(&self) -> Option<Background> { fn background(&self) -> Option<Background> {
if let Ok(background) = Background::try_from(self.path.clone()).ok()
Background::try_from(self.path.clone())
{
Some(background)
} else {
None
}
} }
fn subtext(&self) -> String { fn subtext(&self) -> String {

View file

@ -133,12 +133,6 @@ pub(crate) fn get_lists(exp: &Value) -> Vec<Value> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::fs::read_to_string;
use lexpr::{parse::Options, Parser};
use pretty_assertions::assert_eq;
use super::*;
// #[test] // #[test]
// fn test_list() { // fn test_list() {

View file

@ -4,8 +4,6 @@ use cosmic::iced::Executor;
use miette::{miette, Result}; use miette::{miette, Result};
use sqlx::{Connection, SqliteConnection}; use sqlx::{Connection, SqliteConnection};
use super::kinds::ServiceItemKind;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Model<T> { pub struct Model<T> {
pub items: Vec<T>, pub items: Vec<T>,

View file

@ -13,7 +13,7 @@ use crate::{Background, Slide, SlideBuilder, TextAlignment};
use super::{ use super::{
content::Content, content::Content,
kinds::ServiceItemKind, kinds::ServiceItemKind,
model::{get_db, LibraryKind, Model}, model::{LibraryKind, Model},
service_items::ServiceTrait, service_items::ServiceTrait,
}; };
@ -57,13 +57,7 @@ impl Content for Presentation {
} }
fn background(&self) -> Option<Background> { fn background(&self) -> Option<Background> {
if let Ok(background) = Background::try_from(self.path.clone()).ok()
Background::try_from(self.path.clone())
{
Some(background)
} else {
None
}
} }
fn subtext(&self) -> String { fn subtext(&self) -> String {

View file

@ -3,7 +3,7 @@ use std::ops::Deref;
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
use crisp::types::{Keyword, Symbol, Value}; use crisp::types::{Keyword, Symbol, Value};
use miette::{miette, Result}; use miette::Result;
use tracing::{debug, error}; use tracing::{debug, error};
use crate::Slide; use crate::Slide;
@ -153,52 +153,48 @@ impl From<&Value> for ServiceItem {
database_id: 0, database_id: 0,
kind: ServiceItemKind::Content(slide), kind: ServiceItemKind::Content(slide),
} }
} else { } else if let Some(background) =
if let Some(background) = list.get(background_pos)
list.get(background_pos) {
{ match background {
match background { Value::List(item) => match &item[0] {
Value::List(item) => match &item[0] { Value::Symbol(Symbol(s))
Value::Symbol(Symbol(s)) if s == "image" =>
if s == "image" => {
{ Self::from(&Image::from(
Self::from(&Image::from( background,
background, ))
))
}
Value::Symbol(Symbol(s))
if s == "video" =>
{
Self::from(&Video::from(
background,
))
}
Value::Symbol(Symbol(s))
if s == "presentation" =>
{
Self::from(
&Presentation::from(
background,
),
)
}
_ => todo!(),
},
_ => {
error!(
"There is no background here: {:?}",
background
);
ServiceItem::default()
} }
Value::Symbol(Symbol(s))
if s == "video" =>
{
Self::from(&Video::from(
background,
))
}
Value::Symbol(Symbol(s))
if s == "presentation" =>
{
Self::from(&Presentation::from(
background,
))
}
_ => todo!(),
},
_ => {
error!(
"There is no background here: {:?}",
background
);
ServiceItem::default()
} }
} else {
error!(
"There is no background here: {:?}",
background_pos
);
ServiceItem::default()
} }
} else {
error!(
"There is no background here: {:?}",
background_pos
);
ServiceItem::default()
} }
} }
Value::Symbol(Symbol(s)) if s == "song" => { Value::Symbol(Symbol(s)) if s == "song" => {
@ -342,7 +338,7 @@ mod test {
use crate::core::presentations::PresKind; use crate::core::presentations::PresKind;
use super::*; use super::*;
use pretty_assertions::{assert_eq, assert_ne}; use pretty_assertions::assert_eq;
fn test_song() -> Song { fn test_song() -> Song {
Song { Song {

View file

@ -1,6 +1,5 @@
// use cosmic::dialog::ashpd::url::Url; // use cosmic::dialog::ashpd::url::Url;
use crisp::types::{Keyword, Symbol, Value}; use crisp::types::{Keyword, Symbol, Value};
use gstreamer::query::Uri;
use iced_video_player::Video; use iced_video_player::Video;
use miette::{miette, Result}; use miette::{miette, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -13,7 +12,15 @@ use tracing::error;
use super::songs::Song; use super::songs::Song;
#[derive( #[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Serialize,
Deserialize,
Hash,
)] )]
pub enum TextAlignment { pub enum TextAlignment {
TopLeft, TopLeft,

View file

@ -8,14 +8,14 @@ use sqlx::{
pool::PoolConnection, query, sqlite::SqliteRow, FromRow, Row, pool::PoolConnection, query, sqlite::SqliteRow, FromRow, Row,
Sqlite, SqliteConnection, SqlitePool, Sqlite, SqliteConnection, SqlitePool,
}; };
use tracing::{debug, error}; use tracing::error;
use crate::{core::slide, Slide, SlideBuilder}; use crate::{core::slide, Slide, SlideBuilder};
use super::{ use super::{
content::Content, content::Content,
kinds::ServiceItemKind, kinds::ServiceItemKind,
model::{get_db, LibraryKind, Model}, model::{LibraryKind, Model},
service_items::ServiceTrait, service_items::ServiceTrait,
slide::{Background, TextAlignment}, slide::{Background, TextAlignment},
}; };
@ -132,10 +132,7 @@ impl FromRow<'_, SqliteRow> for Song {
}), }),
background: { background: {
let string: String = row.try_get(7)?; let string: String = row.try_get(7)?;
match Background::try_from(string) { Background::try_from(string).ok()
Ok(background) => Some(background),
Err(_) => None,
}
}, },
text_alignment: Some({ text_alignment: Some({
let horizontal_alignment: String = row.try_get(3)?; let horizontal_alignment: String = row.try_get(3)?;
@ -423,7 +420,7 @@ pub async fn update_song_in_db(
if let Some(vo) = item.verse_order { if let Some(vo) = item.verse_order {
vo.into_iter() vo.into_iter()
.map(|mut s| { .map(|mut s| {
s.push_str(" "); s.push(' ');
s s
}) })
.collect::<String>() .collect::<String>()
@ -538,7 +535,6 @@ impl Song {
lyric_list.push(lyric.to_string()); lyric_list.push(lyric.to_string());
} else { } else {
// error!("NOT WORKING!"); // error!("NOT WORKING!");
()
}; };
} }
// for lyric in lyric_list.iter() { // for lyric in lyric_list.iter() {
@ -556,7 +552,7 @@ mod test {
use std::fs::read_to_string; use std::fs::read_to_string;
use super::*; use super::*;
use pretty_assertions::{assert_eq, assert_ne}; use pretty_assertions::assert_eq;
#[test] #[test]
pub fn test_song_lyrics() { pub fn test_song_lyrics() {
@ -724,7 +720,7 @@ You saved my soul"
let lisp = read_to_string("./test_song.lisp").expect("oops"); let lisp = read_to_string("./test_song.lisp").expect("oops");
let lisp_value = crisp::reader::read(&lisp); let lisp_value = crisp::reader::read(&lisp);
match lisp_value { match lisp_value {
Value::List(v) => v.get(0).unwrap().clone(), Value::List(v) => v.first().unwrap().clone(),
_ => Value::Nil, _ => Value::Nil,
} }
} }

View file

@ -3,7 +3,7 @@ use crate::{Background, SlideBuilder, TextAlignment};
use super::{ use super::{
content::Content, content::Content,
kinds::ServiceItemKind, kinds::ServiceItemKind,
model::{get_db, LibraryKind, Model}, model::{LibraryKind, Model},
service_items::ServiceTrait, service_items::ServiceTrait,
slide::Slide, slide::Slide,
}; };
@ -50,13 +50,7 @@ impl Content for Video {
} }
fn background(&self) -> Option<Background> { fn background(&self) -> Option<Background> {
if let Ok(background) = Background::try_from(self.path.clone()).ok()
Background::try_from(self.path.clone())
{
Some(background)
} else {
None
}
} }
fn subtext(&self) -> String { fn subtext(&self) -> String {

View file

@ -37,17 +37,14 @@ pub fn parse_lisp(value: Value) -> Vec<ServiceItem> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::{ use std::{fs::read_to_string, path::PathBuf};
fs::read_to_string,
path::{Path, PathBuf},
};
use crate::{ use crate::{
core::{ core::{
images::Image, kinds::ServiceItemKind, images::Image, kinds::ServiceItemKind,
service_items::ServiceTrait, songs::Song, videos::Video, service_items::ServiceTrait, songs::Song, videos::Video,
}, },
Background, Slide, SlideBuilder, TextAlignment, Background, TextAlignment,
}; };
use super::*; use super::*;

View file

@ -1,5 +1,4 @@
use clap::{command, Parser}; use clap::{command, Parser};
use core::model::{get_db, LibraryKind};
use core::service_items::{ServiceItem, ServiceItemModel}; use core::service_items::{ServiceItem, ServiceItemModel};
use core::slide::*; use core::slide::*;
use core::songs::Song; use core::songs::Song;
@ -14,13 +13,12 @@ use cosmic::iced_widget::{column, row};
use cosmic::widget::dnd_destination::DragId; use cosmic::widget::dnd_destination::DragId;
use cosmic::widget::nav_bar::nav_bar_style; use cosmic::widget::nav_bar::nav_bar_style;
use cosmic::widget::segmented_button::Entity; use cosmic::widget::segmented_button::Entity;
use cosmic::widget::text;
use cosmic::widget::tooltip::Position as TPosition; use cosmic::widget::tooltip::Position as TPosition;
use cosmic::widget::{ use cosmic::widget::{
button, horizontal_space, nav_bar, search_input, text_input, button, horizontal_space, nav_bar, search_input, tooltip, Space,
tooltip, Space,
}; };
use cosmic::widget::{icon, slider}; use cosmic::widget::{icon, slider};
use cosmic::widget::{text, toggler};
use cosmic::{executor, Application, ApplicationExt, Element}; use cosmic::{executor, Application, ApplicationExt, Element};
use cosmic::{prelude::*, theme}; use cosmic::{prelude::*, theme};
use cosmic::{widget::Container, Theme}; use cosmic::{widget::Container, Theme};
@ -130,7 +128,7 @@ enum Message {
EditorToggle(bool), EditorToggle(bool),
} }
const HEADER_SPACE: u16 = 20; const HEADER_SPACE: u16 = 6;
impl cosmic::Application for App { impl cosmic::Application for App {
type Executor = executor::Default; type Executor = executor::Default;
@ -300,11 +298,11 @@ impl cosmic::Application for App {
.into()] .into()]
} }
fn header_end(&self) -> Vec<Element<Self::Message>> { fn header_end(&self) -> Vec<Element<Self::Message>> {
let editor_toggle = toggler(self.editor_mode.is_some()) // let editor_toggle = toggler(self.editor_mode.is_some())
.label("Editor") // .label("Editor")
.spacing(10) // .spacing(10)
.width(Length::Shrink) // .width(Length::Shrink)
.on_toggle(Message::EditorToggle); // .on_toggle(Message::EditorToggle);
let presenter_window = self.windows.get(1); let presenter_window = self.windows.get(1);
let text = if self.presentation_open { let text = if self.presentation_open {
@ -314,7 +312,30 @@ impl cosmic::Application for App {
}; };
vec![ vec![
editor_toggle.into(), tooltip(
button::custom(
row!(
Container::new(
icon::from_name("document-edit-symbolic")
.scale(3)
)
.center_y(Length::Fill),
text::body(if self.editor_mode.is_some() {
"Present Mode"
} else {
"Edit Mode"
})
)
.spacing(5),
)
.class(cosmic::theme::style::Button::HeaderBar)
.on_press(Message::EditorToggle(
self.editor_mode.is_none(),
)),
"Enter Edit Mode",
TPosition::Bottom,
)
.into(),
horizontal_space().width(HEADER_SPACE).into(), horizontal_space().width(HEADER_SPACE).into(),
tooltip( tooltip(
button::custom( button::custom(
@ -353,12 +374,8 @@ impl cosmic::Application for App {
button::custom( button::custom(
row!( row!(
Container::new( Container::new(
icon::from_name(if self.library_open { icon::from_name("view-list-symbolic")
"arrow-right" .scale(3)
} else {
"view-list-symbolic"
})
.scale(3)
) )
.center_y(Length::Fill), .center_y(Length::Fill),
text::body(if self.library_open { text::body(if self.library_open {
@ -702,7 +719,7 @@ impl cosmic::Application for App {
let library = if self.library_open { let library = if self.library_open {
Container::new(if let Some(library) = &self.library { Container::new(if let Some(library) = &self.library {
library.view().map(|m| Message::Library(m)) library.view().map(Message::Library)
} else { } else {
Space::new(0, 0).into() Space::new(0, 0).into()
}) })
@ -713,7 +730,7 @@ impl cosmic::Application for App {
}; };
let song_editor = let song_editor =
self.song_editor.view().map(|m| Message::SongEditor(m)); self.song_editor.view().map(Message::SongEditor);
let row = row![ let row = row![
Container::new( Container::new(
@ -727,8 +744,7 @@ impl cosmic::Application for App {
.class(theme::style::Button::Transparent) .class(theme::style::Button::Transparent)
) )
.center_y(Length::Fill) .center_y(Length::Fill)
.align_right(Length::Fill) .align_right(Length::FillPortion(1)),
.width(Length::FillPortion(1)),
Container::new(slide_preview) Container::new(slide_preview)
.center_y(Length::Fill) .center_y(Length::Fill)
.width(Length::FillPortion(3)), .width(Length::FillPortion(3)),
@ -743,8 +759,7 @@ impl cosmic::Application for App {
.class(theme::style::Button::Transparent) .class(theme::style::Button::Transparent)
) )
.center_y(Length::Fill) .center_y(Length::Fill)
.align_left(Length::Fill) .align_left(Length::FillPortion(1)),
.width(Length::FillPortion(1)),
library library
] ]
.width(Length::Fill) .width(Length::Fill)
@ -863,8 +878,6 @@ where
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*;
use pretty_assertions::assert_eq;
fn test_slide() -> String { fn test_slide() -> String {
let slide = r#"(slide (image :source "./somehting.jpg" :fill cover let slide = r#"(slide (image :source "./somehting.jpg" :fill cover

View file

@ -1,21 +1,22 @@
use std::rc::Rc;
use cosmic::{ use cosmic::{
iced::{ iced::{
alignment::Vertical, clipboard::dnd::DndAction, alignment::Vertical, clipboard::dnd::DndAction,
futures::FutureExt, Background, Border, Color, Length, futures::FutureExt, Background, Border, Color, Length,
}, },
iced_core::widget::tree::State,
iced_widget::{column, row as rowm, text as textm}, iced_widget::{column, row as rowm, text as textm},
theme, theme,
widget::{ widget::{
button, container, horizontal_space, icon, mouse_area, button, container, horizontal_space, icon, mouse_area,
responsive, row, scrollable, text, text_input, Container, responsive, row, scrollable, text, text_input, Container,
DndSource, Icon, Space, Widget, DndSource, Space, Widget,
}, },
Element, Task, Element, Task,
}; };
use miette::{miette, IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use sqlx::{ use sqlx::{pool::PoolConnection, Sqlite, SqlitePool};
pool::PoolConnection, Sqlite, SqliteConnection, SqlitePool,
};
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
use crate::core::{ use crate::core::{
@ -359,7 +360,7 @@ impl<'a> Library {
|(index, item)| { |(index, item)| {
let service_item = item.to_service_item(); let service_item = item.to_service_item();
let drag_item = let drag_item =
self.single_item(index, item, model); Box::new(self.single_item(index, item, &model));
let visual_item = self let visual_item = self
.single_item(index, item, model) .single_item(index, item, model)
.map(|_| Message::None); .map(|_| Message::None);
@ -386,11 +387,25 @@ impl<'a> Library {
)), )),
) )
.action(DndAction::Copy) .action(DndAction::Copy)
// .drag_icon(move |i| { .drag_icon({
// let state = let model = model.kind.clone();
// drag_item.as_widget().state(); move |i| {
// (drag_item, state, i) let state = State::None;
// }) let icon = match model {
LibraryKind::Song => icon::from_name(
"folder-music-symbolic",
)
,
LibraryKind::Video => icon::from_name("folder-videos-symbolic"),
LibraryKind::Image => icon::from_name("folder-pictures-symbolic"),
LibraryKind::Presentation => icon::from_name("x-office-presentation-symbolic"),
};
(
icon.into(),
state,
i,
)
}})
.drag_content(move || { .drag_content(move || {
service_item.to_owned() service_item.to_owned()
}) })
@ -398,10 +413,11 @@ impl<'a> Library {
}, },
) )
}) })
.spacing(2) .spacing(2)
.width(Length::Fill), .width(Length::Fill),
) )
.spacing(5); .spacing(5)
.height(Length::Fill);
let library_toolbar = rowm!( let library_toolbar = rowm!(
text_input("Search...", ""), text_input("Search...", ""),

View file

@ -1,4 +1,4 @@
use miette::{miette, IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc}; use std::{fs::File, io::BufReader, path::PathBuf, sync::Arc};
use cosmic::{ use cosmic::{
@ -13,7 +13,7 @@ use cosmic::{
scrollable::{ scrollable::{
scroll_to, AbsoluteOffset, Direction, Scrollbar, scroll_to, AbsoluteOffset, Direction, Scrollbar,
}, },
span, stack, text, span, stack,
}, },
prelude::*, prelude::*,
widget::{ widget::{
@ -29,13 +29,10 @@ use url::Url;
use crate::{ use crate::{
core::{service_items::ServiceItemModel, slide::Slide}, core::{service_items::ServiceItemModel, slide::Slide},
ui::text_svg::{self, Font as SvgFont},
BackgroundKind, BackgroundKind,
}; };
use super::text_svg::{
self, shadow, stroke, Font as SvgFont, TextSvg,
};
const REFERENCE_WIDTH: f32 = 1920.0; const REFERENCE_WIDTH: f32 = 1920.0;
const REFERENCE_HEIGHT: f32 = 1080.0; const REFERENCE_HEIGHT: f32 = 1080.0;
@ -528,39 +525,43 @@ async fn start_audio(sink: Arc<Sink>, audio: PathBuf) {
fn scale_font(font_size: f32, width: f32) -> f32 { fn scale_font(font_size: f32, width: f32) -> f32 {
let scale_factor = (REFERENCE_WIDTH / width).sqrt(); let scale_factor = (REFERENCE_WIDTH / width).sqrt();
// debug!(scale_factor); // debug!(scale_factor);
let font_size = if font_size > 0.0 {
if font_size > 0.0 {
font_size / scale_factor font_size / scale_factor
} else { } else {
50.0 50.0
}; }
font_size
} }
pub(crate) fn slide_view<'a>( pub(crate) fn slide_view(
slide: Slide, slide: Slide,
video: &'a Option<Video>, video: &Option<Video>,
font: Font, font: Font,
delegate: bool, delegate: bool,
hide_mouse: bool, hide_mouse: bool,
) -> Element<'a, Message> { ) -> Element<'_, Message> {
responsive(move |size| { responsive(move |size| {
let width = size.height * 16.0 / 9.0; let width = size.height * 16.0 / 9.0;
let font_size = scale_font(slide.font_size() as f32, width); let font_size = scale_font(slide.font_size() as f32, width);
// let font = SvgFont::from(font).size(font_size.floor() as u8);
let slide_text = slide.text(); let slide_text = slide.text();
// SVG based
// let font = SvgFont::from(font).size(font_size.floor() as u8);
// let text = text_svg::TextSvg::new() // let text = text_svg::TextSvg::new()
// .text(&slide_text) // .text(&slide_text)
// .fill("#fff") // .fill("#fff")
// .shadow(shadow(2, 2, 5, "#000000")) // .shadow(text_svg::shadow(2, 2, 5, "#000000"))
// .stroke(stroke(1, "#000")) // .stroke(text_svg::stroke(1, "#000"))
// .font(font) // .font(font)
// .view() // .view()
// .map(|m| Message::None); // .map(|m| Message::None);
// let text = text!("{}", &slide_text); // let text = text!("{}", &slide_text);
// text widget based
let lines = slide_text.lines(); let lines = slide_text.lines();
let text: Vec<Element<Message>> = lines let text: Vec<Element<Message>> = lines
.map(|t| { .map(|t| {
rich_text([span(format!("{}\n", t.to_string())) rich_text([span(format!("{}\n", t))
.background( .background(
Background::Color(Color::BLACK) Background::Color(Color::BLACK)
.scale_alpha(0.4), .scale_alpha(0.4),
@ -573,6 +574,8 @@ pub(crate) fn slide_view<'a>(
}) })
.collect(); .collect();
let text = Column::with_children(text).spacing(6); let text = Column::with_children(text).spacing(6);
//Next
let text_container = Container::new(text) let text_container = Container::new(text)
.center(Length::Fill) .center(Length::Fill)
.align_x(Horizontal::Left); .align_x(Horizontal::Left);

View file

@ -31,8 +31,10 @@ use super::presenter::slide_view;
pub struct SongEditor { pub struct SongEditor {
pub song: Option<Song>, pub song: Option<Song>,
title: String, title: String,
fonts: combo_box::State<String>, font_db: fontdb::Database,
font_sizes: Vec<String>, fonts: Vec<(fontdb::ID, String)>,
fonts_combo: combo_box::State<String>,
font_sizes: combo_box::State<String>,
font: String, font: String,
author: String, author: String,
audio: PathBuf, audio: PathBuf,
@ -72,42 +74,21 @@ impl SongEditor {
pub fn new() -> Self { pub fn new() -> Self {
let fonts = font_dir(); let fonts = font_dir();
debug!(?fonts); debug!(?fonts);
let mut fontdb = fontdb::Database::new(); let mut font_db = fontdb::Database::new();
fontdb.load_system_fonts(); font_db.load_system_fonts();
let fonts: Vec<String> = fontdb let mut fonts: Vec<(fontdb::ID, String)> = font_db
.faces() .faces()
.map(|f| { .map(|f| {
let mut font = f.to_owned().post_script_name; let id = f.id;
if let Some(at) = font.find("-") { let font_base_name: String =
let _ = font.split_off(at); f.families.iter().map(|f| f.0.clone()).collect();
} let font_weight = f.weight;
let indices: Vec<usize> = font let font_style = f.style;
.chars() let font_stretch = f.stretch;
.enumerate() (id, font_base_name)
.filter(|(index, c)| {
c.is_uppercase() && *index != 0
})
.map(|(index, c)| index)
.collect();
let mut font_parts = vec![];
for index in indices.iter().rev() {
let (first, last) = font.split_at(*index);
font_parts.push(first);
if !last.is_empty() {
font_parts.push(last);
}
}
font_parts
.iter()
.map(|s| {
let mut s = s.to_string();
s.push(' ');
s
})
.collect()
}) })
.collect(); .collect();
fonts.dedup();
// let fonts = vec![ // let fonts = vec![
// String::from("Quicksand"), // String::from("Quicksand"),
// String::from("Noto Sans"), // String::from("Noto Sans"),
@ -134,13 +115,16 @@ impl SongEditor {
"70".to_string(), "70".to_string(),
"80".to_string(), "80".to_string(),
]; ];
let font_texts = fonts.iter().map(|f| f.1.clone()).collect();
Self { Self {
song: None, song: None,
fonts: combo_box::State::new(fonts), font_db,
fonts,
fonts_combo: combo_box::State::new(font_texts),
title: "Death was Arrested".to_owned(), title: "Death was Arrested".to_owned(),
font: "Quicksand".to_owned(), font: "Quicksand".to_owned(),
font_size: 16, font_size: 16,
font_sizes, font_sizes: combo_box::State::new(font_sizes),
verse_order: "Death was Arrested".to_owned(), verse_order: "Death was Arrested".to_owned(),
lyrics: text_editor::Content::new(), lyrics: text_editor::Content::new(),
editing: false, editing: false,
@ -167,7 +151,7 @@ impl SongEditor {
self.verse_order = verse_order self.verse_order = verse_order
.into_iter() .into_iter()
.map(|mut s| { .map(|mut s| {
s.push_str(" "); s.push(' ');
s s
}) })
.collect(); .collect();
@ -189,11 +173,23 @@ impl SongEditor {
self.background = song.background; self.background = song.background;
} }
Message::ChangeFont(font) => { Message::ChangeFont(font) => {
let font_id = self
.fonts
.iter()
.filter(|f| f.1 == font)
.map(|f| f.0)
.next();
if let Some(id) = font_id {
if let Some(face) = self.font_db.face(id) {
self.font = face.post_script_name.clone();
// self.current_font = Font::from(face);
}
}
self.font = font.clone(); self.font = font.clone();
let font_name = font.into_boxed_str(); let font_name = font.into_boxed_str();
let family = Family::Name(Box::leak(font_name)); let family = Family::Name(Box::leak(font_name));
let weight = Weight::Normal; let weight = Weight::Bold;
let stretch = Stretch::Normal; let stretch = Stretch::Normal;
let style = Style::Normal; let style = Style::Normal;
let font = Font { let font = Font {
@ -205,15 +201,7 @@ impl SongEditor {
self.current_font = font; self.current_font = font;
// return self.update_song(song); // return self.update_song(song);
} }
Message::ChangeFontSize(size) => { Message::ChangeFontSize(size) => self.font_size = size,
if let Some(size) = self.font_sizes.get(size) {
if let Ok(size) = size.parse() {
debug!(font_size = size);
self.font_size = size;
// return self.update_song(song);
}
}
}
Message::ChangeTitle(title) => { Message::ChangeTitle(title) => {
self.title = title.clone(); self.title = title.clone();
if let Some(song) = &mut self.song { if let Some(song) = &mut self.song {
@ -227,7 +215,6 @@ impl SongEditor {
if let Some(mut song) = self.song.clone() { if let Some(mut song) = self.song.clone() {
let verse_order = verse_order let verse_order = verse_order
.split(" ") .split(" ")
.into_iter()
.map(|s| s.to_owned()) .map(|s| s.to_owned())
.collect(); .collect();
song.verse_order = Some(verse_order); song.verse_order = Some(verse_order);
@ -305,40 +292,25 @@ impl SongEditor {
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(index, slide)| { .map(|(index, slide)| {
let svg = Handle::from_memory(r#"<svg viewBox="0 0 1280 720" xmlns="http://www.w3.org/2000/svg"> container(
<defs> slide_view(
<filter id="shadow"> slide,
<feDropShadow dx="10" dy="10" stdDeviation="5" flood-color='#000' /> if index == 0 {
</filter> &self.video
</defs> } else {
<text dominant-baseline="middle" text-anchor="middle" font-weight="bold" font-family="Quicksand" font-size="80" fill="white" stroke="black" stroke-width="2" style="filter:url(#shadow);"> &None
<tspan x="50%" y="50" >Hello World this is</tspan> },
<tspan x="50%" y="140">longer chunks of text</tspan> self.current_font,
<tspan x="50%" y="230">where we need to test whether the text</tspan> false,
<tspan x="50%" y="320">will look ok!</tspan> false,
</text>
</svg>"#.as_bytes());
stack!(
container(
slide_view(
slide,
if index == 0 {
&self.video
} else {
&None
},
self.current_font,
false,
false,
)
.map(|_| Message::None),
) )
.height(250) .map(|_| Message::None),
.center_x(Length::Fill) )
.padding([0, 20]) .height(250)
.clip(true), .center_x(Length::Fill)
Svg::new(svg), .padding([0, 20])
).into() .clip(true)
.into()
}) })
.collect(); .collect();
scrollable( scrollable(
@ -396,22 +368,34 @@ order",
fn toolbar(&self) -> Element<Message> { fn toolbar(&self) -> Element<Message> {
let selected_font = &self.font; let selected_font = &self.font;
let selected_font_size = self let selected_font_size = {
.font_sizes let font_size_position = self
.iter() .font_sizes
.position(|s| *s == self.font_size.to_string()); .options()
.iter()
.position(|s| *s == self.font_size.to_string());
self.font_sizes
.options()
.get(font_size_position.unwrap_or_default())
};
let font_selector = combo_box( let font_selector = combo_box(
&self.fonts, &self.fonts_combo,
"Font", "Font",
Some(selected_font), Some(selected_font),
Message::ChangeFont, Message::ChangeFont,
) )
.width(200); .width(200);
let font_size = dropdown( let font_size = combo_box(
&self.font_sizes, &self.font_sizes,
"Font Size",
selected_font_size, selected_font_size,
Message::ChangeFontSize, |size| {
); Message::ChangeFontSize(
size.parse().expect("Should be a number"),
)
},
)
.width(theme::active().cosmic().space_xxl());
let background_selector = button::icon( let background_selector = button::icon(
icon::from_name("folder-pictures-symbolic").scale(2), icon::from_name("folder-pictures-symbolic").scale(2),
@ -427,6 +411,7 @@ order",
horizontal_space(), horizontal_space(),
background_selector background_selector
] ]
.spacing(10)
.into() .into()
} }

View file

@ -1,4 +1,7 @@
use std::fmt::Display; use std::{
fmt::Display,
hash::{Hash, Hasher},
};
use colors_transform::Rgb; use colors_transform::Rgb;
use cosmic::{ use cosmic::{
@ -7,9 +10,9 @@ use cosmic::{
Length, Length,
}, },
prelude::*, prelude::*,
widget::{container, responsive, svg::Handle, Svg}, widget::{container, lazy, responsive, svg::Handle, Svg},
}; };
use tracing::{debug, error}; use tracing::error;
use crate::TextAlignment; use crate::TextAlignment;
@ -21,9 +24,21 @@ pub struct TextSvg {
stroke: Option<Stroke>, stroke: Option<Stroke>,
fill: Color, fill: Color,
alignment: TextAlignment, alignment: TextAlignment,
handle: Option<Handle>,
} }
#[derive(Clone, Debug, Default, PartialEq, Eq)] impl Hash for TextSvg {
fn hash<H: Hasher>(&self, state: &mut H) {
self.text.hash(state);
self.font.hash(state);
self.shadow.hash(state);
self.stroke.hash(state);
self.fill.hash(state);
self.alignment.hash(state);
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Font { pub struct Font {
name: String, name: String,
weight: Weight, weight: Weight,
@ -61,11 +76,11 @@ impl Font {
} }
pub fn get_weight(&self) -> Weight { pub fn get_weight(&self) -> Weight {
self.weight.clone() self.weight
} }
pub fn get_style(&self) -> Style { pub fn get_style(&self) -> Style {
self.style.clone() self.style
} }
pub fn weight(mut self, weight: impl Into<Weight>) -> Self { pub fn weight(mut self, weight: impl Into<Weight>) -> Self {
@ -92,6 +107,12 @@ impl Font {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Color(Rgb); pub struct Color(Rgb);
impl Hash for Color {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_css_hex_string().hash(state);
}
}
impl Color { impl Color {
pub fn from_hex_str(color: impl AsRef<str>) -> Color { pub fn from_hex_str(color: impl AsRef<str>) -> Color {
match Rgb::from_hex_str(color.as_ref()) { match Rgb::from_hex_str(color.as_ref()) {
@ -128,7 +149,7 @@ impl Display for Color {
} }
} }
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq, Hash)]
pub struct Shadow { pub struct Shadow {
pub offset_x: i16, pub offset_x: i16,
pub offset_y: i16, pub offset_y: i16,
@ -136,7 +157,7 @@ pub struct Shadow {
pub color: Color, pub color: Color,
} }
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq, Hash)]
pub struct Stroke { pub struct Stroke {
size: u16, size: u16,
color: Color, color: Color,
@ -153,6 +174,8 @@ impl TextSvg {
} }
} }
// pub fn build(self)
pub fn fill(mut self, color: impl Into<Color>) -> Self { pub fn fill(mut self, color: impl Into<Color>) -> Self {
self.fill = color.into(); self.fill = color.into();
self self
@ -226,12 +249,11 @@ impl TextSvg {
self.fill, stroke, text); self.fill, stroke, text);
// debug!(final_svg); // debug!(final_svg);
lazy(self.clone(), move |_s| Svg::new(Handle::from_memory(
Svg::new(Handle::from_memory( Box::leak(<std::string::String as Clone>::clone(&final_svg).into_boxed_str()).as_bytes(),
Box::leak(final_svg.into_boxed_str()).as_bytes(),
)) ))
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill))
.into() .into()
})).width(Length::Fill).height(Length::Fill).into() })).width(Length::Fill).height(Length::Fill).into()
} }

View file

@ -1,6 +1,7 @@
#+TITLE: The Task list for Lumina #+TITLE: The Task list for Lumina
* TODO Check into =mupdf-rs= for loading PDF's.
* TODO [#A] Text could be built by using SVG instead of the text element. Maybe I could construct my own text element even * TODO [#A] Text could be built by using SVG instead of the text element. Maybe I could construct my own text element even
This does almost work. There is a clear amount of lag or rather hang up since switching to the =text_svg= element. I think I may only keep it till I can figure out how to do strokes and shadows in iced's normal text element. This does almost work. There is a clear amount of lag or rather hang up since switching to the =text_svg= element. I think I may only keep it till I can figure out how to do strokes and shadows in iced's normal text element.