mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 14:13:53 +01:00
add option to show images in AdvancedLabel, available through config via textcfg
This commit is contained in:
parent
8c434743f8
commit
b1998e3316
@ -10,6 +10,10 @@ font = ''
|
|||||||
# \h1.0;: set the height-alignment (to the default: 1.0 / align text to be on one baseline)
|
# \h1.0;: set the height-alignment (to the default: 1.0 / align text to be on one baseline)
|
||||||
# \cRRGGBB: set color to this hex value.
|
# \cRRGGBB: set color to this hex value.
|
||||||
# \cFFFFFF: default color (white)
|
# \cFFFFFF: default color (white)
|
||||||
|
# \iCover:<id>;: show cover with this ID (ID >= 0)
|
||||||
|
# \iCover:0;
|
||||||
|
# \iCustomFile:<path>:: show cover stored in custom files at the given path (path terminated by #, is another textcfg)
|
||||||
|
# \iCustomFile:my_image.jpg#
|
||||||
# \<char>: <char> (\\ => \, \# => #, \% => %, ...)
|
# \<char>: <char> (\\ => \, \# => #, \% => %, ...)
|
||||||
# custom properties:
|
# custom properties:
|
||||||
# %<mode><search text>%
|
# %<mode><search text>%
|
||||||
@ -31,8 +35,19 @@ font = ''
|
|||||||
status_bar = '''\t
|
status_bar = '''\t
|
||||||
\s0.5;?\A#\c505050by \c593D6E\A##?\a#?\A# ##\c505050on \c264524\a##\c808080?%>Year=%# (%>Year=%)## | \d'''
|
\s0.5;?\A#\c505050by \c593D6E\A##?\a#?\A# ##\c505050on \c264524\a##\c808080?%>Year=%# (%>Year=%)## | \d'''
|
||||||
|
|
||||||
idle_top = '''\t \s0.5;\c505050(\d?%>Genre=%#, %>Genre=%##)
|
# Two lines
|
||||||
?\A#\c505050by \c593D6E\A##?\a#?\A# ##\c505050on \c264524\a##\c808080?%>Year=%# (%>Year=%)##'''
|
# 1:
|
||||||
|
# - Title (Size 1.0, White)
|
||||||
|
# - Duration (and, if set, Genre) in brackets (Size 0.5, Gray)
|
||||||
|
# - (\h0.5; = set height-align to 0.5, only affects flag image in second line)
|
||||||
|
# 2:
|
||||||
|
# - if there is an Artist:
|
||||||
|
# - "by <Artist>" (purple)
|
||||||
|
# - if there is a "Flag: <name>" tag, show the image saved as "<name>.png"
|
||||||
|
# - "on <Album>", if there is an Album (green)
|
||||||
|
# - "(<year>)", if there is a Year=<year> tag (gray)
|
||||||
|
idle_top = '''\t \s0.5;\c505050(\d?%>Genre=%#, %>Genre=%##)\h0.5;
|
||||||
|
?\A#\c505050by \c593D6E\A?%>Flag: %# \s0.25;\iCustomFile:%>Flag: %.png#\s0.5;####?\a#?\A# ##\c505050on \c264524\a##\c808080?%>Year=%# (%>Year=%)##'''
|
||||||
|
|
||||||
idle_side1 = ''
|
idle_side1 = ''
|
||||||
idle_side2 = ''
|
idle_side2 = ''
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
use std::{
|
use std::time::Instant;
|
||||||
sync::{atomic::AtomicU8, Arc},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use musicdb_lib::data::{song::Song, ArtistId};
|
use musicdb_lib::data::{song::Song, ArtistId};
|
||||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle};
|
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle};
|
||||||
|
@ -1004,12 +1004,18 @@ impl ListAlbum {
|
|||||||
Vec2::new(0.0, 0.5),
|
Vec2::new(0.0, 0.5),
|
||||||
vec![vec![
|
vec![vec![
|
||||||
(
|
(
|
||||||
gui_text::Content::new(name, Color::from_int_rgb(8, 61, 47)),
|
gui_text::AdvancedContent::Text(gui_text::Content::new(
|
||||||
|
name,
|
||||||
|
Color::from_int_rgb(8, 61, 47),
|
||||||
|
)),
|
||||||
1.0,
|
1.0,
|
||||||
1.0,
|
1.0,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
gui_text::Content::new(half_sized_info, Color::GRAY),
|
gui_text::AdvancedContent::Text(gui_text::Content::new(
|
||||||
|
half_sized_info,
|
||||||
|
Color::GRAY,
|
||||||
|
)),
|
||||||
0.5,
|
0.5,
|
||||||
1.0,
|
1.0,
|
||||||
),
|
),
|
||||||
@ -1142,11 +1148,18 @@ impl ListSong {
|
|||||||
Vec2::new(0.0, 0.5),
|
Vec2::new(0.0, 0.5),
|
||||||
vec![vec![
|
vec![vec![
|
||||||
(
|
(
|
||||||
gui_text::Content::new(name, Color::from_int_rgb(175, 175, 175)),
|
gui_text::AdvancedContent::Text(gui_text::Content::new(
|
||||||
|
name,
|
||||||
|
Color::from_int_rgb(175, 175, 175),
|
||||||
|
)),
|
||||||
1.0,
|
1.0,
|
||||||
1.0,
|
1.0,
|
||||||
),
|
),
|
||||||
(gui_text::Content::new(duration, Color::GRAY), 0.6, 1.0),
|
(
|
||||||
|
gui_text::AdvancedContent::Text(gui_text::Content::new(duration, Color::GRAY)),
|
||||||
|
0.6,
|
||||||
|
1.0,
|
||||||
|
),
|
||||||
]],
|
]],
|
||||||
);
|
);
|
||||||
config.redraw = true;
|
config.redraw = true;
|
||||||
|
@ -181,12 +181,18 @@ impl GuiElem for QueueViewer {
|
|||||||
let dr = fmt_dur(info.database.queue.duration_remaining(&info.database));
|
let dr = fmt_dur(info.database.queue.duration_remaining(&info.database));
|
||||||
label.content = vec![
|
label.content = vec![
|
||||||
vec![(
|
vec![(
|
||||||
gui_text::Content::new(format!("Total: {dt}"), Color::GRAY),
|
gui_text::AdvancedContent::Text(gui_text::Content::new(
|
||||||
|
format!("Total: {dt}"),
|
||||||
|
Color::GRAY,
|
||||||
|
)),
|
||||||
1.0,
|
1.0,
|
||||||
1.0,
|
1.0,
|
||||||
)],
|
)],
|
||||||
vec![(
|
vec![(
|
||||||
gui_text::Content::new(format!("Remaining: {dr}"), Color::GRAY),
|
gui_text::AdvancedContent::Text(gui_text::Content::new(
|
||||||
|
format!("Remaining: {dr}"),
|
||||||
|
Color::GRAY,
|
||||||
|
)),
|
||||||
1.0,
|
1.0,
|
||||||
1.0,
|
1.0,
|
||||||
)],
|
)],
|
||||||
@ -454,19 +460,19 @@ impl QueueSong {
|
|||||||
Vec2::new(0.0, 0.5),
|
Vec2::new(0.0, 0.5),
|
||||||
vec![vec![
|
vec![vec![
|
||||||
(
|
(
|
||||||
gui_text::Content::new(
|
gui_text::AdvancedContent::Text(gui_text::Content::new(
|
||||||
song.title.clone(),
|
song.title.clone(),
|
||||||
if current {
|
if current {
|
||||||
Color::from_int_rgb(194, 76, 178)
|
Color::from_int_rgb(194, 76, 178)
|
||||||
} else {
|
} else {
|
||||||
Color::from_int_rgb(120, 76, 194)
|
Color::from_int_rgb(120, 76, 194)
|
||||||
},
|
},
|
||||||
),
|
)),
|
||||||
1.0,
|
1.0,
|
||||||
1.0,
|
1.0,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
gui_text::Content::new(
|
gui_text::AdvancedContent::Text(gui_text::Content::new(
|
||||||
{
|
{
|
||||||
let duration = song.duration_millis / 1000;
|
let duration = song.duration_millis / 1000;
|
||||||
format!(" {}:{:0>2}", duration / 60, duration % 60)
|
format!(" {}:{:0>2}", duration / 60, duration % 60)
|
||||||
@ -476,7 +482,7 @@ impl QueueSong {
|
|||||||
} else {
|
} else {
|
||||||
Color::DARK_GRAY
|
Color::DARK_GRAY
|
||||||
},
|
},
|
||||||
),
|
)),
|
||||||
0.6,
|
0.6,
|
||||||
1.0,
|
1.0,
|
||||||
),
|
),
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
use std::rc::Rc;
|
use std::{fmt::Display, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
|
use musicdb_lib::data::CoverId;
|
||||||
use speedy2d::{
|
use speedy2d::{
|
||||||
color::Color,
|
color::Color,
|
||||||
dimen::Vec2,
|
dimen::Vec2,
|
||||||
font::{FormattedTextBlock, TextLayout, TextOptions},
|
font::{FormattedTextBlock, TextLayout, TextOptions},
|
||||||
|
image::ImageHandle,
|
||||||
shape::Rectangle,
|
shape::Rectangle,
|
||||||
window::{ModifiersState, MouseButton},
|
window::{ModifiersState, MouseButton},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::gui::{GuiAction, GuiElem, GuiElemCfg};
|
use crate::gui::{GuiAction, GuiElem, GuiElemCfg, GuiServerImage};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@ -30,6 +32,7 @@ pub struct Content {
|
|||||||
background: Option<Color>,
|
background: Option<Color>,
|
||||||
formatted: Option<Rc<FormattedTextBlock>>,
|
formatted: Option<Rc<FormattedTextBlock>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
impl Content {
|
impl Content {
|
||||||
pub fn new(text: String, color: Color) -> Self {
|
pub fn new(text: String, color: Color) -> Self {
|
||||||
@ -286,6 +289,36 @@ impl GuiElem for TextField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum AdvancedContent {
|
||||||
|
Text(Content),
|
||||||
|
Image {
|
||||||
|
source: ImageSource,
|
||||||
|
handle: Option<Option<ImageHandle>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ImageSource {
|
||||||
|
Cover(CoverId),
|
||||||
|
CustomFile(String),
|
||||||
|
}
|
||||||
|
impl AdvancedContent {
|
||||||
|
pub fn will_redraw(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Text(c) => c.will_redraw(),
|
||||||
|
Self::Image { source: _, handle } => handle.is_none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for AdvancedContent {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Text(c) => write!(f, "{}", c.text),
|
||||||
|
Self::Image { .. } => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// More advanced version of `Label`.
|
/// More advanced version of `Label`.
|
||||||
/// Allows stringing together multiple `Content`s in one line.
|
/// Allows stringing together multiple `Content`s in one line.
|
||||||
pub struct AdvancedLabel {
|
pub struct AdvancedLabel {
|
||||||
@ -297,13 +330,17 @@ pub struct AdvancedLabel {
|
|||||||
pub align: Vec2,
|
pub align: Vec2,
|
||||||
/// (Content, Size-Scale, Height)
|
/// (Content, Size-Scale, Height)
|
||||||
/// Size-Scale and Height should default to 1.0.
|
/// Size-Scale and Height should default to 1.0.
|
||||||
pub content: Vec<Vec<(Content, f32, f32)>>,
|
pub content: Vec<Vec<(AdvancedContent, f32, f32)>>,
|
||||||
/// the position from where content drawing starts.
|
/// the position from where content drawing starts.
|
||||||
/// recalculated when layouting is performed.
|
/// recalculated when layouting is performed.
|
||||||
content_pos: Vec2,
|
content_pos: Vec2,
|
||||||
}
|
}
|
||||||
impl AdvancedLabel {
|
impl AdvancedLabel {
|
||||||
pub fn new(config: GuiElemCfg, align: Vec2, content: Vec<Vec<(Content, f32, f32)>>) -> Self {
|
pub fn new(
|
||||||
|
config: GuiElemCfg,
|
||||||
|
align: Vec2,
|
||||||
|
content: Vec<Vec<(AdvancedContent, f32, f32)>>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
children: vec![],
|
children: vec![],
|
||||||
@ -350,6 +387,8 @@ impl GuiElem for AdvancedLabel {
|
|||||||
let mut len = 0.0;
|
let mut len = 0.0;
|
||||||
let mut height = 0.0;
|
let mut height = 0.0;
|
||||||
for (c, scale, _) in line {
|
for (c, scale, _) in line {
|
||||||
|
match c {
|
||||||
|
AdvancedContent::Text(c) => {
|
||||||
let size = info
|
let size = info
|
||||||
.font
|
.font
|
||||||
.layout_text(&c.text, 1.0, TextOptions::new())
|
.layout_text(&c.text, 1.0, TextOptions::new())
|
||||||
@ -359,6 +398,20 @@ impl GuiElem for AdvancedLabel {
|
|||||||
height = size.y * scale;
|
height = size.y * scale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AdvancedContent::Image { source, handle } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (c, scale, _) in line {
|
||||||
|
match c {
|
||||||
|
AdvancedContent::Text(_) => {}
|
||||||
|
AdvancedContent::Image { source, handle } => {
|
||||||
|
if let Some(Some(handle)) = handle {
|
||||||
|
let size = handle.size().into_f32();
|
||||||
|
len += height * size.x / size.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if len > max_len {
|
if len > max_len {
|
||||||
max_len = len;
|
max_len = len;
|
||||||
}
|
}
|
||||||
@ -382,12 +435,68 @@ impl GuiElem for AdvancedLabel {
|
|||||||
};
|
};
|
||||||
for line in &mut self.content {
|
for line in &mut self.content {
|
||||||
for (c, s, _) in line {
|
for (c, s, _) in line {
|
||||||
|
match c {
|
||||||
|
AdvancedContent::Text(c) => {
|
||||||
c.formatted = Some(info.font.layout_text(
|
c.formatted = Some(info.font.layout_text(
|
||||||
&c.text,
|
&c.text,
|
||||||
scale * (*s),
|
scale * (*s),
|
||||||
TextOptions::new(),
|
TextOptions::new(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
AdvancedContent::Image { source, handle } => {
|
||||||
|
if handle.is_none() {
|
||||||
|
match source {
|
||||||
|
ImageSource::Cover(id) => {
|
||||||
|
if let Some(img) = info.covers.get_mut(&id) {
|
||||||
|
if let Some(img) = img.get_init(g) {
|
||||||
|
*handle = Some(Some(img));
|
||||||
|
} else {
|
||||||
|
match img {
|
||||||
|
GuiServerImage::Loading(_) => {}
|
||||||
|
GuiServerImage::Loaded(_) => {}
|
||||||
|
GuiServerImage::Error => {
|
||||||
|
*handle = Some(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info.covers.insert(
|
||||||
|
*id,
|
||||||
|
GuiServerImage::new_cover(
|
||||||
|
*id,
|
||||||
|
Arc::clone(&info.get_con),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageSource::CustomFile(path) => {
|
||||||
|
if let Some(img) = info.custom_images.get_mut(path) {
|
||||||
|
if let Some(img) = img.get_init(g) {
|
||||||
|
*handle = Some(Some(img));
|
||||||
|
} else {
|
||||||
|
match img {
|
||||||
|
GuiServerImage::Loading(_) => {}
|
||||||
|
GuiServerImage::Loaded(_) => {}
|
||||||
|
GuiServerImage::Error => {
|
||||||
|
*handle = Some(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info.custom_images.insert(
|
||||||
|
path.clone(),
|
||||||
|
GuiServerImage::new_custom_file(
|
||||||
|
path.clone(),
|
||||||
|
Arc::clone(&info.get_con),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -395,20 +504,51 @@ impl GuiElem for AdvancedLabel {
|
|||||||
let mut pos_y = info.pos.top_left().y + self.content_pos.y;
|
let mut pos_y = info.pos.top_left().y + self.content_pos.y;
|
||||||
for line in &self.content {
|
for line in &self.content {
|
||||||
let mut pos_x = pos_x_start;
|
let mut pos_x = pos_x_start;
|
||||||
let height = line
|
let height_div_by = line
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|v| v.0.formatted.as_ref())
|
.map(|(_, scale, _)| *scale)
|
||||||
|
.reduce(f32::max)
|
||||||
|
.unwrap_or(1.0);
|
||||||
|
let line_height = line
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(v, _, _)| {
|
||||||
|
if let AdvancedContent::Text(c) = v {
|
||||||
|
Some(c)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter_map(|v| v.formatted.as_ref())
|
||||||
.map(|f| f.height())
|
.map(|f| f.height())
|
||||||
.reduce(f32::max)
|
.reduce(f32::max)
|
||||||
.unwrap_or(0.0);
|
.unwrap_or(0.0);
|
||||||
for (c, _, h) in line {
|
for (c, scale, placement_height) in line {
|
||||||
|
// not super accurate, but pretty good
|
||||||
|
let rel_scale = f32::min(1.0, scale / height_div_by);
|
||||||
|
match c {
|
||||||
|
AdvancedContent::Text(c) => {
|
||||||
if let Some(f) = &c.formatted {
|
if let Some(f) = &c.formatted {
|
||||||
let y = pos_y + (height - f.height()) * h;
|
let y = pos_y + (line_height - f.height()) * placement_height;
|
||||||
g.draw_text(Vec2::new(pos_x, y), c.color, f);
|
g.draw_text(Vec2::new(pos_x, y), c.color, f);
|
||||||
pos_x += f.width();
|
pos_x += f.width();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pos_y += height;
|
AdvancedContent::Image { source: _, handle } => {
|
||||||
|
if let Some(Some(handle)) = handle {
|
||||||
|
let size = handle.size().into_f32();
|
||||||
|
let h = line_height * rel_scale;
|
||||||
|
let w = h * size.x / size.y;
|
||||||
|
let y = pos_y + (line_height - h) * placement_height;
|
||||||
|
g.draw_rectangle_image(
|
||||||
|
Rectangle::from_tuples((pos_x, y), (pos_x + w, y + h)),
|
||||||
|
handle,
|
||||||
|
);
|
||||||
|
pos_x += w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos_y += line_height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,10 @@ use std::{
|
|||||||
str::{Chars, FromStr},
|
str::{Chars, FromStr},
|
||||||
};
|
};
|
||||||
|
|
||||||
use musicdb_lib::data::{database::Database, song::Song, GeneralData};
|
use musicdb_lib::data::{database::Database, song::Song, CoverId, GeneralData};
|
||||||
use speedy2d::color::Color;
|
use speedy2d::color::Color;
|
||||||
|
|
||||||
use crate::gui_text::Content;
|
use crate::gui_text::{AdvancedContent, Content, ImageSource};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TextBuilder(pub Vec<TextPart>);
|
pub struct TextBuilder(pub Vec<TextPart>);
|
||||||
@ -34,9 +34,15 @@ pub enum TextPart {
|
|||||||
/// If `1` is something, uses `2`.
|
/// If `1` is something, uses `2`.
|
||||||
/// If `1` is nothing, uses `3`.
|
/// If `1` is nothing, uses `3`.
|
||||||
If(TextBuilder, TextBuilder, TextBuilder),
|
If(TextBuilder, TextBuilder, TextBuilder),
|
||||||
|
ImgCover(CoverId),
|
||||||
|
ImgCustom(TextBuilder),
|
||||||
}
|
}
|
||||||
impl TextBuilder {
|
impl TextBuilder {
|
||||||
pub fn gen(&self, db: &Database, current_song: Option<&Song>) -> Vec<Vec<(Content, f32, f32)>> {
|
pub fn gen(
|
||||||
|
&self,
|
||||||
|
db: &Database,
|
||||||
|
current_song: Option<&Song>,
|
||||||
|
) -> Vec<Vec<(AdvancedContent, f32, f32)>> {
|
||||||
let mut out = vec![];
|
let mut out = vec![];
|
||||||
let mut line = vec![];
|
let mut line = vec![];
|
||||||
let mut c = Color::WHITE;
|
let mut c = Color::WHITE;
|
||||||
@ -58,15 +64,31 @@ impl TextBuilder {
|
|||||||
&self,
|
&self,
|
||||||
db: &Database,
|
db: &Database,
|
||||||
current_song: Option<&Song>,
|
current_song: Option<&Song>,
|
||||||
out: &mut Vec<Vec<(Content, f32, f32)>>,
|
out: &mut Vec<Vec<(AdvancedContent, f32, f32)>>,
|
||||||
line: &mut Vec<(Content, f32, f32)>,
|
line: &mut Vec<(AdvancedContent, f32, f32)>,
|
||||||
scale: &mut f32,
|
scale: &mut f32,
|
||||||
align: &mut f32,
|
align: &mut f32,
|
||||||
color: &mut Color,
|
color: &mut Color,
|
||||||
) {
|
) {
|
||||||
macro_rules! push {
|
macro_rules! push {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
line.push((Content::new($e, *color), *scale, *align))
|
line.push((
|
||||||
|
AdvancedContent::Text(Content::new($e, *color)),
|
||||||
|
*scale,
|
||||||
|
*align,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! push_img {
|
||||||
|
($e:expr) => {
|
||||||
|
line.push((
|
||||||
|
AdvancedContent::Image {
|
||||||
|
source: $e,
|
||||||
|
handle: None,
|
||||||
|
},
|
||||||
|
*scale,
|
||||||
|
*align,
|
||||||
|
))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn all_general<'a>(
|
fn all_general<'a>(
|
||||||
@ -168,6 +190,17 @@ impl TextBuilder {
|
|||||||
no.gen_to(db, current_song, out, line, scale, align, color);
|
no.gen_to(db, current_song, out, line, scale, align, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TextPart::ImgCover(id) => {
|
||||||
|
push_img!(ImageSource::Cover(*id));
|
||||||
|
}
|
||||||
|
TextPart::ImgCustom(path) => {
|
||||||
|
push_img!(ImageSource::CustomFile(
|
||||||
|
path.gen(db, current_song)
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|v| v.into_iter().map(|(v, _, _)| v.to_string()))
|
||||||
|
.collect()
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,6 +318,41 @@ impl TextBuilder {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
Some('i') => {
|
||||||
|
done!();
|
||||||
|
let mut src = String::new();
|
||||||
|
loop {
|
||||||
|
match chars.next() {
|
||||||
|
None => {
|
||||||
|
return Err(TextBuilderParseError::InvalidImageSourceName(
|
||||||
|
src,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(':') => break,
|
||||||
|
Some(c) => src.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vec.push(match src.as_str() {
|
||||||
|
"Cover" => {
|
||||||
|
let mut id = String::new();
|
||||||
|
loop {
|
||||||
|
match chars.next() {
|
||||||
|
None | Some(';') => break,
|
||||||
|
Some(c) => id.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(id) = id.parse() {
|
||||||
|
TextPart::ImgCover(id)
|
||||||
|
} else {
|
||||||
|
return Err(TextBuilderParseError::InvalidImageCoverId(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"CustomFile" => TextPart::ImgCustom(Self::from_chars(chars)?),
|
||||||
|
_ => {
|
||||||
|
return Err(TextBuilderParseError::InvalidImageSourceName(src))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Some(ch) => current.push(ch),
|
Some(ch) => current.push(ch),
|
||||||
},
|
},
|
||||||
'%' => {
|
'%' => {
|
||||||
@ -337,6 +405,8 @@ pub enum TextBuilderParseError {
|
|||||||
TooFewCharsForColor,
|
TooFewCharsForColor,
|
||||||
ColorNotHex,
|
ColorNotHex,
|
||||||
CouldntParse(String, String),
|
CouldntParse(String, String),
|
||||||
|
InvalidImageSourceName(String),
|
||||||
|
InvalidImageCoverId(String),
|
||||||
}
|
}
|
||||||
impl Display for TextBuilderParseError {
|
impl Display for TextBuilderParseError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
@ -351,6 +421,8 @@ impl Display for TextBuilderParseError {
|
|||||||
Self::TooFewCharsForColor => write!(f, "Too few chars for color: Syntax is \\cRRGGBB."),
|
Self::TooFewCharsForColor => write!(f, "Too few chars for color: Syntax is \\cRRGGBB."),
|
||||||
Self::ColorNotHex => write!(f, "Color value wasn't a hex number! Syntax is \\cRRGGBB, where R, G, and B are values from 0-9 and A-F (hex 0-F)."),
|
Self::ColorNotHex => write!(f, "Color value wasn't a hex number! Syntax is \\cRRGGBB, where R, G, and B are values from 0-9 and A-F (hex 0-F)."),
|
||||||
Self::CouldntParse(v, t) => write!(f, "Couldn't parse value '{v}' to type '{t}'."),
|
Self::CouldntParse(v, t) => write!(f, "Couldn't parse value '{v}' to type '{t}'."),
|
||||||
|
Self::InvalidImageSourceName(name) => write!(f, "Invalid image source name: '{name}'."),
|
||||||
|
Self::InvalidImageCoverId(id) => write!(f, "Invalid image cover id: '{id}'."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user