Compare commits
3 commits
2413b96791
...
645411b59c
Author | SHA1 | Date | |
---|---|---|---|
645411b59c | |||
3fe77c93e2 | |||
a186d3bec4 |
16 changed files with 471 additions and 400 deletions
22
flake.nix
22
flake.nix
|
@ -8,16 +8,18 @@
|
||||||
fenix.url = "github:nix-community/fenix";
|
fenix.url = "github:nix-community/fenix";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs: with inputs;
|
outputs =
|
||||||
flake-utils.lib.eachDefaultSystem
|
inputs:
|
||||||
(system:
|
with inputs;
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
overlays = [fenix.overlays.default];
|
overlays = [ fenix.overlays.default ];
|
||||||
# overlays = [cargo2nix.overlays.default];
|
# overlays = [cargo2nix.overlays.default];
|
||||||
};
|
};
|
||||||
naersk' = pkgs.callPackage naersk {};
|
naersk' = pkgs.callPackage naersk { };
|
||||||
nbi = with pkgs; [
|
nbi = with pkgs; [
|
||||||
# Rust tools
|
# Rust tools
|
||||||
alejandra
|
alejandra
|
||||||
|
@ -52,6 +54,7 @@
|
||||||
vulkan-tools
|
vulkan-tools
|
||||||
libGL
|
libGL
|
||||||
cargo-flamegraph
|
cargo-flamegraph
|
||||||
|
bacon
|
||||||
|
|
||||||
fontconfig
|
fontconfig
|
||||||
glib
|
glib
|
||||||
|
@ -74,11 +77,14 @@
|
||||||
sqlx-cli
|
sqlx-cli
|
||||||
cargo-watch
|
cargo-watch
|
||||||
];
|
];
|
||||||
in rec
|
in
|
||||||
|
rec {
|
||||||
|
devShell =
|
||||||
|
pkgs.mkShell.override
|
||||||
{
|
{
|
||||||
devShell = pkgs.mkShell.override {
|
|
||||||
# stdenv = pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv;
|
# stdenv = pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv;
|
||||||
} {
|
}
|
||||||
|
{
|
||||||
nativeBuildInputs = nbi;
|
nativeBuildInputs = nbi;
|
||||||
buildInputs = bi;
|
buildInputs = bi;
|
||||||
LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${
|
LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${
|
||||||
|
|
|
@ -17,7 +17,7 @@ use std::path::PathBuf;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Debug, Default, PartialEq, Serialize, Deserialize,
|
Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize,
|
||||||
)]
|
)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
@ -52,8 +52,9 @@ impl Content for Image {
|
||||||
if self.path.exists() {
|
if self.path.exists() {
|
||||||
self.path
|
self.path
|
||||||
.file_name()
|
.file_name()
|
||||||
.map(|f| f.to_string_lossy().to_string())
|
.map_or("Missing image".into(), |f| {
|
||||||
.unwrap_or("Missing image".into())
|
f.to_string_lossy().to_string()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
"Missing image".into()
|
"Missing image".into()
|
||||||
}
|
}
|
||||||
|
@ -85,7 +86,7 @@ impl From<&Value> for Image {
|
||||||
let path =
|
let path =
|
||||||
p.to_str().unwrap_or_default().to_string();
|
p.to_str().unwrap_or_default().to_string();
|
||||||
let title =
|
let title =
|
||||||
path.rsplit_once("/").unwrap_or_default().1;
|
path.rsplit_once('/').unwrap_or_default().1;
|
||||||
title.to_string()
|
title.to_string()
|
||||||
});
|
});
|
||||||
Self {
|
Self {
|
||||||
|
@ -154,14 +155,16 @@ impl Model<Image> {
|
||||||
.await;
|
.await;
|
||||||
match result {
|
match result {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
for image in v.into_iter() {
|
for image in v {
|
||||||
let _ = self.add_item(image);
|
let _ = self.add_item(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("There was an error in converting images: {e}")
|
error!(
|
||||||
|
"There was an error in converting images: {e}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +175,7 @@ pub async fn update_image_in_db(
|
||||||
let path = image
|
let path = image
|
||||||
.path
|
.path
|
||||||
.to_str()
|
.to_str()
|
||||||
.map(|s| s.to_string())
|
.map(std::string::ToString::to_string)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
query!(
|
query!(
|
||||||
r#"UPDATE images SET title = $2, file_path = $3 WHERE id = $1"#,
|
r#"UPDATE images SET title = $2, file_path = $3 WHERE id = $1"#,
|
||||||
|
|
|
@ -21,11 +21,11 @@ pub enum ServiceItemKind {
|
||||||
impl std::fmt::Display for ServiceItemKind {
|
impl std::fmt::Display for ServiceItemKind {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
let s = match self {
|
let s = match self {
|
||||||
Self::Song(s) => "song".to_owned(),
|
Self::Song(_) => "song".to_owned(),
|
||||||
Self::Image(i) => "image".to_owned(),
|
Self::Image(_) => "image".to_owned(),
|
||||||
Self::Video(v) => "video".to_owned(),
|
Self::Video(_) => "video".to_owned(),
|
||||||
Self::Presentation(p) => "html".to_owned(),
|
Self::Presentation(_) => "html".to_owned(),
|
||||||
Self::Content(s) => "content".to_owned(),
|
Self::Content(_) => "content".to_owned(),
|
||||||
};
|
};
|
||||||
write!(f, "{s}")
|
write!(f, "{s}")
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ impl std::fmt::Display for ServiceItemKind {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
impl From<ServiceItemKind> for String {
|
impl From<ServiceItemKind> for String {
|
||||||
fn from(val: ServiceItemKind) -> String {
|
fn from(val: ServiceItemKind) -> Self {
|
||||||
match val {
|
match val {
|
||||||
ServiceItemKind::Song(_) => "song".to_owned(),
|
ServiceItemKind::Song(_) => "song".to_owned(),
|
||||||
ServiceItemKind::Video(_) => "video".to_owned(),
|
ServiceItemKind::Video(_) => "video".to_owned(),
|
||||||
|
@ -76,7 +76,9 @@ impl Display for ParseError {
|
||||||
f: &mut std::fmt::Formatter<'_>,
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
) -> std::fmt::Result {
|
) -> std::fmt::Result {
|
||||||
let message = match self {
|
let message = match self {
|
||||||
Self::UnknownType => "The type does not exist. It needs to be one of 'song', 'video', 'image', 'presentation', or 'content'",
|
Self::UnknownType => {
|
||||||
|
"The type does not exist. It needs to be one of 'song', 'video', 'image', 'presentation', or 'content'"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
write!(f, "Error: {message}")
|
write!(f, "Error: {message}")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
|
|
||||||
use cosmic::iced::Executor;
|
use miette::{Result, miette};
|
||||||
use miette::{miette, Result};
|
|
||||||
use sqlx::{Connection, SqliteConnection};
|
use sqlx::{Connection, SqliteConnection};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -10,7 +9,7 @@ pub struct Model<T> {
|
||||||
pub kind: LibraryKind,
|
pub kind: LibraryKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||||
pub enum LibraryKind {
|
pub enum LibraryKind {
|
||||||
Song,
|
Song,
|
||||||
Video,
|
Video,
|
||||||
|
@ -46,6 +45,7 @@ impl<T> Model<T> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn get_item(&self, index: i32) -> Option<&T> {
|
pub fn get_item(&self, index: i32) -> Option<&T> {
|
||||||
self.items.get(index as usize)
|
self.items.get(index as usize)
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,8 +75,9 @@ impl Content for Presentation {
|
||||||
if self.path.exists() {
|
if self.path.exists() {
|
||||||
self.path
|
self.path
|
||||||
.file_name()
|
.file_name()
|
||||||
.map(|f| f.to_string_lossy().to_string())
|
.map_or("Missing presentation".into(), |f| {
|
||||||
.unwrap_or("Missing presentation".into())
|
f.to_string_lossy().to_string()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
"Missing presentation".into()
|
"Missing presentation".into()
|
||||||
}
|
}
|
||||||
|
@ -190,14 +191,16 @@ impl ServiceTrait for Presentation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Presentation {
|
impl Presentation {
|
||||||
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
title: "".to_string(),
|
title: String::new(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_kind(&self) -> &PresKind {
|
#[must_use]
|
||||||
|
pub const fn get_kind(&self) -> &PresKind {
|
||||||
&self.kind
|
&self.kind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,7 +243,7 @@ impl Model<Presentation> {
|
||||||
.await;
|
.await;
|
||||||
match result {
|
match result {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
for presentation in v.into_iter() {
|
for presentation in v {
|
||||||
let _ = self.add_item(Presentation {
|
let _ = self.add_item(Presentation {
|
||||||
id: presentation.id,
|
id: presentation.id,
|
||||||
title: presentation.title,
|
title: presentation.title,
|
||||||
|
@ -267,7 +270,7 @@ pub async fn update_presentation_in_db(
|
||||||
let path = presentation
|
let path = presentation
|
||||||
.path
|
.path
|
||||||
.to_str()
|
.to_str()
|
||||||
.map(|s| s.to_string())
|
.map(std::string::ToString::to_string)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let html = presentation.kind == PresKind::Html;
|
let html = presentation.kind == PresKind::Html;
|
||||||
query!(
|
query!(
|
||||||
|
|
|
@ -79,13 +79,13 @@ impl AsMimeTypes for ServiceItem {
|
||||||
impl From<&ServiceItem> for Value {
|
impl From<&ServiceItem> for Value {
|
||||||
fn from(value: &ServiceItem) -> Self {
|
fn from(value: &ServiceItem) -> Self {
|
||||||
match &value.kind {
|
match &value.kind {
|
||||||
ServiceItemKind::Song(song) => Value::from(song),
|
ServiceItemKind::Song(song) => Self::from(song),
|
||||||
ServiceItemKind::Video(video) => Value::from(video),
|
ServiceItemKind::Video(video) => Self::from(video),
|
||||||
ServiceItemKind::Image(image) => Value::from(image),
|
ServiceItemKind::Image(image) => Self::from(image),
|
||||||
ServiceItemKind::Presentation(presentation) => {
|
ServiceItemKind::Presentation(presentation) => {
|
||||||
Value::from(presentation)
|
Self::from(presentation)
|
||||||
}
|
}
|
||||||
ServiceItemKind::Content(slide) => Value::from(slide),
|
ServiceItemKind::Content(slide) => Self::from(slide),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,8 +172,8 @@ impl From<&Value> for ServiceItem {
|
||||||
} else if let Some(background) =
|
} else if let Some(background) =
|
||||||
list.get(background_pos)
|
list.get(background_pos)
|
||||||
{
|
{
|
||||||
match background {
|
if let Value::List(item) = background {
|
||||||
Value::List(item) => match &item[0] {
|
match &item[0] {
|
||||||
Value::Symbol(Symbol(s))
|
Value::Symbol(Symbol(s))
|
||||||
if s == "image" =>
|
if s == "image" =>
|
||||||
{
|
{
|
||||||
|
@ -196,30 +196,29 @@ impl From<&Value> for ServiceItem {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
},
|
}
|
||||||
_ => {
|
} else {
|
||||||
error!(
|
error!(
|
||||||
"There is no background here: {:?}",
|
"There is no background here: {:?}",
|
||||||
background
|
background
|
||||||
);
|
);
|
||||||
ServiceItem::default()
|
Self::default()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error!(
|
error!(
|
||||||
"There is no background here: {:?}",
|
"There is no background here: {:?}",
|
||||||
background_pos
|
background_pos
|
||||||
);
|
);
|
||||||
ServiceItem::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Symbol(Symbol(s)) if s == "song" => {
|
Value::Symbol(Symbol(s)) if s == "song" => {
|
||||||
let song = lisp_to_song(list.clone());
|
let song = lisp_to_song(list.clone());
|
||||||
Self::from(&song)
|
Self::from(&song)
|
||||||
}
|
}
|
||||||
_ => ServiceItem::default(),
|
_ => Self::default(),
|
||||||
},
|
},
|
||||||
_ => ServiceItem::default(),
|
_ => Self::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,7 +257,7 @@ impl From<&Song> for ServiceItem {
|
||||||
kind: ServiceItemKind::Song(song.clone()),
|
kind: ServiceItemKind::Song(song.clone()),
|
||||||
database_id: song.id,
|
database_id: song.id,
|
||||||
title: song.title.clone(),
|
title: song.title.clone(),
|
||||||
slides: slides,
|
slides,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -279,7 +278,7 @@ impl From<&Video> for ServiceItem {
|
||||||
kind: ServiceItemKind::Video(video.clone()),
|
kind: ServiceItemKind::Video(video.clone()),
|
||||||
database_id: video.id,
|
database_id: video.id,
|
||||||
title: video.title.clone(),
|
title: video.title.clone(),
|
||||||
slides: slides,
|
slides,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -300,7 +299,7 @@ impl From<&Image> for ServiceItem {
|
||||||
kind: ServiceItemKind::Image(image.clone()),
|
kind: ServiceItemKind::Image(image.clone()),
|
||||||
database_id: image.id,
|
database_id: image.id,
|
||||||
title: image.title.clone(),
|
title: image.title.clone(),
|
||||||
slides: slides,
|
slides,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -323,7 +322,7 @@ impl From<&Presentation> for ServiceItem {
|
||||||
),
|
),
|
||||||
database_id: presentation.id,
|
database_id: presentation.id,
|
||||||
title: presentation.title.clone(),
|
title: presentation.title.clone(),
|
||||||
slides: slides,
|
slides,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
|
@ -107,9 +107,9 @@ impl TryFrom<&Background> for Video {
|
||||||
fn try_from(
|
fn try_from(
|
||||||
value: &Background,
|
value: &Background,
|
||||||
) -> std::result::Result<Self, Self::Error> {
|
) -> std::result::Result<Self, Self::Error> {
|
||||||
Video::new(
|
Self::new(
|
||||||
&url::Url::from_file_path(value.path.clone())
|
&url::Url::from_file_path(value.path.clone())
|
||||||
.map_err(|_| ParseError::BackgroundNotVideo)?,
|
.map_err(|()| ParseError::BackgroundNotVideo)?,
|
||||||
)
|
)
|
||||||
.map_err(|_| ParseError::BackgroundNotVideo)
|
.map_err(|_| ParseError::BackgroundNotVideo)
|
||||||
}
|
}
|
||||||
|
@ -121,9 +121,9 @@ impl TryFrom<Background> for Video {
|
||||||
fn try_from(
|
fn try_from(
|
||||||
value: Background,
|
value: Background,
|
||||||
) -> std::result::Result<Self, Self::Error> {
|
) -> std::result::Result<Self, Self::Error> {
|
||||||
Video::new(
|
Self::new(
|
||||||
&url::Url::from_file_path(value.path)
|
&url::Url::from_file_path(value.path)
|
||||||
.map_err(|_| ParseError::BackgroundNotVideo)?,
|
.map_err(|()| ParseError::BackgroundNotVideo)?,
|
||||||
)
|
)
|
||||||
.map_err(|_| ParseError::BackgroundNotVideo)
|
.map_err(|_| ParseError::BackgroundNotVideo)
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ impl TryFrom<Background> for Video {
|
||||||
impl TryFrom<String> for Background {
|
impl TryFrom<String> for Background {
|
||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
Background::try_from(value.as_str())
|
Self::try_from(value.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ impl TryFrom<PathBuf> for Background {
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
let path = path.replace("~", &home);
|
let path = path.replace('~', &home);
|
||||||
PathBuf::from(path)
|
PathBuf::from(path)
|
||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
|
@ -192,10 +192,10 @@ impl TryFrom<&str> for Background {
|
||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
let value = value.trim_start_matches("file://");
|
let value = value.trim_start_matches("file://");
|
||||||
if value.starts_with("~") {
|
if value.starts_with('~') {
|
||||||
if let Some(home) = dirs::home_dir() {
|
if let Some(home) = dirs::home_dir() {
|
||||||
if let Some(home) = home.to_str() {
|
if let Some(home) = home.to_str() {
|
||||||
let value = value.replace("~", home);
|
let value = value.replace('~', home);
|
||||||
Self::try_from(PathBuf::from(value))
|
Self::try_from(PathBuf::from(value))
|
||||||
} else {
|
} else {
|
||||||
Self::try_from(PathBuf::from(value))
|
Self::try_from(PathBuf::from(value))
|
||||||
|
@ -252,9 +252,9 @@ impl Display for ParseError {
|
||||||
impl From<String> for BackgroundKind {
|
impl From<String> for BackgroundKind {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
if value == "image" {
|
if value == "image" {
|
||||||
BackgroundKind::Image
|
Self::Image
|
||||||
} else {
|
} else {
|
||||||
BackgroundKind::Video
|
Self::Video
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,7 +281,7 @@ impl Slide {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_font_size(mut self, font_size: i32) -> Self {
|
pub const fn set_font_size(mut self, font_size: i32) -> Self {
|
||||||
self.font_size = font_size;
|
self.font_size = font_size;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -291,12 +291,12 @@ impl Slide {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_pdf_index(mut self, pdf_index: u32) -> Self {
|
pub const fn set_pdf_index(mut self, pdf_index: u32) -> Self {
|
||||||
self.pdf_index = pdf_index;
|
self.pdf_index = pdf_index;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn background(&self) -> &Background {
|
pub const fn background(&self) -> &Background {
|
||||||
&self.background
|
&self.background
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,11 +304,11 @@ impl Slide {
|
||||||
self.text.clone()
|
self.text.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_alignment(&self) -> TextAlignment {
|
pub const fn text_alignment(&self) -> TextAlignment {
|
||||||
self.text_alignment
|
self.text_alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font_size(&self) -> i32 {
|
pub const fn font_size(&self) -> i32 {
|
||||||
self.font_size
|
self.font_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +316,7 @@ impl Slide {
|
||||||
self.font.clone()
|
self.font.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn video_loop(&self) -> bool {
|
pub const fn video_loop(&self) -> bool {
|
||||||
self.video_loop
|
self.video_loop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,13 +328,13 @@ impl Slide {
|
||||||
self.pdf_page.clone()
|
self.pdf_page.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pdf_index(&self) -> u32 {
|
pub const fn pdf_index(&self) -> u32 {
|
||||||
self.pdf_index
|
self.pdf_index
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn song_slides(song: &Song) -> Result<Vec<Self>> {
|
pub fn song_slides(song: &Song) -> Result<Vec<Self>> {
|
||||||
let lyrics = song.get_lyrics()?;
|
let lyrics = song.get_lyrics()?;
|
||||||
let slides: Vec<Slide> = lyrics
|
let slides: Vec<Self> = lyrics
|
||||||
.iter()
|
.iter()
|
||||||
.map(|l| {
|
.map(|l| {
|
||||||
let song = song.clone();
|
let song = song.clone();
|
||||||
|
@ -358,7 +358,7 @@ impl Slide {
|
||||||
Ok(slides)
|
Ok(slides)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_index(&mut self, index: i32) {
|
pub(crate) const fn set_index(&mut self, index: i32) {
|
||||||
self.id = index;
|
self.id = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,7 +381,7 @@ impl From<&Value> for Slide {
|
||||||
fn from(value: &Value) -> Self {
|
fn from(value: &Value) -> Self {
|
||||||
match value {
|
match value {
|
||||||
Value::List(list) => lisp_to_slide(list),
|
Value::List(list) => lisp_to_slide(list),
|
||||||
_ => Slide::default(),
|
_ => Self::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -404,7 +404,7 @@ fn lisp_to_slide(lisp: &Vec<Value>) -> Slide {
|
||||||
slide = slide.background(lisp_to_background(background));
|
slide = slide.background(lisp_to_background(background));
|
||||||
} else {
|
} else {
|
||||||
slide = slide.background(Background::default());
|
slide = slide.background(Background::default());
|
||||||
};
|
}
|
||||||
|
|
||||||
let text_position = lisp.iter().position(|v| match v {
|
let text_position = lisp.iter().position(|v| match v {
|
||||||
Value::List(vec) => {
|
Value::List(vec) => {
|
||||||
|
@ -691,9 +691,7 @@ impl SlideBuilder {
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Default::default()
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
use std::{collections::HashMap, option::Option, path::PathBuf};
|
use std::{collections::HashMap, option::Option, path::PathBuf};
|
||||||
|
|
||||||
use cosmic::iced::Executor;
|
|
||||||
use crisp::types::{Keyword, Symbol, Value};
|
use crisp::types::{Keyword, Symbol, Value};
|
||||||
use miette::{miette, IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result, miette};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
pool::PoolConnection, query, sqlite::SqliteRow, FromRow, Row,
|
FromRow, Row, Sqlite, SqliteConnection, SqlitePool,
|
||||||
Sqlite, SqliteConnection, SqlitePool,
|
pool::PoolConnection, query, sqlite::SqliteRow,
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{core::slide, Slide, SlideBuilder};
|
use crate::{Slide, SlideBuilder, core::slide};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
content::Content,
|
content::Content,
|
||||||
|
@ -128,7 +127,9 @@ impl FromRow<'_, SqliteRow> for Song {
|
||||||
})),
|
})),
|
||||||
verse_order: Some({
|
verse_order: Some({
|
||||||
let str: &str = row.try_get(0)?;
|
let str: &str = row.try_get(0)?;
|
||||||
str.split(' ').map(|s| s.to_string()).collect()
|
str.split(' ')
|
||||||
|
.map(std::string::ToString::to_string)
|
||||||
|
.collect()
|
||||||
}),
|
}),
|
||||||
background: {
|
background: {
|
||||||
let string: String = row.try_get(7)?;
|
let string: String = row.try_get(7)?;
|
||||||
|
@ -250,9 +251,7 @@ pub fn lisp_to_song(list: Vec<Value>) -> Song {
|
||||||
.position(|v| v == &Value::Keyword(Keyword::from("title")))
|
.position(|v| v == &Value::Keyword(Keyword::from("title")))
|
||||||
{
|
{
|
||||||
let pos = key_pos + 1;
|
let pos = key_pos + 1;
|
||||||
list.get(pos)
|
list.get(pos).map_or(String::from("song"), String::from)
|
||||||
.map(String::from)
|
|
||||||
.unwrap_or(String::from("song"))
|
|
||||||
} else {
|
} else {
|
||||||
String::from("song")
|
String::from("song")
|
||||||
};
|
};
|
||||||
|
@ -319,30 +318,30 @@ pub fn lisp_to_song(list: Vec<Value>) -> Song {
|
||||||
let lyric = String::from(&lyric[1]);
|
let lyric = String::from(&lyric[1]);
|
||||||
|
|
||||||
let verse_title = match lyric_verse.as_str() {
|
let verse_title = match lyric_verse.as_str() {
|
||||||
"i1" => r#"\n\nIntro 1\n"#,
|
"i1" => r"\n\nIntro 1\n",
|
||||||
"i2" => r#"\n\nIntro 1\n"#,
|
"i2" => r"\n\nIntro 1\n",
|
||||||
"v1" => r#"\n\nVerse 1\n"#,
|
"v1" => r"\n\nVerse 1\n",
|
||||||
"v2" => r#"\n\nVerse 2\n"#,
|
"v2" => r"\n\nVerse 2\n",
|
||||||
"v3" => r#"\n\nVerse 3\n"#,
|
"v3" => r"\n\nVerse 3\n",
|
||||||
"v4" => r#"\n\nVerse 4\n"#,
|
"v4" => r"\n\nVerse 4\n",
|
||||||
"v5" => r#"\n\nVerse 5\n"#,
|
"v5" => r"\n\nVerse 5\n",
|
||||||
"c1" => r#"\n\nChorus 1\n"#,
|
"c1" => r"\n\nChorus 1\n",
|
||||||
"c2" => r#"\n\nChorus 2\n"#,
|
"c2" => r"\n\nChorus 2\n",
|
||||||
"c3" => r#"\n\nChorus 3\n"#,
|
"c3" => r"\n\nChorus 3\n",
|
||||||
"c4" => r#"\n\nChorus 4\n"#,
|
"c4" => r"\n\nChorus 4\n",
|
||||||
"b1" => r#"\n\nBridge 1\n"#,
|
"b1" => r"\n\nBridge 1\n",
|
||||||
"b2" => r#"\n\nBridge 2\n"#,
|
"b2" => r"\n\nBridge 2\n",
|
||||||
"e1" => r#"\n\nEnding 1\n"#,
|
"e1" => r"\n\nEnding 1\n",
|
||||||
"e2" => r#"\n\nEnding 2\n"#,
|
"e2" => r"\n\nEnding 2\n",
|
||||||
"o1" => r#"\n\nOther 1\n"#,
|
"o1" => r"\n\nOther 1\n",
|
||||||
"o2" => r#"\n\nOther 2\n"#,
|
"o2" => r"\n\nOther 2\n",
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
|
|
||||||
let lyric = format!("{verse_title}{lyric}");
|
let lyric = format!("{verse_title}{lyric}");
|
||||||
let lyric = lyric.replace(
|
let lyric = lyric.replace(
|
||||||
"\\n", r#"
|
"\\n", r"
|
||||||
"#,
|
",
|
||||||
);
|
);
|
||||||
lyrics.push(lyric);
|
lyrics.push(lyric);
|
||||||
}
|
}
|
||||||
|
@ -392,15 +391,15 @@ impl Model<Song> {
|
||||||
let result = query(r#"SELECT verse_order as "verse_order!", font_size as "font_size!: i32", background_type as "background_type!", horizontal_text_alignment as "horizontal_text_alignment!", vertical_text_alignment as "vertical_text_alignment!", title as "title!", font as "font!", background as "background!", lyrics as "lyrics!", ccli as "ccli!", author as "author!", audio as "audio!", id as "id: i32" from songs"#).fetch_all(db).await;
|
let result = query(r#"SELECT verse_order as "verse_order!", font_size as "font_size!: i32", background_type as "background_type!", horizontal_text_alignment as "horizontal_text_alignment!", vertical_text_alignment as "vertical_text_alignment!", title as "title!", font as "font!", background as "background!", lyrics as "lyrics!", ccli as "ccli!", author as "author!", audio as "audio!", id as "id: i32" from songs"#).fetch_all(db).await;
|
||||||
match result {
|
match result {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
for song in s.into_iter() {
|
for song in s {
|
||||||
match Song::from_row(&song) {
|
match Song::from_row(&song) {
|
||||||
Ok(song) => {
|
Ok(song) => {
|
||||||
let _ = self.add_item(song);
|
let _ = self.add_item(song);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Could not convert song: {e}")
|
error!("Could not convert song: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -425,7 +424,7 @@ pub async fn update_song_in_db(
|
||||||
})
|
})
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
} else {
|
} else {
|
||||||
String::from("")
|
String::new()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -488,13 +487,13 @@ impl Song {
|
||||||
let verse_order = self.verse_order.clone();
|
let verse_order = self.verse_order.clone();
|
||||||
|
|
||||||
let mut lyric_map = HashMap::new();
|
let mut lyric_map = HashMap::new();
|
||||||
let mut verse_title = String::from("");
|
let mut verse_title = String::new();
|
||||||
let mut lyric = String::from("");
|
let mut lyric = String::new();
|
||||||
for (i, line) in raw_lyrics.split('\n').enumerate() {
|
for (i, line) in raw_lyrics.split('\n').enumerate() {
|
||||||
if VERSE_KEYWORDS.contains(&line) {
|
if VERSE_KEYWORDS.contains(&line) {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
lyric_map.insert(verse_title, lyric);
|
lyric_map.insert(verse_title, lyric);
|
||||||
lyric = String::from("");
|
lyric = String::new();
|
||||||
verse_title = line.to_string();
|
verse_title = line.to_string();
|
||||||
} else {
|
} else {
|
||||||
verse_title = line.to_string();
|
verse_title = line.to_string();
|
||||||
|
@ -535,7 +534,7 @@ 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() {
|
||||||
// debug!(lyric = ?lyric)
|
// debug!(lyric = ?lyric)
|
||||||
|
@ -626,7 +625,27 @@ You saved my soul"
|
||||||
let lyrics = song.get_lyrics();
|
let lyrics = song.get_lyrics();
|
||||||
match lyrics {
|
match lyrics {
|
||||||
Ok(lyrics) => {
|
Ok(lyrics) => {
|
||||||
assert_eq!(vec!["From the Day\nI Am They", "When You found me,\nI was so blind\nMy sin was before me,\nI was swallowed by pride", "But out of the darkness,\nYou brought me to Your light\nYou showed me new mercy\nAnd opened up my eyes", "From the day\nYou saved my soul\n'Til the very moment\nWhen I come home", "I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul", "Where brilliant light\nIs all around\nAnd endless joy\nIs the only sound", "Oh, rest my heart\nForever now\nOh, in Your arms\nI'll always be found", "From the day\nYou saved my soul\n'Til the very moment\nWhen I come home", "I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul", "My love is Yours\nMy heart is Yours\nMy life is Yours\nForever", "My love is Yours\nMy heart is Yours\nMy life is Yours\nForever", "From the day\nYou saved my soul\n'Til the very moment\nWhen I come home", "I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul", "From the day\nYou saved my soul\n'Til the very moment\nWhen I come home", "I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul", "Oh Oh Oh\nFrom the day\nYou saved my soul\n"], lyrics);
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
"From the Day\nI Am They",
|
||||||
|
"When You found me,\nI was so blind\nMy sin was before me,\nI was swallowed by pride",
|
||||||
|
"But out of the darkness,\nYou brought me to Your light\nYou showed me new mercy\nAnd opened up my eyes",
|
||||||
|
"From the day\nYou saved my soul\n'Til the very moment\nWhen I come home",
|
||||||
|
"I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul",
|
||||||
|
"Where brilliant light\nIs all around\nAnd endless joy\nIs the only sound",
|
||||||
|
"Oh, rest my heart\nForever now\nOh, in Your arms\nI'll always be found",
|
||||||
|
"From the day\nYou saved my soul\n'Til the very moment\nWhen I come home",
|
||||||
|
"I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul",
|
||||||
|
"My love is Yours\nMy heart is Yours\nMy life is Yours\nForever",
|
||||||
|
"My love is Yours\nMy heart is Yours\nMy life is Yours\nForever",
|
||||||
|
"From the day\nYou saved my soul\n'Til the very moment\nWhen I come home",
|
||||||
|
"I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul",
|
||||||
|
"From the day\nYou saved my soul\n'Til the very moment\nWhen I come home",
|
||||||
|
"I'll sing, I'll dance,\nMy heart will overflow\nFrom the day\nYou saved my soul",
|
||||||
|
"Oh Oh Oh\nFrom the day\nYou saved my soul\n"
|
||||||
|
],
|
||||||
|
lyrics
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
assert!(false, "{:?}", e)
|
assert!(false, "{:?}", e)
|
||||||
|
|
|
@ -11,7 +11,9 @@ pub fn bg_from_video(
|
||||||
video: &Path,
|
video: &Path,
|
||||||
screenshot: &Path,
|
screenshot: &Path,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
if !screenshot.exists() {
|
if screenshot.exists() {
|
||||||
|
debug!("Screenshot already exists");
|
||||||
|
} else {
|
||||||
let output_duration = Command::new("ffprobe")
|
let output_duration = Command::new("ffprobe")
|
||||||
.args(["-i", &video.to_string_lossy()])
|
.args(["-i", &video.to_string_lossy()])
|
||||||
.output()
|
.output()
|
||||||
|
@ -26,9 +28,9 @@ pub fn bg_from_video(
|
||||||
let mut duration = log.split_off(duration_index + 10);
|
let mut duration = log.split_off(duration_index + 10);
|
||||||
duration.truncate(11);
|
duration.truncate(11);
|
||||||
// debug!("rust-duration-is: {duration}");
|
// debug!("rust-duration-is: {duration}");
|
||||||
let mut hours = String::from("");
|
let mut hours = String::new();
|
||||||
let mut minutes = String::from("");
|
let mut minutes = String::new();
|
||||||
let mut seconds = String::from("");
|
let mut seconds = String::new();
|
||||||
for (i, c) in duration.chars().enumerate() {
|
for (i, c) in duration.chars().enumerate() {
|
||||||
if i <= 1 {
|
if i <= 1 {
|
||||||
hours.push(c);
|
hours.push(c);
|
||||||
|
@ -63,8 +65,6 @@ pub fn bg_from_video(
|
||||||
.expect("failed to execute ffmpeg");
|
.expect("failed to execute ffmpeg");
|
||||||
// io::stdout().write_all(&output.stdout).unwrap();
|
// io::stdout().write_all(&output.stdout).unwrap();
|
||||||
// io::stderr().write_all(&output.stderr).unwrap();
|
// io::stderr().write_all(&output.stderr).unwrap();
|
||||||
} else {
|
|
||||||
debug!("Screenshot already exists");
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,12 @@ use super::{
|
||||||
service_items::ServiceTrait,
|
service_items::ServiceTrait,
|
||||||
slide::Slide,
|
slide::Slide,
|
||||||
};
|
};
|
||||||
use cosmic::iced::Executor;
|
|
||||||
use crisp::types::{Keyword, Symbol, Value};
|
use crisp::types::{Keyword, Symbol, Value};
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
pool::PoolConnection, query, query_as, Sqlite, SqliteConnection,
|
Sqlite, SqliteConnection, SqlitePool, pool::PoolConnection,
|
||||||
SqlitePool,
|
query, query_as,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
@ -57,8 +56,9 @@ impl Content for Video {
|
||||||
if self.path.exists() {
|
if self.path.exists() {
|
||||||
self.path
|
self.path
|
||||||
.file_name()
|
.file_name()
|
||||||
.map(|f| f.to_string_lossy().to_string())
|
.map_or("Missing video".into(), |f| {
|
||||||
.unwrap_or("Missing video".into())
|
f.to_string_lossy().to_string()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
"Missing video".into()
|
"Missing video".into()
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ impl From<&Value> for Video {
|
||||||
let path =
|
let path =
|
||||||
p.to_str().unwrap_or_default().to_string();
|
p.to_str().unwrap_or_default().to_string();
|
||||||
let title =
|
let title =
|
||||||
path.rsplit_once("/").unwrap_or_default().1;
|
path.rsplit_once('/').unwrap_or_default().1;
|
||||||
title.to_string()
|
title.to_string()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -124,8 +124,7 @@ impl From<&Value> for Video {
|
||||||
}) {
|
}) {
|
||||||
let pos = loop_pos + 1;
|
let pos = loop_pos + 1;
|
||||||
list.get(pos)
|
list.get(pos)
|
||||||
.map(|l| String::from(l) == *"true")
|
.is_some_and(|l| String::from(l) == *"true")
|
||||||
.unwrap_or_default()
|
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
@ -193,14 +192,16 @@ impl Model<Video> {
|
||||||
let result = query_as!(Video, r#"SELECT title as "title!", file_path as "path!", start_time as "start_time!: f32", end_time as "end_time!: f32", loop as "looping!", id as "id: i32" from videos"#).fetch_all(db).await;
|
let result = query_as!(Video, r#"SELECT title as "title!", file_path as "path!", start_time as "start_time!: f32", end_time as "end_time!: f32", loop as "looping!", id as "id: i32" from videos"#).fetch_all(db).await;
|
||||||
match result {
|
match result {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
for video in v.into_iter() {
|
for video in v {
|
||||||
let _ = self.add_item(video);
|
let _ = self.add_item(video);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("There was an error in converting videos: {e}")
|
error!(
|
||||||
|
"There was an error in converting videos: {e}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +212,7 @@ pub async fn update_video_in_db(
|
||||||
let path = video
|
let path = video
|
||||||
.path
|
.path
|
||||||
.to_str()
|
.to_str()
|
||||||
.map(|s| s.to_string())
|
.map(std::string::ToString::to_string)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
query!(
|
query!(
|
||||||
r#"UPDATE videos SET title = $2, file_path = $3, start_time = $4, end_time = $5, loop = $6 WHERE id = $1"#,
|
r#"UPDATE videos SET title = $2, file_path = $3, start_time = $4, end_time = $5, loop = $6 WHERE id = $1"#,
|
||||||
|
|
104
src/main.rs
104
src/main.rs
|
@ -1,31 +1,32 @@
|
||||||
use clap::{command, Parser};
|
use clap::{Parser, command};
|
||||||
use core::service_items::ServiceItem;
|
use core::service_items::ServiceItem;
|
||||||
use core::slide::*;
|
use core::slide::{
|
||||||
|
Background, BackgroundKind, Slide, SlideBuilder, TextAlignment,
|
||||||
|
};
|
||||||
use core::songs::Song;
|
use core::songs::Song;
|
||||||
use cosmic::app::context_drawer::ContextDrawer;
|
use cosmic::app::context_drawer::ContextDrawer;
|
||||||
use cosmic::app::{Core, Settings, Task};
|
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, event, window, Length, Point};
|
use cosmic::iced::{self, Length, Point, event, window};
|
||||||
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::segmented_button::Entity;
|
|
||||||
use cosmic::widget::text;
|
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, mouse_area, nav_bar, search_input,
|
Space, button, horizontal_space, mouse_area, nav_bar,
|
||||||
tooltip, vertical_space, Space,
|
search_input, tooltip, vertical_space,
|
||||||
};
|
};
|
||||||
use cosmic::widget::{icon, slider};
|
use cosmic::widget::{icon, slider};
|
||||||
use cosmic::{executor, Application, ApplicationExt, Element};
|
use cosmic::{Application, ApplicationExt, Element, executor};
|
||||||
use cosmic::{widget::Container, Theme};
|
|
||||||
use crisp::types::Value;
|
use crisp::types::Value;
|
||||||
use lisp::parse_lisp;
|
use lisp::parse_lisp;
|
||||||
use miette::{miette, Result};
|
use miette::{Result, miette};
|
||||||
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;
|
||||||
|
@ -34,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};
|
||||||
|
@ -94,9 +95,9 @@ fn main() -> Result<()> {
|
||||||
.map_err(|e| miette!("Invalid things... {}", e))
|
.map_err(|e| miette!("Invalid things... {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(_state: &App) -> Theme {
|
// fn theme(_state: &App) -> Theme {
|
||||||
Theme::dark()
|
// Theme::dark()
|
||||||
}
|
// }
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
core: Core,
|
core: Core,
|
||||||
|
@ -135,9 +136,6 @@ enum Message {
|
||||||
Quit,
|
Quit,
|
||||||
Key(Key, Modifiers),
|
Key(Key, Modifiers),
|
||||||
None,
|
None,
|
||||||
DndLeave(Entity),
|
|
||||||
DndEnter(Entity, Vec<String>),
|
|
||||||
DndDrop,
|
|
||||||
EditorToggle(bool),
|
EditorToggle(bool),
|
||||||
ChangeServiceItem(usize),
|
ChangeServiceItem(usize),
|
||||||
AddServiceItem(usize, ServiceItem),
|
AddServiceItem(usize, ServiceItem),
|
||||||
|
@ -233,11 +231,11 @@ impl cosmic::Application for App {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// nav_model.activate_position(0);
|
// nav_model.activate_position(0);
|
||||||
let mut app = App {
|
let mut app = Self {
|
||||||
presenter,
|
presenter,
|
||||||
core,
|
core,
|
||||||
nav_model,
|
nav_model,
|
||||||
service: items.clone(),
|
service: items,
|
||||||
file: PathBuf::default(),
|
file: PathBuf::default(),
|
||||||
windows,
|
windows,
|
||||||
presentation_open: false,
|
presentation_open: false,
|
||||||
|
@ -248,7 +246,7 @@ impl cosmic::Application for App {
|
||||||
song_editor,
|
song_editor,
|
||||||
searching: false,
|
searching: false,
|
||||||
search_results: vec![],
|
search_results: vec![],
|
||||||
search_query: "".into(),
|
search_query: String::new(),
|
||||||
search_id: cosmic::widget::Id::unique(),
|
search_id: cosmic::widget::Id::unique(),
|
||||||
current_item: (0, 0),
|
current_item: (0, 0),
|
||||||
library_dragged_item: None,
|
library_dragged_item: None,
|
||||||
|
@ -259,11 +257,11 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
if input.ui {
|
if input.ui {
|
||||||
debug!("main view");
|
debug!("main view");
|
||||||
batch.push(app.update_title())
|
batch.push(app.update_title());
|
||||||
} else {
|
} else {
|
||||||
debug!("window view");
|
debug!("window view");
|
||||||
batch.push(app.show_window())
|
batch.push(app.show_window());
|
||||||
};
|
}
|
||||||
|
|
||||||
batch.push(app.add_library());
|
batch.push(app.add_library());
|
||||||
// batch.push(app.add_service(items, Arc::clone(&fontdb)));
|
// batch.push(app.add_service(items, Arc::clone(&fontdb)));
|
||||||
|
@ -403,7 +401,7 @@ impl cosmic::Application for App {
|
||||||
.fold(0, |a, item| a + item.slides.len());
|
.fold(0, |a, item| a + item.slides.len());
|
||||||
|
|
||||||
let total_slides_text =
|
let total_slides_text =
|
||||||
format!("Total Slides: {}", total_slides);
|
format!("Total Slides: {total_slides}");
|
||||||
let row = row![
|
let row = row![
|
||||||
text::body(total_items_text),
|
text::body(total_items_text),
|
||||||
text::body(total_slides_text)
|
text::body(total_slides_text)
|
||||||
|
@ -635,7 +633,7 @@ impl cosmic::Application for App {
|
||||||
cosmic::Action::App(
|
cosmic::Action::App(
|
||||||
Message::Present(m),
|
Message::Present(m),
|
||||||
)
|
)
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
|
@ -661,7 +659,7 @@ impl cosmic::Application for App {
|
||||||
m,
|
m,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
|
@ -694,7 +692,7 @@ impl cosmic::Application for App {
|
||||||
cosmic::Action::App(
|
cosmic::Action::App(
|
||||||
Message::Present(m),
|
Message::Present(m),
|
||||||
)
|
)
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
|
@ -735,7 +733,7 @@ impl cosmic::Application for App {
|
||||||
m,
|
m,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
|
@ -816,10 +814,8 @@ impl cosmic::Application for App {
|
||||||
});
|
});
|
||||||
|
|
||||||
self.windows.push(id);
|
self.windows.push(id);
|
||||||
_ = self.set_window_title(
|
_ = self
|
||||||
format!("window_{}", count),
|
.set_window_title(format!("window_{count}"), id);
|
||||||
id,
|
|
||||||
);
|
|
||||||
|
|
||||||
spawn_window.map(|id| {
|
spawn_window.map(|id| {
|
||||||
cosmic::Action::App(Message::WindowOpened(
|
cosmic::Action::App(Message::WindowOpened(
|
||||||
|
@ -873,34 +869,6 @@ impl cosmic::Application for App {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Quit => cosmic::iced::exit(),
|
Message::Quit => cosmic::iced::exit(),
|
||||||
Message::DndEnter(entity, data) => {
|
|
||||||
debug!(?entity);
|
|
||||||
debug!(?data);
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Message::DndDrop => {
|
|
||||||
// debug!(?entity);
|
|
||||||
// debug!(?action);
|
|
||||||
// debug!(?service_item);
|
|
||||||
|
|
||||||
if let Some(library) = &self.library
|
|
||||||
&& let Some((lib, item)) = library.dragged_item
|
|
||||||
{
|
|
||||||
// match lib {
|
|
||||||
// core::model::LibraryKind::Song => ,
|
|
||||||
// core::model::LibraryKind::Video => todo!(),
|
|
||||||
// core::model::LibraryKind::Image => todo!(),
|
|
||||||
// core::model::LibraryKind::Presentation => todo!(),
|
|
||||||
// }
|
|
||||||
let item = library.get_song(item).unwrap();
|
|
||||||
let item = ServiceItem::from(item);
|
|
||||||
self.nav_model
|
|
||||||
.insert()
|
|
||||||
.text(item.title.clone())
|
|
||||||
.data(item);
|
|
||||||
}
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Message::AddLibrary(library) => {
|
Message::AddLibrary(library) => {
|
||||||
self.library = Some(library);
|
self.library = Some(library);
|
||||||
Task::none()
|
Task::none()
|
||||||
|
@ -910,10 +878,6 @@ impl cosmic::Application for App {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::None => Task::none(),
|
Message::None => Task::none(),
|
||||||
Message::DndLeave(entity) => {
|
|
||||||
// debug!(?entity);
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Message::EditorToggle(edit) => {
|
Message::EditorToggle(edit) => {
|
||||||
if edit {
|
if edit {
|
||||||
self.editor_mode = Some(EditorMode::Song);
|
self.editor_mode = Some(EditorMode::Song);
|
||||||
|
@ -992,7 +956,7 @@ impl cosmic::Application for App {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::CloseSearch => {
|
Message::CloseSearch => {
|
||||||
self.search_query = "".into();
|
self.search_query = String::new();
|
||||||
self.search_results = vec![];
|
self.search_results = vec![];
|
||||||
self.searching = false;
|
self.searching = false;
|
||||||
Task::none()
|
Task::none()
|
||||||
|
@ -1006,12 +970,12 @@ impl cosmic::Application for App {
|
||||||
song_editor::Message::ChangeSong(song),
|
song_editor::Message::ChangeSong(song),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
ServiceItemKind::Video(video) => todo!(),
|
ServiceItemKind::Video(_video) => todo!(),
|
||||||
ServiceItemKind::Image(image) => todo!(),
|
ServiceItemKind::Image(_image) => todo!(),
|
||||||
ServiceItemKind::Presentation(presentation) => {
|
ServiceItemKind::Presentation(_presentation) => {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
ServiceItemKind::Content(slide) => todo!(),
|
ServiceItemKind::Content(_slide) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1198,7 +1162,7 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_library(&mut self) -> Task<Message> {
|
fn add_library(&self) -> Task<Message> {
|
||||||
Task::perform(async move { Library::new().await }, |x| {
|
Task::perform(async move { Library::new().await }, |x| {
|
||||||
cosmic::Action::App(Message::AddLibrary(x))
|
cosmic::Action::App(Message::AddLibrary(x))
|
||||||
})
|
})
|
||||||
|
@ -1220,7 +1184,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_service(
|
fn add_service(
|
||||||
&mut self,
|
&self,
|
||||||
items: Vec<ServiceItem>,
|
items: Vec<ServiceItem>,
|
||||||
fontdb: Arc<fontdb::Database>,
|
fontdb: Arc<fontdb::Database>,
|
||||||
) -> Task<Message> {
|
) -> Task<Message> {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced::{
|
iced::{
|
||||||
alignment::Vertical, clipboard::dnd::DndAction,
|
alignment::Vertical, clipboard::dnd::DndAction,
|
||||||
|
@ -7,9 +9,10 @@ use cosmic::{
|
||||||
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, context_menu, horizontal_space, icon,
|
||||||
responsive, row, scrollable, text, text_input, Container,
|
menu::{self, Action as MenuAction},
|
||||||
DndSource, Space, Widget,
|
mouse_area, responsive, row, scrollable, text, text_input,
|
||||||
|
Container, DndSource, Space,
|
||||||
},
|
},
|
||||||
Element, Task,
|
Element, Task,
|
||||||
};
|
};
|
||||||
|
@ -41,6 +44,29 @@ pub(crate) struct Library {
|
||||||
pub dragged_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>,
|
||||||
|
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 {
|
pub(crate) enum Action {
|
||||||
|
@ -53,7 +79,7 @@ pub(crate) enum Action {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum Message {
|
pub(crate) enum Message {
|
||||||
AddItem,
|
AddItem,
|
||||||
RemoveItem,
|
DeleteItem((LibraryKind, i32)),
|
||||||
OpenItem(Option<(LibraryKind, i32)>),
|
OpenItem(Option<(LibraryKind, i32)>),
|
||||||
HoverLibrary(Option<LibraryKind>),
|
HoverLibrary(Option<LibraryKind>),
|
||||||
OpenLibrary(Option<LibraryKind>),
|
OpenLibrary(Option<LibraryKind>),
|
||||||
|
@ -69,6 +95,7 @@ pub(crate) enum Message {
|
||||||
UpdatePresentation(Presentation),
|
UpdatePresentation(Presentation),
|
||||||
PresentationChanged,
|
PresentationChanged,
|
||||||
Error(String),
|
Error(String),
|
||||||
|
OpenContext(i32),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +117,8 @@ impl<'a> Library {
|
||||||
dragged_item: None,
|
dragged_item: None,
|
||||||
editing_item: None,
|
editing_item: None,
|
||||||
db,
|
db,
|
||||||
|
menu_keys: HashMap::new(),
|
||||||
|
context_menu: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +130,16 @@ impl<'a> Library {
|
||||||
match message {
|
match message {
|
||||||
Message::AddItem => (),
|
Message::AddItem => (),
|
||||||
Message::None => (),
|
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) => {
|
Message::OpenItem(item) => {
|
||||||
debug!(?item);
|
debug!(?item);
|
||||||
self.editing_item = item;
|
self.editing_item = item;
|
||||||
|
@ -133,18 +171,18 @@ impl<'a> Library {
|
||||||
if kind != LibraryKind::Song {
|
if kind != LibraryKind::Song {
|
||||||
error!("Not editing a song item");
|
error!("Not editing a song item");
|
||||||
return Action::None;
|
return Action::None;
|
||||||
};
|
}
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.song_library
|
.song_library
|
||||||
.update_item(song.clone(), index)
|
.update_item(song.clone(), index)
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(()) => {
|
||||||
return Action::Task(
|
return Action::Task(
|
||||||
Task::future(self.db.acquire()).and_then(
|
Task::future(self.db.acquire()).and_then(
|
||||||
move |conn| update_in_db(&song, conn),
|
move |conn| update_in_db(&song, conn),
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
Err(_) => todo!(),
|
Err(_) => todo!(),
|
||||||
}
|
}
|
||||||
|
@ -162,13 +200,13 @@ impl<'a> Library {
|
||||||
if kind != LibraryKind::Image {
|
if kind != LibraryKind::Image {
|
||||||
error!("Not editing a image item");
|
error!("Not editing a image item");
|
||||||
return Action::None;
|
return Action::None;
|
||||||
};
|
}
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.image_library
|
.image_library
|
||||||
.update_item(image.clone(), index)
|
.update_item(image.clone(), index)
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(()) => {
|
||||||
return Action::Task(
|
return Action::Task(
|
||||||
Task::future(self.db.acquire()).and_then(
|
Task::future(self.db.acquire()).and_then(
|
||||||
move |conn| {
|
move |conn| {
|
||||||
|
@ -181,7 +219,7 @@ impl<'a> Library {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
Err(_) => todo!(),
|
Err(_) => todo!(),
|
||||||
}
|
}
|
||||||
|
@ -196,13 +234,13 @@ impl<'a> Library {
|
||||||
if kind != LibraryKind::Video {
|
if kind != LibraryKind::Video {
|
||||||
error!("Not editing a video item");
|
error!("Not editing a video item");
|
||||||
return Action::None;
|
return Action::None;
|
||||||
};
|
}
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.video_library
|
.video_library
|
||||||
.update_item(video.clone(), index)
|
.update_item(video.clone(), index)
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(()) => {
|
||||||
return Action::Task(
|
return Action::Task(
|
||||||
Task::future(self.db.acquire()).and_then(
|
Task::future(self.db.acquire()).and_then(
|
||||||
move |conn| {
|
move |conn| {
|
||||||
|
@ -215,7 +253,7 @@ impl<'a> Library {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
Err(_) => todo!(),
|
Err(_) => todo!(),
|
||||||
}
|
}
|
||||||
|
@ -230,13 +268,13 @@ impl<'a> Library {
|
||||||
if kind != LibraryKind::Presentation {
|
if kind != LibraryKind::Presentation {
|
||||||
error!("Not editing a presentation item");
|
error!("Not editing a presentation item");
|
||||||
return Action::None;
|
return Action::None;
|
||||||
};
|
}
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.presentation_library
|
.presentation_library
|
||||||
.update_item(presentation.clone(), index)
|
.update_item(presentation.clone(), index)
|
||||||
{
|
{
|
||||||
Ok(_) => return Action::Task(
|
Ok(()) => return Action::Task(
|
||||||
Task::future(self.db.acquire()).and_then(
|
Task::future(self.db.acquire()).and_then(
|
||||||
move |conn| {
|
move |conn| {
|
||||||
Task::perform(
|
Task::perform(
|
||||||
|
@ -254,7 +292,10 @@ impl<'a> Library {
|
||||||
}
|
}
|
||||||
Message::PresentationChanged => (),
|
Message::PresentationChanged => (),
|
||||||
Message::Error(_) => (),
|
Message::Error(_) => (),
|
||||||
};
|
Message::OpenContext(index) => {
|
||||||
|
self.context_menu = Some(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
Action::None
|
Action::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +356,7 @@ impl<'a> Library {
|
||||||
textm!("Presentations").align_y(Vertical::Center),
|
textm!("Presentations").align_y(Vertical::Center),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
let item_count = model.items.len();
|
let item_count = model.items.len();
|
||||||
row = row.push(horizontal_space());
|
row = row.push(horizontal_space());
|
||||||
row = row
|
row = row
|
||||||
|
@ -373,9 +414,9 @@ impl<'a> Library {
|
||||||
let service_item = item.to_service_item();
|
let service_item = item.to_service_item();
|
||||||
let visual_item = self
|
let visual_item = self
|
||||||
.single_item(index, item, model)
|
.single_item(index, item, model)
|
||||||
.map(|_| Message::None);
|
.map(|()| Message::None);
|
||||||
DndSource::<Message, ServiceItem>::new(
|
DndSource::<Message, ServiceItem>::new({
|
||||||
mouse_area(visual_item)
|
let mouse_area = Element::from(mouse_area(visual_item)
|
||||||
.on_drag(Message::DragItem(service_item.clone()))
|
.on_drag(Message::DragItem(service_item.clone()))
|
||||||
.on_enter(Message::HoverItem(
|
.on_enter(Message::HoverItem(
|
||||||
Some((
|
Some((
|
||||||
|
@ -389,13 +430,31 @@ impl<'a> Library {
|
||||||
index as i32,
|
index as i32,
|
||||||
))),
|
))),
|
||||||
)
|
)
|
||||||
|
.on_right_press(Message::OpenContext(index as i32))
|
||||||
.on_exit(Message::HoverItem(None))
|
.on_exit(Message::HoverItem(None))
|
||||||
.on_press(Message::SelectItem(
|
.on_press(Message::SelectItem(
|
||||||
Some((
|
Some((
|
||||||
model.kind,
|
model.kind,
|
||||||
index as i32,
|
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)
|
.action(DndAction::Copy)
|
||||||
.drag_icon({
|
.drag_icon({
|
||||||
|
@ -418,7 +477,7 @@ impl<'a> Library {
|
||||||
)
|
)
|
||||||
}})
|
}})
|
||||||
.drag_content(move || {
|
.drag_content(move || {
|
||||||
service_item.to_owned()
|
service_item.clone()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
},
|
},
|
||||||
|
@ -481,11 +540,10 @@ impl<'a> Library {
|
||||||
.accent_text_color()
|
.accent_text_color()
|
||||||
.into()
|
.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()
|
theme::active().cosmic().control_0().into()
|
||||||
} else {
|
} else {
|
||||||
theme::active()
|
theme::active()
|
||||||
|
@ -563,6 +621,7 @@ impl<'a> Library {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unused_async)]
|
||||||
pub async fn search_items(
|
pub async fn search_items(
|
||||||
&self,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
|
@ -573,14 +632,18 @@ impl<'a> Library {
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|song| song.title.to_lowercase().contains(&query))
|
.filter(|song| song.title.to_lowercase().contains(&query))
|
||||||
.map(|song| song.to_service_item())
|
.map(
|
||||||
|
super::super::core::content::Content::to_service_item,
|
||||||
|
)
|
||||||
.collect();
|
.collect();
|
||||||
let videos: Vec<ServiceItem> = self
|
let videos: Vec<ServiceItem> = self
|
||||||
.video_library
|
.video_library
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|vid| vid.title.to_lowercase().contains(&query))
|
.filter(|vid| vid.title.to_lowercase().contains(&query))
|
||||||
.map(|vid| vid.to_service_item())
|
.map(
|
||||||
|
super::super::core::content::Content::to_service_item,
|
||||||
|
)
|
||||||
.collect();
|
.collect();
|
||||||
let images: Vec<ServiceItem> = self
|
let images: Vec<ServiceItem> = self
|
||||||
.image_library
|
.image_library
|
||||||
|
@ -589,14 +652,18 @@ impl<'a> Library {
|
||||||
.filter(|image| {
|
.filter(|image| {
|
||||||
image.title.to_lowercase().contains(&query)
|
image.title.to_lowercase().contains(&query)
|
||||||
})
|
})
|
||||||
.map(|image| image.to_service_item())
|
.map(
|
||||||
|
super::super::core::content::Content::to_service_item,
|
||||||
|
)
|
||||||
.collect();
|
.collect();
|
||||||
let presentations: Vec<ServiceItem> = self
|
let presentations: Vec<ServiceItem> = self
|
||||||
.presentation_library
|
.presentation_library
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|pres| pres.title.to_lowercase().contains(&query))
|
.filter(|pres| pres.title.to_lowercase().contains(&query))
|
||||||
.map(|pres| pres.to_service_item())
|
.map(
|
||||||
|
super::super::core::content::Content::to_service_item,
|
||||||
|
)
|
||||||
.collect();
|
.collect();
|
||||||
items.extend(videos);
|
items.extend(videos);
|
||||||
items.extend(images);
|
items.extend(images);
|
||||||
|
@ -656,7 +723,7 @@ fn update_in_db(
|
||||||
Task::perform(
|
Task::perform(
|
||||||
update_song_in_db(song.to_owned(), conn).map(
|
update_song_in_db(song.to_owned(), conn).map(
|
||||||
move |r| match r {
|
move |r| match r {
|
||||||
Ok(_) => {
|
Ok(()) => {
|
||||||
warn!(
|
warn!(
|
||||||
"Should have updated song: {:?}",
|
"Should have updated song: {:?}",
|
||||||
song_title
|
song_title
|
||||||
|
@ -667,7 +734,7 @@ fn update_in_db(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|_| Message::SongChanged,
|
|()| Message::SongChanged,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,13 @@ use cosmic::{
|
||||||
iced_widget::{
|
iced_widget::{
|
||||||
scrollable::{
|
scrollable::{
|
||||||
scroll_to, AbsoluteOffset, Direction, Scrollbar,
|
scroll_to, AbsoluteOffset, Direction, Scrollbar,
|
||||||
}, stack, vertical_rule,
|
},
|
||||||
|
stack, vertical_rule,
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widget::{
|
widget::{
|
||||||
container, image, mouse_area, responsive, scrollable, text, Container, Id, Row, Space,
|
container, image, mouse_area, responsive, scrollable, text,
|
||||||
|
Container, Id, Row, Space,
|
||||||
},
|
},
|
||||||
Task,
|
Task,
|
||||||
};
|
};
|
||||||
|
@ -152,7 +154,7 @@ impl Presenter {
|
||||||
absolute_slide_index: 0,
|
absolute_slide_index: 0,
|
||||||
total_slides,
|
total_slides,
|
||||||
video,
|
video,
|
||||||
audio: items[0].slides[0].audio().clone(),
|
audio: items[0].slides[0].audio(),
|
||||||
service: items,
|
service: items,
|
||||||
video_position: 0.0,
|
video_position: 0.0,
|
||||||
hovered_slide: None,
|
hovered_slide: None,
|
||||||
|
@ -219,8 +221,8 @@ impl Presenter {
|
||||||
let offset = AbsoluteOffset {
|
let offset = AbsoluteOffset {
|
||||||
x: {
|
x: {
|
||||||
if self.current_slide_index > 2 {
|
if self.current_slide_index > 2 {
|
||||||
self.current_slide_index as f32 * 187.5
|
(self.current_slide_index as f32)
|
||||||
- 187.5
|
.mul_add(187.5, -187.5)
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
}
|
}
|
||||||
|
@ -254,7 +256,7 @@ impl Presenter {
|
||||||
?current_audio,
|
?current_audio,
|
||||||
"audio needs to change"
|
"audio needs to change"
|
||||||
);
|
);
|
||||||
self.audio = Some(new_audio.clone());
|
self.audio = Some(new_audio);
|
||||||
tasks.push(self.start_audio());
|
tasks.push(self.start_audio());
|
||||||
}
|
}
|
||||||
Some(current_audio) => {
|
Some(current_audio) => {
|
||||||
|
@ -269,10 +271,10 @@ impl Presenter {
|
||||||
?new_audio,
|
?new_audio,
|
||||||
"could not find audio, need to change"
|
"could not find audio, need to change"
|
||||||
);
|
);
|
||||||
self.audio = Some(new_audio.clone());
|
self.audio = Some(new_audio);
|
||||||
tasks.push(self.start_audio());
|
tasks.push(self.start_audio());
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
} else {
|
} else {
|
||||||
self.audio = None;
|
self.audio = None;
|
||||||
self.update(Message::EndAudio);
|
self.update(Message::EndAudio);
|
||||||
|
@ -324,7 +326,7 @@ impl Presenter {
|
||||||
std::time::Duration::from_secs_f32(position),
|
std::time::Duration::from_secs_f32(position),
|
||||||
);
|
);
|
||||||
match video.seek(position, false) {
|
match video.seek(position, false) {
|
||||||
Ok(_) => debug!(
|
Ok(()) => debug!(
|
||||||
"Video position changed: {:?}",
|
"Video position changed: {:?}",
|
||||||
position
|
position
|
||||||
),
|
),
|
||||||
|
@ -353,7 +355,7 @@ impl Presenter {
|
||||||
install_ctx
|
install_ctx
|
||||||
.set_desktop_id(&format!("{}.desktop", "org.chriscochrun.lumina"));
|
.set_desktop_id(&format!("{}.desktop", "org.chriscochrun.lumina"));
|
||||||
let install_detail = missing_plugin.installer_detail();
|
let install_detail = missing_plugin.installer_detail();
|
||||||
println!("installing plugins: {}", install_detail);
|
println!("installing plugins: {install_detail}");
|
||||||
let status = gst_pbutils::missing_plugins::install_plugins_sync(
|
let status = gst_pbutils::missing_plugins::install_plugins_sync(
|
||||||
&[&install_detail],
|
&[&install_detail],
|
||||||
Some(&install_ctx),
|
Some(&install_ctx),
|
||||||
|
@ -389,7 +391,7 @@ impl Presenter {
|
||||||
Message::Error(error) => {
|
Message::Error(error) => {
|
||||||
error!(error);
|
error!(error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
Action::None
|
Action::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,7 +641,7 @@ impl Presenter {
|
||||||
v.set_looping(
|
v.set_looping(
|
||||||
self.current_slide.video_loop(),
|
self.current_slide.video_loop(),
|
||||||
);
|
);
|
||||||
self.video = Some(v)
|
self.video = Some(v);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
|
@ -662,7 +664,7 @@ impl Presenter {
|
||||||
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.1), audio),
|
||||||
|_| Message::None,
|
|()| Message::None,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
debug!(?self.audio, "Apparently this doesn't exist");
|
debug!(?self.audio, "Apparently this doesn't exist");
|
||||||
|
|
|
@ -54,10 +54,10 @@ struct EditorProgram {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SlideEditor {
|
impl SlideEditor {
|
||||||
pub fn view<'a>(
|
pub fn view(
|
||||||
&'a self,
|
&self,
|
||||||
font: Font,
|
font: Font,
|
||||||
) -> cosmic::Element<'a, SlideWidget> {
|
) -> cosmic::Element<'_, SlideWidget> {
|
||||||
container(
|
container(
|
||||||
widget::canvas(&self.program)
|
widget::canvas(&self.program)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
|
@ -122,10 +122,10 @@ impl<'a> Program<SlideWidget, cosmic::Theme, cosmic::Renderer>
|
||||||
match event {
|
match event {
|
||||||
canvas::Event::Mouse(event) => match event {
|
canvas::Event::Mouse(event) => match event {
|
||||||
cosmic::iced::mouse::Event::CursorEntered => {
|
cosmic::iced::mouse::Event::CursorEntered => {
|
||||||
debug!("cursor entered")
|
debug!("cursor entered");
|
||||||
}
|
}
|
||||||
cosmic::iced::mouse::Event::CursorLeft => {
|
cosmic::iced::mouse::Event::CursorLeft => {
|
||||||
debug!("cursor left")
|
debug!("cursor left");
|
||||||
}
|
}
|
||||||
cosmic::iced::mouse::Event::CursorMoved {
|
cosmic::iced::mouse::Event::CursorMoved {
|
||||||
position,
|
position,
|
||||||
|
@ -140,7 +140,7 @@ impl<'a> Program<SlideWidget, cosmic::Theme, cosmic::Renderer>
|
||||||
}
|
}
|
||||||
cosmic::iced::mouse::Event::ButtonPressed(button) => {
|
cosmic::iced::mouse::Event::ButtonPressed(button) => {
|
||||||
// self.mouse_button_pressed = Some(button);
|
// self.mouse_button_pressed = Some(button);
|
||||||
debug!(?button, "mouse button pressed")
|
debug!(?button, "mouse button pressed");
|
||||||
}
|
}
|
||||||
cosmic::iced::mouse::Event::ButtonReleased(
|
cosmic::iced::mouse::Event::ButtonReleased(
|
||||||
button,
|
button,
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
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::{Family, Stretch, Style, Weight},
|
|
||||||
Font, Length,
|
Font, Length,
|
||||||
|
font::{Family, Stretch, Style, Weight},
|
||||||
},
|
},
|
||||||
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, 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 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::{
|
||||||
core::{service_items::ServiceTrait, songs::Song},
|
Background, BackgroundKind, core::songs::Song,
|
||||||
ui::slide_editor::SlideEditor,
|
ui::slide_editor::SlideEditor,
|
||||||
Background, BackgroundKind,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SongEditor {
|
pub struct SongEditor {
|
||||||
pub song: Option<Song>,
|
pub song: Option<Song>,
|
||||||
|
@ -140,11 +139,11 @@ impl SongEditor {
|
||||||
self.song = Some(song.clone());
|
self.song = Some(song.clone());
|
||||||
self.title = song.title;
|
self.title = song.title;
|
||||||
if let Some(font) = song.font {
|
if let Some(font) = song.font {
|
||||||
self.font = font
|
self.font = font;
|
||||||
};
|
}
|
||||||
if let Some(font_size) = song.font_size {
|
if let Some(font_size) = song.font_size {
|
||||||
self.font_size = font_size as usize
|
self.font_size = font_size as usize;
|
||||||
};
|
}
|
||||||
if let Some(verse_order) = song.verse_order {
|
if let Some(verse_order) = song.verse_order {
|
||||||
self.verse_order = verse_order
|
self.verse_order = verse_order
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -155,18 +154,18 @@ impl SongEditor {
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
if let Some(author) = song.author {
|
if let Some(author) = song.author {
|
||||||
self.author = author
|
self.author = author;
|
||||||
};
|
}
|
||||||
if let Some(audio) = song.audio {
|
if let Some(audio) = song.audio {
|
||||||
self.audio = audio
|
self.audio = audio;
|
||||||
};
|
}
|
||||||
if let Some(ccli) = song.ccli {
|
if let Some(ccli) = song.ccli {
|
||||||
self.ccli = ccli
|
self.ccli = ccli;
|
||||||
};
|
}
|
||||||
if let Some(lyrics) = song.lyrics {
|
if let Some(lyrics) = song.lyrics {
|
||||||
self.lyrics =
|
self.lyrics =
|
||||||
text_editor::Content::with_text(&lyrics)
|
text_editor::Content::with_text(&lyrics);
|
||||||
};
|
}
|
||||||
self.background_video(&song.background);
|
self.background_video(&song.background);
|
||||||
self.background = song.background;
|
self.background = song.background;
|
||||||
}
|
}
|
||||||
|
@ -212,8 +211,8 @@ impl SongEditor {
|
||||||
self.verse_order = verse_order.clone();
|
self.verse_order = verse_order.clone();
|
||||||
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(' ')
|
||||||
.map(|s| s.to_owned())
|
.map(std::borrow::ToOwned::to_owned)
|
||||||
.collect();
|
.collect();
|
||||||
song.verse_order = Some(verse_order);
|
song.verse_order = Some(verse_order);
|
||||||
return self.update_song(song);
|
return self.update_song(song);
|
||||||
|
@ -244,8 +243,7 @@ impl SongEditor {
|
||||||
Message::ChangeBackground(Ok(path)) => {
|
Message::ChangeBackground(Ok(path)) => {
|
||||||
debug!(?path);
|
debug!(?path);
|
||||||
if let Some(mut song) = self.song.clone() {
|
if let Some(mut song) = self.song.clone() {
|
||||||
let background =
|
let background = Background::try_from(path).ok();
|
||||||
Background::try_from(path.clone()).ok();
|
|
||||||
self.background_video(&background);
|
self.background_video(&background);
|
||||||
song.background = background;
|
song.background = background;
|
||||||
return self.update_song(song);
|
return self.update_song(song);
|
||||||
|
@ -258,7 +256,7 @@ impl SongEditor {
|
||||||
return Action::Task(Task::perform(
|
return Action::Task(Task::perform(
|
||||||
pick_background(),
|
pick_background(),
|
||||||
Message::ChangeBackground,
|
Message::ChangeBackground,
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -416,7 +414,7 @@ order",
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn editing(&self) -> bool {
|
pub const fn editing(&self) -> bool {
|
||||||
self.editing
|
self.editing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,15 +121,18 @@ impl From<&str> for Font {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
|
#[must_use]
|
||||||
pub fn get_name(&self) -> String {
|
pub fn get_name(&self) -> String {
|
||||||
self.name.clone()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_weight(&self) -> Weight {
|
#[must_use]
|
||||||
|
pub const fn get_weight(&self) -> Weight {
|
||||||
self.weight
|
self.weight
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_style(&self) -> Style {
|
#[must_use]
|
||||||
|
pub const fn get_style(&self) -> Style {
|
||||||
self.style
|
self.style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +151,8 @@ impl Font {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(mut self, size: u8) -> Self {
|
#[must_use]
|
||||||
|
pub const fn size(mut self, size: u8) -> Self {
|
||||||
self.size = size;
|
self.size = size;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -161,12 +165,12 @@ impl Hash for Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
pub fn from_hex_str(color: impl AsRef<str>) -> Color {
|
pub fn from_hex_str(color: impl AsRef<str>) -> Self {
|
||||||
match Rgb::from_hex_str(color.as_ref()) {
|
match Rgb::from_hex_str(color.as_ref()) {
|
||||||
Ok(rgb) => Color(rgb),
|
Ok(rgb) => Self(rgb),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("error in making color from hex_str: {:?}", e);
|
error!("error in making color from hex_str: {:?}", e);
|
||||||
Color::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,7 +240,10 @@ impl TextSvg {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alignment(mut self, alignment: TextAlignment) -> Self {
|
pub const fn alignment(
|
||||||
|
mut self,
|
||||||
|
alignment: TextAlignment,
|
||||||
|
) -> Self {
|
||||||
self.alignment = alignment;
|
self.alignment = alignment;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -255,7 +262,7 @@ impl TextSvg {
|
||||||
shadow.spread,
|
shadow.spread,
|
||||||
shadow.color)
|
shadow.color)
|
||||||
} else {
|
} else {
|
||||||
"".into()
|
String::new()
|
||||||
};
|
};
|
||||||
let stroke = if let Some(stroke) = &self.stroke {
|
let stroke = if let Some(stroke) = &self.stroke {
|
||||||
format!(
|
format!(
|
||||||
|
@ -263,17 +270,17 @@ impl TextSvg {
|
||||||
stroke.color, stroke.size
|
stroke.color, stroke.size
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
"".into()
|
String::new()
|
||||||
};
|
};
|
||||||
let size = Size::new(1920.0, 1080.0);
|
let size = Size::new(1920.0, 1080.0);
|
||||||
let font_size = self.font.size as f32;
|
let font_size = f32::from(self.font.size);
|
||||||
let total_lines = self.text.lines().count();
|
let total_lines = self.text.lines().count();
|
||||||
let half_lines = (total_lines / 2) as f32;
|
let half_lines = (total_lines / 2) as f32;
|
||||||
let middle_position = size.height / 2.0;
|
let middle_position = size.height / 2.0;
|
||||||
let line_spacing = 10.0;
|
let line_spacing = 10.0;
|
||||||
let text_and_line_spacing = font_size + line_spacing;
|
let text_and_line_spacing = font_size + line_spacing;
|
||||||
let starting_y_position =
|
let starting_y_position = half_lines
|
||||||
middle_position - (half_lines * text_and_line_spacing);
|
.mul_add(-text_and_line_spacing, middle_position);
|
||||||
|
|
||||||
let text_pieces: Vec<String> = self
|
let text_pieces: Vec<String> = self
|
||||||
.text
|
.text
|
||||||
|
@ -282,8 +289,10 @@ impl TextSvg {
|
||||||
.map(|(index, text)| {
|
.map(|(index, text)| {
|
||||||
format!(
|
format!(
|
||||||
"<tspan x=\"50%\" y=\"{}\">{}</tspan>",
|
"<tspan x=\"50%\" y=\"{}\">{}</tspan>",
|
||||||
|
(index as f32).mul_add(
|
||||||
|
text_and_line_spacing,
|
||||||
starting_y_position
|
starting_y_position
|
||||||
+ (index as f32 * text_and_line_spacing),
|
),
|
||||||
text
|
text
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -350,7 +359,7 @@ impl TextSvg {
|
||||||
self.text
|
self.text
|
||||||
.lines()
|
.lines()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, t)| format!("<tspan x=\"50%\">{}</tspan>", t))
|
.map(|(i, t)| format!("<tspan x=\"50%\">{t}</tspan>"))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -391,7 +400,7 @@ pub fn text_svg_generator(
|
||||||
.shadow(shadow(2, 2, 5, "#000000"))
|
.shadow(shadow(2, 2, 5, "#000000"))
|
||||||
.stroke(stroke(3, "#000"))
|
.stroke(stroke(3, "#000"))
|
||||||
.font(
|
.font(
|
||||||
Font::from(slide.font().clone())
|
Font::from(slide.font())
|
||||||
.size(slide.font_size().try_into().unwrap()),
|
.size(slide.font_size().try_into().unwrap()),
|
||||||
)
|
)
|
||||||
.fontdb(Arc::clone(&fontdb))
|
.fontdb(Arc::clone(&fontdb))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue