mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-09-13 15:16:14 +02:00
339 lines
13 KiB
Rust
339 lines
13 KiB
Rust
use std::{
|
|
sync::{atomic::AtomicBool, Arc},
|
|
time::Instant,
|
|
};
|
|
|
|
use musicdb_lib::data::ArtistId;
|
|
use speedy2d::{color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle};
|
|
|
|
use crate::{
|
|
gui::{rect_from_rel, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiServerImage},
|
|
gui_anim::AnimationController,
|
|
gui_base::Button,
|
|
gui_playback::{get_right_x, image_display, CurrentInfo},
|
|
gui_playpause::PlayPause,
|
|
gui_text::{AdvancedLabel, Label},
|
|
};
|
|
|
|
pub struct IdleDisplay {
|
|
pub config: GuiElemCfg,
|
|
pub idle_mode: f32,
|
|
pub current_info: CurrentInfo,
|
|
pub current_artist_image: Option<(ArtistId, Option<(String, Option<Option<ImageHandle>>)>)>,
|
|
pub c_idle_exit_hint: Button<[Label; 1]>,
|
|
pub c_top_label: AdvancedLabel,
|
|
pub c_side1_label: AdvancedLabel,
|
|
pub c_side2_label: AdvancedLabel,
|
|
pub c_buttons: PlayPause,
|
|
pub c_buttons_custom_pos: bool,
|
|
|
|
pub cover_aspect_ratio: AnimationController<f32>,
|
|
pub artist_image_aspect_ratio: AnimationController<f32>,
|
|
|
|
pub cover_pos: Option<Rectangle>,
|
|
pub cover_left: f32,
|
|
pub cover_top: f32,
|
|
pub cover_bottom: f32,
|
|
|
|
pub artist_image_pos: Option<Rectangle>,
|
|
/// 0.0 -> same height as cover,
|
|
/// 0.5 -> lower half of cover
|
|
pub artist_image_top: f32,
|
|
pub artist_image_to_cover_margin: f32,
|
|
|
|
pub force_reset_texts: bool,
|
|
|
|
is_fav: (bool, Arc<AtomicBool>),
|
|
}
|
|
|
|
impl IdleDisplay {
|
|
pub fn new(config: GuiElemCfg) -> Self {
|
|
let cover_bottom = 0.79;
|
|
let is_fav = Arc::new(AtomicBool::new(false));
|
|
Self {
|
|
config,
|
|
idle_mode: 0.0,
|
|
current_info: CurrentInfo::new(),
|
|
current_artist_image: None,
|
|
c_idle_exit_hint: Button::new(
|
|
GuiElemCfg::default().disabled(),
|
|
|_| vec![GuiAction::EndIdle(true)],
|
|
[Label::new(
|
|
GuiElemCfg::default(),
|
|
"Back".to_owned(),
|
|
Color::GRAY,
|
|
None,
|
|
Vec2::new(0.5, 0.5),
|
|
)],
|
|
),
|
|
c_top_label: AdvancedLabel::new(
|
|
GuiElemCfg::at(Rectangle::from_tuples((0.05, 0.02), (0.95, 0.18))),
|
|
Vec2::new(0.5, 0.5),
|
|
vec![],
|
|
),
|
|
c_side1_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]),
|
|
c_side2_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]),
|
|
is_fav: (false, Arc::clone(&is_fav)),
|
|
c_buttons: PlayPause::new(GuiElemCfg::default(), is_fav),
|
|
c_buttons_custom_pos: false,
|
|
cover_aspect_ratio: AnimationController::new(
|
|
1.0,
|
|
1.0,
|
|
0.01,
|
|
1.0,
|
|
0.8,
|
|
0.6,
|
|
Instant::now(),
|
|
),
|
|
artist_image_aspect_ratio: AnimationController::new(
|
|
0.0,
|
|
0.0,
|
|
0.01,
|
|
1.0,
|
|
0.8,
|
|
0.6,
|
|
Instant::now(),
|
|
),
|
|
cover_pos: None,
|
|
cover_left: 0.01,
|
|
cover_top: 0.21,
|
|
cover_bottom,
|
|
artist_image_pos: None,
|
|
artist_image_top: 0.5,
|
|
artist_image_to_cover_margin: 0.01,
|
|
force_reset_texts: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl GuiElem for IdleDisplay {
|
|
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
|
|
Box::new(
|
|
[
|
|
self.c_idle_exit_hint.elem_mut(),
|
|
self.c_top_label.elem_mut(),
|
|
self.c_side1_label.elem_mut(),
|
|
self.c_side2_label.elem_mut(),
|
|
self.c_buttons.elem_mut(),
|
|
]
|
|
.into_iter(),
|
|
)
|
|
}
|
|
fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {
|
|
// draw background
|
|
g.draw_rectangle(
|
|
info.pos.clone(),
|
|
Color::from_rgba(0.0, 0.0, 0.0, 0.5 + 0.5 * self.idle_mode),
|
|
);
|
|
// update current_info
|
|
self.current_info.update(info, g);
|
|
if self.current_info.new_song || self.force_reset_texts {
|
|
self.current_info.new_song = false;
|
|
self.force_reset_texts = false;
|
|
let is_fav = self
|
|
.current_info
|
|
.current_song
|
|
.and_then(|id| info.database.get_song(&id))
|
|
.map(|song| song.general.tags.iter().any(|v| v == "Fav"))
|
|
.unwrap_or(false);
|
|
if self.is_fav.0 != is_fav {
|
|
self.is_fav.0 = is_fav;
|
|
self.is_fav
|
|
.1
|
|
.store(is_fav, std::sync::atomic::Ordering::Relaxed);
|
|
}
|
|
self.c_top_label.content = if let Some(song) = self.current_info.current_song {
|
|
info.gui_config
|
|
.idle_top_text
|
|
.gen(&info.database, info.database.get_song(&song))
|
|
} else {
|
|
vec![]
|
|
};
|
|
self.c_top_label.config_mut().redraw = true;
|
|
self.c_side1_label.content = if let Some(song) = self.current_info.current_song {
|
|
info.gui_config
|
|
.idle_side1_text
|
|
.gen(&info.database, info.database.get_song(&song))
|
|
} else {
|
|
vec![]
|
|
};
|
|
self.c_side1_label.config_mut().redraw = true;
|
|
self.c_side2_label.content = if let Some(song) = self.current_info.current_song {
|
|
info.gui_config
|
|
.idle_side2_text
|
|
.gen(&info.database, info.database.get_song(&song))
|
|
} else {
|
|
vec![]
|
|
};
|
|
self.c_side2_label.config_mut().redraw = true;
|
|
// check artist
|
|
if let Some(artist_id) = self
|
|
.current_info
|
|
.current_song
|
|
.as_ref()
|
|
.and_then(|id| info.database.songs().get(id))
|
|
.map(|song| song.artist)
|
|
{
|
|
if self.current_artist_image.is_none()
|
|
|| self
|
|
.current_artist_image
|
|
.as_ref()
|
|
.is_some_and(|(a, _)| *a != artist_id)
|
|
{
|
|
self.current_artist_image = Some((artist_id, None));
|
|
self.artist_image_aspect_ratio.target = 0.0;
|
|
if let Some(artist) = info.database.artists().get(&artist_id) {
|
|
for tag in &artist.general.tags {
|
|
if tag.starts_with("ImageExt=") {
|
|
let filename = format!("{}.{}", artist.name, &tag[9..]);
|
|
self.current_artist_image =
|
|
Some((artist_id, Some((filename.clone(), None))));
|
|
if !info.custom_images.contains_key(&filename) {
|
|
info.custom_images.insert(
|
|
filename.clone(),
|
|
GuiServerImage::new_custom_file(
|
|
filename,
|
|
Arc::clone(&info.get_con),
|
|
),
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if self.current_artist_image.is_some() {
|
|
self.current_artist_image = None;
|
|
self.artist_image_aspect_ratio.target = 0.0;
|
|
}
|
|
}
|
|
}
|
|
if self.current_info.new_cover {
|
|
self.current_info.new_cover = false;
|
|
match self.current_info.current_cover {
|
|
None | Some((_, Some(None))) => {
|
|
self.cover_aspect_ratio.target = 0.0;
|
|
}
|
|
Some((_, None)) | Some((_, Some(Some(_)))) => {}
|
|
}
|
|
}
|
|
if let Some((_, Some((img, h)))) = &mut self.current_artist_image {
|
|
if h.is_none() {
|
|
if let Some(img) = info.custom_images.get_mut(img) {
|
|
if let Some(img) = img.get_init(g) {
|
|
*h = Some(Some(img));
|
|
} else if img.is_err() {
|
|
*h = Some(None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// draw cover
|
|
if let Some(Some(cover)) = self
|
|
.current_info
|
|
.current_cover
|
|
.as_ref()
|
|
.map(|v| v.1.as_ref())
|
|
{
|
|
image_display(
|
|
g,
|
|
cover.as_ref(),
|
|
self.cover_pos.as_ref().map(|v| rect_from_rel(v, &info.pos)),
|
|
info.pos.top_left().x + info.pos.height() * self.cover_left,
|
|
info.pos.top_left().y + info.pos.height() * self.cover_top,
|
|
info.pos.top_left().y + info.pos.height() * self.cover_bottom,
|
|
&mut self.cover_aspect_ratio,
|
|
);
|
|
}
|
|
// draw artist image
|
|
if let Some((_, Some((_, Some(img))))) = &self.current_artist_image {
|
|
let top = info.pos.top_left().y + info.pos.height() * self.cover_top;
|
|
let bottom = info.pos.top_left().y + info.pos.height() * self.cover_bottom;
|
|
image_display(
|
|
g,
|
|
img.as_ref(),
|
|
self.artist_image_pos
|
|
.as_ref()
|
|
.map(|v| rect_from_rel(v, &info.pos)),
|
|
get_right_x(
|
|
info.pos.top_left().x + info.pos.height() * self.cover_left,
|
|
top,
|
|
bottom,
|
|
self.cover_aspect_ratio.value,
|
|
) + info.pos.height() * self.artist_image_to_cover_margin,
|
|
top + (bottom - top) * self.artist_image_top,
|
|
bottom,
|
|
&mut self.artist_image_aspect_ratio,
|
|
);
|
|
}
|
|
// move children to make space for cover
|
|
let ar_updated = self
|
|
.cover_aspect_ratio
|
|
.update(info.time.clone(), info.high_performance)
|
|
| self
|
|
.artist_image_aspect_ratio
|
|
.update(info.time.clone(), info.high_performance);
|
|
if ar_updated || info.pos.size() != self.config.pixel_pos.size() {
|
|
if let Some(h) = &info.helper {
|
|
h.request_redraw();
|
|
}
|
|
// make thing be relative to width instead of to height by multiplying with this
|
|
let top = self.cover_top;
|
|
let bottom = self.cover_bottom;
|
|
let left = (get_right_x(self.cover_left, top, bottom, self.cover_aspect_ratio.value)
|
|
+ self.artist_image_to_cover_margin)
|
|
* info.pos.height()
|
|
/ info.pos.width();
|
|
let ai_top = top + (bottom - top) * self.artist_image_top;
|
|
let max_right = 1.0 - self.cover_left * info.pos.height() / info.pos.width();
|
|
self.c_side1_label.config_mut().pos =
|
|
Rectangle::from_tuples((left, top), (max_right, ai_top));
|
|
let left = get_right_x(
|
|
left,
|
|
ai_top * info.pos.height() / info.pos.width(),
|
|
bottom * info.pos.height() / info.pos.width(),
|
|
self.artist_image_aspect_ratio.value,
|
|
);
|
|
self.c_side2_label.config_mut().pos =
|
|
Rectangle::from_tuples((left, ai_top), (max_right, bottom));
|
|
// limit width of c_buttons
|
|
let buttons_right_pos = 1.0;
|
|
let buttons_width_max = info.pos.height() * 0.08 * 4.0 / info.pos.width();
|
|
// buttons use at most half the width (set to 0.2 later, when screen space is used for other things)
|
|
let buttons_width = buttons_width_max.min(0.5);
|
|
if !self.c_buttons_custom_pos {
|
|
self.c_buttons.config_mut().pos = Rectangle::from_tuples(
|
|
(buttons_right_pos - buttons_width, 0.86),
|
|
(buttons_right_pos, 0.94),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
fn config(&self) -> &GuiElemCfg {
|
|
&self.config
|
|
}
|
|
fn config_mut(&mut self) -> &mut GuiElemCfg {
|
|
&mut self.config
|
|
}
|
|
fn any(&self) -> &dyn std::any::Any {
|
|
self
|
|
}
|
|
fn any_mut(&mut self) -> &mut dyn std::any::Any {
|
|
self
|
|
}
|
|
fn elem(&self) -> &dyn GuiElem {
|
|
self
|
|
}
|
|
fn elem_mut(&mut self) -> &mut dyn GuiElem {
|
|
self
|
|
}
|
|
fn updated_library(&mut self) {
|
|
self.current_info.update = true;
|
|
self.force_reset_texts = true;
|
|
}
|
|
fn updated_queue(&mut self) {
|
|
self.current_info.update = true;
|
|
}
|
|
}
|