mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 14:13:53 +01:00
change idle screen for musicdb-client, library search now defaults to something smarter and more intuitive
This commit is contained in:
parent
f429f17876
commit
30b466b2ff
@ -1142,3 +1142,17 @@ impl GuiCover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn morph_rect(a: &Rectangle, b: &Rectangle, p: f32) -> Rectangle {
|
||||||
|
let q = 1.0 - p;
|
||||||
|
Rectangle::from_tuples(
|
||||||
|
(
|
||||||
|
a.top_left().x * q + b.top_left().x * p,
|
||||||
|
a.top_left().y * q + b.top_left().y * p,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
a.bottom_right().x * q + b.bottom_right().x * p,
|
||||||
|
a.bottom_right().y * q + b.bottom_right().y * p,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
use musicdb_lib::data::{database::Database, AlbumId, ArtistId, SongId};
|
use musicdb_lib::data::{
|
||||||
|
album::Album, artist::Artist, database::Database, song::Song, AlbumId, ArtistId, SongId,
|
||||||
|
};
|
||||||
use regex::{Regex, RegexBuilder};
|
use regex::{Regex, RegexBuilder};
|
||||||
use speedy2d::{
|
use speedy2d::{
|
||||||
color::Color,
|
color::Color,
|
||||||
@ -31,6 +34,15 @@ with Regex search and drag-n-drop.
|
|||||||
pub struct LibraryBrowser {
|
pub struct LibraryBrowser {
|
||||||
config: GuiElemCfg,
|
config: GuiElemCfg,
|
||||||
pub children: Vec<GuiElem>,
|
pub children: Vec<GuiElem>,
|
||||||
|
// - - -
|
||||||
|
library_sorted: Vec<(ArtistId, Vec<SongId>, Vec<(AlbumId, Vec<SongId>)>)>,
|
||||||
|
library_filtered: Vec<(
|
||||||
|
ArtistId,
|
||||||
|
Vec<(SongId, f32)>,
|
||||||
|
Vec<(AlbumId, Vec<(SongId, f32)>, f32)>,
|
||||||
|
f32,
|
||||||
|
)>,
|
||||||
|
// - - -
|
||||||
search_artist: String,
|
search_artist: String,
|
||||||
search_artist_regex: Option<Regex>,
|
search_artist_regex: Option<Regex>,
|
||||||
search_album: String,
|
search_album: String,
|
||||||
@ -39,14 +51,24 @@ pub struct LibraryBrowser {
|
|||||||
search_song_regex: Option<Regex>,
|
search_song_regex: Option<Regex>,
|
||||||
filter_target_state: Rc<AtomicBool>,
|
filter_target_state: Rc<AtomicBool>,
|
||||||
filter_state: f32,
|
filter_state: f32,
|
||||||
|
library_updated: bool,
|
||||||
|
search_settings_changed: Rc<AtomicBool>,
|
||||||
search_is_case_sensitive: Rc<AtomicBool>,
|
search_is_case_sensitive: Rc<AtomicBool>,
|
||||||
search_was_case_sensitive: bool,
|
search_was_case_sensitive: bool,
|
||||||
|
search_prefer_start_matches: Rc<AtomicBool>,
|
||||||
|
search_prefers_start_matches: bool,
|
||||||
}
|
}
|
||||||
fn search_regex_new(pat: &str, case_insensitive: bool) -> Result<Regex, regex::Error> {
|
fn search_regex_new(pat: &str, case_insensitive: bool) -> Result<Option<Regex>, regex::Error> {
|
||||||
RegexBuilder::new(pat)
|
if pat.is_empty() {
|
||||||
.unicode(true)
|
Ok(None)
|
||||||
.case_insensitive(case_insensitive)
|
} else {
|
||||||
.build()
|
Ok(Some(
|
||||||
|
RegexBuilder::new(pat)
|
||||||
|
.unicode(true)
|
||||||
|
.case_insensitive(case_insensitive)
|
||||||
|
.build()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const LP_LIB1: f32 = 0.1;
|
const LP_LIB1: f32 = 0.1;
|
||||||
const LP_LIB2: f32 = 1.0;
|
const LP_LIB2: f32 = 1.0;
|
||||||
@ -79,6 +101,11 @@ impl LibraryBrowser {
|
|||||||
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
|
let search_settings_changed = Rc::new(AtomicBool::new(false));
|
||||||
|
let search_was_case_sensitive = false;
|
||||||
|
let search_is_case_sensitive = Rc::new(AtomicBool::new(search_was_case_sensitive));
|
||||||
|
let search_prefers_start_matches = true;
|
||||||
|
let search_prefer_start_matches = Rc::new(AtomicBool::new(search_prefers_start_matches));
|
||||||
let filter_target_state = Rc::new(AtomicBool::new(false));
|
let filter_target_state = Rc::new(AtomicBool::new(false));
|
||||||
let fts = Rc::clone(&filter_target_state);
|
let fts = Rc::clone(&filter_target_state);
|
||||||
let filter_button = Button::new(
|
let filter_button = Button::new(
|
||||||
@ -98,7 +125,6 @@ impl LibraryBrowser {
|
|||||||
Vec2::new(0.5, 0.5),
|
Vec2::new(0.5, 0.5),
|
||||||
))],
|
))],
|
||||||
);
|
);
|
||||||
let search_is_case_sensitive = Rc::new(AtomicBool::new(false));
|
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
children: vec![
|
children: vec![
|
||||||
@ -107,8 +133,16 @@ impl LibraryBrowser {
|
|||||||
GuiElem::new(search_song),
|
GuiElem::new(search_song),
|
||||||
GuiElem::new(library_scroll_box),
|
GuiElem::new(library_scroll_box),
|
||||||
GuiElem::new(filter_button),
|
GuiElem::new(filter_button),
|
||||||
GuiElem::new(FilterPanel::new(Rc::clone(&search_is_case_sensitive))),
|
GuiElem::new(FilterPanel::new(
|
||||||
|
Rc::clone(&search_settings_changed),
|
||||||
|
Rc::clone(&search_is_case_sensitive),
|
||||||
|
Rc::clone(&search_prefer_start_matches),
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
|
// - - -
|
||||||
|
library_sorted: vec![],
|
||||||
|
library_filtered: vec![],
|
||||||
|
// - - -
|
||||||
search_artist: String::new(),
|
search_artist: String::new(),
|
||||||
search_artist_regex: None,
|
search_artist_regex: None,
|
||||||
search_album: String::new(),
|
search_album: String::new(),
|
||||||
@ -117,8 +151,12 @@ impl LibraryBrowser {
|
|||||||
search_song_regex: None,
|
search_song_regex: None,
|
||||||
filter_target_state,
|
filter_target_state,
|
||||||
filter_state: 0.0,
|
filter_state: 0.0,
|
||||||
|
library_updated: true,
|
||||||
|
search_settings_changed,
|
||||||
search_is_case_sensitive,
|
search_is_case_sensitive,
|
||||||
search_was_case_sensitive: false,
|
search_was_case_sensitive,
|
||||||
|
search_prefer_start_matches,
|
||||||
|
search_prefers_start_matches,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,12 +183,26 @@ impl GuiElemTrait for LibraryBrowser {
|
|||||||
// search
|
// search
|
||||||
let mut search_changed = false;
|
let mut search_changed = false;
|
||||||
let mut rebuild_regex = false;
|
let mut rebuild_regex = false;
|
||||||
let case_sensitive = self
|
if self
|
||||||
.search_is_case_sensitive
|
.search_settings_changed
|
||||||
.load(std::sync::atomic::Ordering::Relaxed);
|
.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
if self.search_was_case_sensitive != case_sensitive {
|
{
|
||||||
self.search_was_case_sensitive = case_sensitive;
|
search_changed = true;
|
||||||
rebuild_regex = true;
|
self.search_settings_changed
|
||||||
|
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let case_sensitive = self
|
||||||
|
.search_is_case_sensitive
|
||||||
|
.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
if self.search_was_case_sensitive != case_sensitive {
|
||||||
|
self.search_was_case_sensitive = case_sensitive;
|
||||||
|
rebuild_regex = true;
|
||||||
|
}
|
||||||
|
let pref_start = self
|
||||||
|
.search_prefer_start_matches
|
||||||
|
.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
if self.search_prefers_start_matches != pref_start {
|
||||||
|
self.search_prefers_start_matches = pref_start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let v = &mut self.children[0].try_as_mut::<TextField>().unwrap().children[0]
|
let v = &mut self.children[0].try_as_mut::<TextField>().unwrap().children[0]
|
||||||
@ -161,7 +213,9 @@ impl GuiElemTrait for LibraryBrowser {
|
|||||||
search_changed = true;
|
search_changed = true;
|
||||||
self.search_artist = v.get_text().clone();
|
self.search_artist = v.get_text().clone();
|
||||||
self.search_artist_regex =
|
self.search_artist_regex =
|
||||||
search_regex_new(&self.search_artist, !case_sensitive).ok();
|
search_regex_new(&self.search_artist, !self.search_was_case_sensitive)
|
||||||
|
.ok()
|
||||||
|
.flatten();
|
||||||
*v.color() = if self.search_artist_regex.is_some() {
|
*v.color() = if self.search_artist_regex.is_some() {
|
||||||
Color::WHITE
|
Color::WHITE
|
||||||
} else {
|
} else {
|
||||||
@ -178,7 +232,9 @@ impl GuiElemTrait for LibraryBrowser {
|
|||||||
search_changed = true;
|
search_changed = true;
|
||||||
self.search_album = v.get_text().clone();
|
self.search_album = v.get_text().clone();
|
||||||
self.search_album_regex =
|
self.search_album_regex =
|
||||||
search_regex_new(&self.search_album, !case_sensitive).ok();
|
search_regex_new(&self.search_album, !self.search_was_case_sensitive)
|
||||||
|
.ok()
|
||||||
|
.flatten();
|
||||||
*v.color() = if self.search_album_regex.is_some() {
|
*v.color() = if self.search_album_regex.is_some() {
|
||||||
Color::WHITE
|
Color::WHITE
|
||||||
} else {
|
} else {
|
||||||
@ -198,7 +254,10 @@ impl GuiElemTrait for LibraryBrowser {
|
|||||||
if rebuild_regex || v.will_redraw() && self.search_song != *v.get_text() {
|
if rebuild_regex || v.will_redraw() && self.search_song != *v.get_text() {
|
||||||
search_changed = true;
|
search_changed = true;
|
||||||
self.search_song = v.get_text().clone();
|
self.search_song = v.get_text().clone();
|
||||||
self.search_song_regex = search_regex_new(&self.search_song, !case_sensitive).ok();
|
self.search_song_regex =
|
||||||
|
search_regex_new(&self.search_song, !self.search_was_case_sensitive)
|
||||||
|
.ok()
|
||||||
|
.flatten();
|
||||||
*v.color() = if self.search_song_regex.is_some() {
|
*v.color() = if self.search_song_regex.is_some() {
|
||||||
Color::WHITE
|
Color::WHITE
|
||||||
} else {
|
} else {
|
||||||
@ -206,7 +265,7 @@ impl GuiElemTrait for LibraryBrowser {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// filter
|
// filter panel
|
||||||
let filter_target_state = self
|
let filter_target_state = self
|
||||||
.filter_target_state
|
.filter_target_state
|
||||||
.load(std::sync::atomic::Ordering::Relaxed);
|
.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
@ -244,112 +303,244 @@ impl GuiElemTrait for LibraryBrowser {
|
|||||||
filter_panel.config.enabled = self.filter_state > 0.0;
|
filter_panel.config.enabled = self.filter_state > 0.0;
|
||||||
}
|
}
|
||||||
// -
|
// -
|
||||||
if self.config.redraw || search_changed || info.pos.size() != self.config.pixel_pos.size() {
|
if self.library_updated {
|
||||||
|
self.library_updated = false;
|
||||||
|
self.update_local_library(&info.database, |(_, a), (_, b)| a.name.cmp(&b.name));
|
||||||
|
search_changed = true;
|
||||||
|
}
|
||||||
|
if search_changed {
|
||||||
|
fn filter(
|
||||||
|
s: &LibraryBrowser,
|
||||||
|
pat: &str,
|
||||||
|
regex: &Option<Regex>,
|
||||||
|
search_text: &String,
|
||||||
|
) -> f32 {
|
||||||
|
if let Some(r) = regex {
|
||||||
|
if s.search_prefers_start_matches {
|
||||||
|
r.find_iter(pat)
|
||||||
|
.map(|m| match pat[0..m.start()].chars().rev().next() {
|
||||||
|
// found at the start of h, reaches to the end (whole pattern is part of the match)
|
||||||
|
None if m.end() == pat.len() => 5.0,
|
||||||
|
// found at start of h
|
||||||
|
None => 4.0,
|
||||||
|
// found after whitespace in h
|
||||||
|
Some(ch) if ch.is_whitespace() => 3.0,
|
||||||
|
// found somewhere else in h
|
||||||
|
_ => 2.0,
|
||||||
|
})
|
||||||
|
.fold(0.0, f32::max)
|
||||||
|
} else {
|
||||||
|
if r.is_match(pat) {
|
||||||
|
2.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if search_text.is_empty() {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.filter_local_library(
|
||||||
|
&info.database,
|
||||||
|
|s, artist| filter(s, &artist.name, &s.search_artist_regex, &s.search_artist),
|
||||||
|
|s, album| filter(s, &album.name, &s.search_album_regex, &s.search_album),
|
||||||
|
|s, song| {
|
||||||
|
if song.album.is_some() || s.search_album.is_empty() {
|
||||||
|
filter(s, &song.title, &s.search_song_regex, &s.search_song)
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.config.redraw = true;
|
||||||
|
}
|
||||||
|
if self.config.redraw || info.pos.size() != self.config.pixel_pos.size() {
|
||||||
self.config.redraw = false;
|
self.config.redraw = false;
|
||||||
self.update_list(&info.database, info.line_height);
|
self.update_ui(&info.database, info.line_height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn updated_library(&mut self) {
|
fn updated_library(&mut self) {
|
||||||
self.config.redraw = true;
|
self.library_updated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl LibraryBrowser {
|
impl LibraryBrowser {
|
||||||
fn update_list(&mut self, db: &Database, line_height: f32) {
|
/// Sets `self.library_sorted` based on the contents of the `Database`.
|
||||||
let song_height = line_height;
|
fn update_local_library(
|
||||||
let artist_height = song_height * 3.0;
|
&mut self,
|
||||||
let album_height = song_height * 2.0;
|
db: &Database,
|
||||||
// sort artists by name
|
sort_artists: impl FnMut(&(&ArtistId, &Artist), &(&ArtistId, &Artist)) -> Ordering,
|
||||||
|
) {
|
||||||
let mut artists = db.artists().iter().collect::<Vec<_>>();
|
let mut artists = db.artists().iter().collect::<Vec<_>>();
|
||||||
artists.sort_by_key(|v| &v.1.name);
|
artists.sort_unstable_by(sort_artists);
|
||||||
let mut gui_elements = vec![];
|
self.library_sorted = artists
|
||||||
for (artist_id, artist) in artists {
|
.into_iter()
|
||||||
if self.search_artist.is_empty()
|
.map(|(ar_id, artist)| {
|
||||||
|| self
|
let singles = artist.singles.iter().map(|id| *id).collect();
|
||||||
.search_artist_regex
|
let albums = artist
|
||||||
.as_ref()
|
.albums
|
||||||
.is_some_and(|regex| regex.is_match(&artist.name))
|
.iter()
|
||||||
{
|
.map(|id| {
|
||||||
let mut artist_gui = Some((
|
let songs = if let Some(album) = db.albums().get(id) {
|
||||||
GuiElem::new(ListArtist::new(
|
album.songs.iter().map(|id| *id).collect()
|
||||||
GuiElemCfg::default(),
|
} else {
|
||||||
*artist_id,
|
eprintln!("[warn] No album with id {id} found in db!");
|
||||||
artist.name.clone(),
|
vec![]
|
||||||
)),
|
};
|
||||||
artist_height,
|
(*id, songs)
|
||||||
));
|
})
|
||||||
if self.search_album.is_empty() {
|
.collect();
|
||||||
for song_id in &artist.singles {
|
(*ar_id, singles, albums)
|
||||||
if let Some(song) = db.songs().get(song_id) {
|
})
|
||||||
if self.search_song.is_empty()
|
.collect();
|
||||||
|| self
|
}
|
||||||
.search_song_regex
|
/// Sets `self.library_filtered` using the value of `self.library_sorted` and filter functions.
|
||||||
.as_ref()
|
/// Return values of the filter functions:
|
||||||
.is_some_and(|regex| regex.is_match(&song.title))
|
/// 0.0 -> don't show
|
||||||
{
|
/// 1.0 -> neutral
|
||||||
if let Some(g) = artist_gui.take() {
|
/// anything else -> priority (determines how things will be sorted)
|
||||||
gui_elements.push(g);
|
/// Album Value = max(Song Values) * AlbumFilterVal
|
||||||
}
|
/// Artist Value = max(Album Values) * ArtistFilterVal
|
||||||
gui_elements.push((
|
fn filter_local_library(
|
||||||
GuiElem::new(ListSong::new(
|
&mut self,
|
||||||
GuiElemCfg::default(),
|
db: &Database,
|
||||||
*song_id,
|
filter_artist: impl Fn(&Self, &Artist) -> f32,
|
||||||
song.title.clone(),
|
filter_album: impl Fn(&Self, &Album) -> f32,
|
||||||
)),
|
filter_song: impl Fn(&Self, &Song) -> f32,
|
||||||
song_height,
|
) {
|
||||||
));
|
let mut a = vec![];
|
||||||
}
|
for (artist_id, singles, albums) in self.library_sorted.iter() {
|
||||||
}
|
if let Some(artist) = db.artists().get(artist_id) {
|
||||||
}
|
let mut filterscore_artist = filter_artist(self, artist);
|
||||||
}
|
if filterscore_artist > 0.0 {
|
||||||
for album_id in &artist.albums {
|
let mut max_score_in_artist = 0.0;
|
||||||
if let Some(album) = db.albums().get(album_id) {
|
if filterscore_artist > 0.0 {
|
||||||
if self.search_album.is_empty()
|
let mut s = singles
|
||||||
|| self
|
.iter()
|
||||||
.search_album_regex
|
.filter_map(|song_id| {
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|regex| regex.is_match(&album.name))
|
|
||||||
{
|
|
||||||
let mut album_gui = Some((
|
|
||||||
GuiElem::new(ListAlbum::new(
|
|
||||||
GuiElemCfg::default(),
|
|
||||||
*album_id,
|
|
||||||
album.name.clone(),
|
|
||||||
)),
|
|
||||||
album_height,
|
|
||||||
));
|
|
||||||
for song_id in &album.songs {
|
|
||||||
if let Some(song) = db.songs().get(song_id) {
|
if let Some(song) = db.songs().get(song_id) {
|
||||||
if self.search_song.is_empty()
|
let filterscore_song = filter_song(self, song);
|
||||||
|| self
|
if filterscore_song > 0.0 {
|
||||||
.search_song_regex
|
if filterscore_song > max_score_in_artist {
|
||||||
.as_ref()
|
max_score_in_artist = filterscore_song;
|
||||||
.is_some_and(|regex| regex.is_match(&song.title))
|
|
||||||
{
|
|
||||||
if let Some(g) = artist_gui.take() {
|
|
||||||
gui_elements.push(g);
|
|
||||||
}
|
}
|
||||||
if let Some(g) = album_gui.take() {
|
return Some((*song_id, filterscore_song));
|
||||||
gui_elements.push(g);
|
|
||||||
}
|
|
||||||
gui_elements.push((
|
|
||||||
GuiElem::new(ListSong::new(
|
|
||||||
GuiElemCfg::default(),
|
|
||||||
*song_id,
|
|
||||||
song.title.clone(),
|
|
||||||
)),
|
|
||||||
song_height,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
None
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
s.sort_by(|(.., a), (.., b)| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||||
|
let mut al = albums
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(album_id, songs)| {
|
||||||
|
if let Some(album) = db.albums().get(album_id) {
|
||||||
|
let mut filterscore_album = filter_album(self, album);
|
||||||
|
if filterscore_album > 0.0 {
|
||||||
|
let mut max_score_in_album = 0.0;
|
||||||
|
let mut s = songs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|song_id| {
|
||||||
|
if let Some(song) = db.songs().get(song_id) {
|
||||||
|
let filterscore_song = filter_song(self, song);
|
||||||
|
if filterscore_song > 0.0 {
|
||||||
|
if filterscore_song > max_score_in_album {
|
||||||
|
max_score_in_album = filterscore_song;
|
||||||
|
}
|
||||||
|
return Some((*song_id, filterscore_song));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
s.sort_by(|(.., a), (.., b)| {
|
||||||
|
b.partial_cmp(a).unwrap_or(Ordering::Equal)
|
||||||
|
});
|
||||||
|
filterscore_album *= max_score_in_album;
|
||||||
|
if filterscore_album > 0.0 {
|
||||||
|
if filterscore_album > max_score_in_artist {
|
||||||
|
max_score_in_artist = filterscore_album;
|
||||||
|
}
|
||||||
|
return Some((*album_id, s, filterscore_album));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
al.sort_by(|(.., a), (.., b)| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||||
|
filterscore_artist *= max_score_in_artist;
|
||||||
|
if filterscore_artist > 0.0 {
|
||||||
|
a.push((*artist_id, s, al, filterscore_artist));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let scroll_box = self.children[3].try_as_mut::<ScrollBox>().unwrap();
|
a.sort_by(|(.., a), (.., b)| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||||
scroll_box.children = gui_elements;
|
self.library_filtered = a;
|
||||||
scroll_box.config_mut().redraw = true;
|
}
|
||||||
|
/// Sets the contents of the `ScrollBox` based on `self.library_filtered`.
|
||||||
|
fn update_ui(&mut self, db: &Database, line_height: f32) {
|
||||||
|
let mut elems = vec![];
|
||||||
|
for (artist_id, singles, albums, _artist_filterscore) in self.library_filtered.iter() {
|
||||||
|
elems.push(self.build_ui_element_artist(*artist_id, db, line_height));
|
||||||
|
for (song_id, _song_filterscore) in singles {
|
||||||
|
elems.push(self.build_ui_element_song(*song_id, db, line_height));
|
||||||
|
}
|
||||||
|
for (album_id, songs, _album_filterscore) in albums {
|
||||||
|
elems.push(self.build_ui_element_album(*album_id, db, line_height));
|
||||||
|
for (song_id, _song_filterscore) in songs {
|
||||||
|
elems.push(self.build_ui_element_song(*song_id, db, line_height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let library_scroll_box = self.children[3].try_as_mut::<ScrollBox>().unwrap();
|
||||||
|
library_scroll_box.children = elems;
|
||||||
|
library_scroll_box.config_mut().redraw = true;
|
||||||
|
}
|
||||||
|
fn build_ui_element_artist(&self, id: ArtistId, db: &Database, h: f32) -> (GuiElem, f32) {
|
||||||
|
(
|
||||||
|
GuiElem::new(ListArtist::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
id,
|
||||||
|
if let Some(v) = db.artists().get(&id) {
|
||||||
|
v.name.to_owned()
|
||||||
|
} else {
|
||||||
|
format!("[ Artist #{id} ]")
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
h * 2.5,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn build_ui_element_album(&self, id: ArtistId, db: &Database, h: f32) -> (GuiElem, f32) {
|
||||||
|
(
|
||||||
|
GuiElem::new(ListAlbum::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
id,
|
||||||
|
if let Some(v) = db.albums().get(&id) {
|
||||||
|
v.name.to_owned()
|
||||||
|
} else {
|
||||||
|
format!("[ Album #{id} ]")
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
h * 1.5,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn build_ui_element_song(&self, id: ArtistId, db: &Database, h: f32) -> (GuiElem, f32) {
|
||||||
|
(
|
||||||
|
GuiElem::new(ListSong::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
id,
|
||||||
|
if let Some(v) = db.songs().get(&id) {
|
||||||
|
v.title.to_owned()
|
||||||
|
} else {
|
||||||
|
format!("[ Song #{id} ]")
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
h,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,8 +598,8 @@ impl GuiElemTrait for ListArtist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.mouse_pos = Vec2::new(
|
self.mouse_pos = Vec2::new(
|
||||||
info.mouse_pos.x - self.config.pixel_pos.top_left().x,
|
info.mouse_pos.x - info.pos.top_left().x,
|
||||||
info.mouse_pos.y - self.config.pixel_pos.top_left().y,
|
info.mouse_pos.y - info.pos.top_left().y,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
||||||
@ -501,8 +692,8 @@ impl GuiElemTrait for ListAlbum {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.mouse_pos = Vec2::new(
|
self.mouse_pos = Vec2::new(
|
||||||
info.mouse_pos.x - self.config.pixel_pos.top_left().x,
|
info.mouse_pos.x - info.pos.top_left().x,
|
||||||
info.mouse_pos.y - self.config.pixel_pos.top_left().y,
|
info.mouse_pos.y - info.pos.top_left().y,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
||||||
@ -595,8 +786,8 @@ impl GuiElemTrait for ListSong {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.mouse_pos = Vec2::new(
|
self.mouse_pos = Vec2::new(
|
||||||
info.mouse_pos.x - self.config.pixel_pos.top_left().x,
|
info.mouse_pos.x - info.pos.top_left().x,
|
||||||
info.mouse_pos.y - self.config.pixel_pos.top_left().y,
|
info.mouse_pos.y - info.pos.top_left().y,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
||||||
@ -641,52 +832,102 @@ struct FilterPanel {
|
|||||||
children: Vec<GuiElem>,
|
children: Vec<GuiElem>,
|
||||||
line_height: f32,
|
line_height: f32,
|
||||||
}
|
}
|
||||||
const FP_CASESENS_N: &'static str = "Switch to case-sensitive search";
|
const FP_CASESENS_N: &'static str = "search is case-insensitive";
|
||||||
const FP_CASESENS_Y: &'static str = "Switch to case-insensitive search";
|
const FP_CASESENS_Y: &'static str = "search is case-sensitive!";
|
||||||
|
const FP_PREFSTART_N: &'static str = "simple search";
|
||||||
|
const FP_PREFSTART_Y: &'static str = "will prefer matches at the start of a word";
|
||||||
impl FilterPanel {
|
impl FilterPanel {
|
||||||
pub fn new(search_is_case_sensitive: Rc<AtomicBool>) -> Self {
|
pub fn new(
|
||||||
|
search_settings_changed: Rc<AtomicBool>,
|
||||||
|
search_is_case_sensitive: Rc<AtomicBool>,
|
||||||
|
search_prefer_start_matches: Rc<AtomicBool>,
|
||||||
|
) -> Self {
|
||||||
let is_case_sensitive = search_is_case_sensitive.load(std::sync::atomic::Ordering::Relaxed);
|
let is_case_sensitive = search_is_case_sensitive.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let prefer_start_matches =
|
||||||
|
search_prefer_start_matches.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let ssc1 = Rc::clone(&search_settings_changed);
|
||||||
|
let ssc2 = search_settings_changed;
|
||||||
Self {
|
Self {
|
||||||
config: GuiElemCfg::default().disabled(),
|
config: GuiElemCfg::default().disabled(),
|
||||||
children: vec![GuiElem::new(ScrollBox::new(
|
children: vec![GuiElem::new(ScrollBox::new(
|
||||||
GuiElemCfg::default(),
|
GuiElemCfg::default(),
|
||||||
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||||
vec![(
|
vec![
|
||||||
GuiElem::new(Button::new(
|
(
|
||||||
GuiElemCfg::default(),
|
GuiElem::new(Button::new(
|
||||||
move |button| {
|
|
||||||
let is_case_sensitive = !search_is_case_sensitive
|
|
||||||
.load(std::sync::atomic::Ordering::Relaxed);
|
|
||||||
search_is_case_sensitive
|
|
||||||
.store(is_case_sensitive, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
*button
|
|
||||||
.children()
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.try_as_mut::<Label>()
|
|
||||||
.unwrap()
|
|
||||||
.content
|
|
||||||
.text() = if is_case_sensitive {
|
|
||||||
FP_CASESENS_Y.to_owned()
|
|
||||||
} else {
|
|
||||||
FP_CASESENS_N.to_owned()
|
|
||||||
};
|
|
||||||
vec![]
|
|
||||||
},
|
|
||||||
vec![GuiElem::new(Label::new(
|
|
||||||
GuiElemCfg::default(),
|
GuiElemCfg::default(),
|
||||||
if is_case_sensitive {
|
move |button| {
|
||||||
FP_CASESENS_Y.to_owned()
|
let v = !search_is_case_sensitive
|
||||||
} else {
|
.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
FP_CASESENS_N.to_owned()
|
search_is_case_sensitive
|
||||||
|
.store(v, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
ssc1.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
*button
|
||||||
|
.children()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.try_as_mut::<Label>()
|
||||||
|
.unwrap()
|
||||||
|
.content
|
||||||
|
.text() = if v {
|
||||||
|
FP_CASESENS_Y.to_owned()
|
||||||
|
} else {
|
||||||
|
FP_CASESENS_N.to_owned()
|
||||||
|
};
|
||||||
|
vec![]
|
||||||
},
|
},
|
||||||
Color::GRAY,
|
vec![GuiElem::new(Label::new(
|
||||||
None,
|
GuiElemCfg::default(),
|
||||||
Vec2::new(0.5, 0.5),
|
if is_case_sensitive {
|
||||||
))],
|
FP_CASESENS_Y.to_owned()
|
||||||
)),
|
} else {
|
||||||
1.0,
|
FP_CASESENS_N.to_owned()
|
||||||
)],
|
},
|
||||||
|
Color::GRAY,
|
||||||
|
None,
|
||||||
|
Vec2::new(0.5, 0.5),
|
||||||
|
))],
|
||||||
|
)),
|
||||||
|
1.0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
GuiElem::new(Button::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
move |button| {
|
||||||
|
let v = !search_prefer_start_matches
|
||||||
|
.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
search_prefer_start_matches
|
||||||
|
.store(v, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
ssc2.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
*button
|
||||||
|
.children()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.try_as_mut::<Label>()
|
||||||
|
.unwrap()
|
||||||
|
.content
|
||||||
|
.text() = if v {
|
||||||
|
FP_PREFSTART_Y.to_owned()
|
||||||
|
} else {
|
||||||
|
FP_PREFSTART_N.to_owned()
|
||||||
|
};
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
|
vec![GuiElem::new(Label::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
if prefer_start_matches {
|
||||||
|
FP_PREFSTART_Y.to_owned()
|
||||||
|
} else {
|
||||||
|
FP_PREFSTART_N.to_owned()
|
||||||
|
},
|
||||||
|
Color::GRAY,
|
||||||
|
None,
|
||||||
|
Vec2::new(0.5, 0.5),
|
||||||
|
))],
|
||||||
|
)),
|
||||||
|
1.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
))],
|
))],
|
||||||
line_height: 0.0,
|
line_height: 0.0,
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,10 @@ use musicdb_lib::{
|
|||||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::MouseButton};
|
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::MouseButton};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gui::{adjust_area, adjust_pos, GuiAction, GuiCover, GuiElem, GuiElemCfg, GuiElemTrait},
|
gui::{
|
||||||
gui_text::{AdvancedLabel, Content, Label},
|
adjust_area, adjust_pos, morph_rect, GuiAction, GuiCover, GuiElem, GuiElemCfg, GuiElemTrait,
|
||||||
|
},
|
||||||
|
gui_text::AdvancedLabel,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -25,25 +27,45 @@ pub struct CurrentSong {
|
|||||||
prev_song: Option<SongId>,
|
prev_song: Option<SongId>,
|
||||||
cover_pos: Rectangle,
|
cover_pos: Rectangle,
|
||||||
covers: VecDeque<(CoverId, Option<(bool, Instant)>)>,
|
covers: VecDeque<(CoverId, Option<(bool, Instant)>)>,
|
||||||
|
idle_changed: bool,
|
||||||
|
idle: f32,
|
||||||
text_updated: Option<Instant>,
|
text_updated: Option<Instant>,
|
||||||
|
text_pos_s: Rectangle,
|
||||||
|
text_pos_l: Rectangle,
|
||||||
|
cover_pos_s: Rectangle,
|
||||||
|
cover_pos_l: Rectangle,
|
||||||
}
|
}
|
||||||
impl CurrentSong {
|
impl CurrentSong {
|
||||||
pub fn new(config: GuiElemCfg) -> Self {
|
pub fn new(config: GuiElemCfg) -> Self {
|
||||||
|
let text_pos_s = Rectangle::from_tuples((0.4, 0.0), (1.0, 1.0));
|
||||||
|
let text_pos_l = Rectangle::from_tuples((0.05, 0.0), (0.95, 0.25));
|
||||||
|
let cover_pos_s = Rectangle::from_tuples((0.0, 0.0), (0.1, 1.0));
|
||||||
|
let cover_pos_l = Rectangle::from_tuples((0.0, 0.26), (0.4, 0.80));
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
children: vec![GuiElem::new(AdvancedLabel::new(
|
children: vec![GuiElem::new(AdvancedLabel::new(
|
||||||
GuiElemCfg::at(Rectangle::from_tuples((0.4, 0.0), (1.0, 1.0))),
|
GuiElemCfg::at(text_pos_s.clone()),
|
||||||
Vec2::new(0.0, 0.5),
|
Vec2::new(0.0, 0.5),
|
||||||
vec![],
|
vec![],
|
||||||
))],
|
))],
|
||||||
cover_pos: Rectangle::new(Vec2::ZERO, Vec2::ZERO),
|
cover_pos: Rectangle::new(Vec2::ZERO, Vec2::ZERO),
|
||||||
covers: VecDeque::new(),
|
covers: VecDeque::new(),
|
||||||
prev_song: None,
|
prev_song: None,
|
||||||
|
idle_changed: false,
|
||||||
|
idle: 0.0,
|
||||||
text_updated: None,
|
text_updated: None,
|
||||||
|
text_pos_s,
|
||||||
|
text_pos_l,
|
||||||
|
cover_pos_s,
|
||||||
|
cover_pos_l,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_idle_mode(&mut self, idle_mode: f32) {
|
pub fn set_idle_mode(&mut self, idle_mode: f32) {
|
||||||
// ...
|
self.idle = idle_mode;
|
||||||
|
self.idle_changed = true;
|
||||||
|
let label = self.children[0].try_as_mut::<AdvancedLabel>().unwrap();
|
||||||
|
label.config_mut().pos = morph_rect(&self.text_pos_s, &self.text_pos_l, idle_mode);
|
||||||
|
label.align = Vec2::new(0.5 * idle_mode, 0.5);
|
||||||
}
|
}
|
||||||
fn color_title(a: f32) -> Color {
|
fn color_title(a: f32) -> Color {
|
||||||
Self::color_with_alpha(&Color::WHITE, a)
|
Self::color_with_alpha(&Color::WHITE, a)
|
||||||
@ -153,33 +175,58 @@ impl GuiElemTrait for CurrentSong {
|
|||||||
prog = 1.0;
|
prog = 1.0;
|
||||||
self.text_updated = None;
|
self.text_updated = None;
|
||||||
}
|
}
|
||||||
self.children[0]
|
for c in self.children[0]
|
||||||
.try_as_mut::<AdvancedLabel>()
|
.try_as_mut::<AdvancedLabel>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.content
|
.content
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.count();
|
{
|
||||||
|
for c in c {
|
||||||
|
*c.0.color() = Self::color_with_alpha(c.0.color(), prog);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// drawing stuff
|
// calculate cover pos
|
||||||
if self.config.pixel_pos.size() != info.pos.size() {
|
if self.idle_changed || self.config.pixel_pos.size() != info.pos.size() {
|
||||||
let leftright = 0.05;
|
let cw = (info.pos.height() / info.pos.width()).min(0.5);
|
||||||
let topbottom = 0.05;
|
let padl = 0.1;
|
||||||
let mut width = 0.3;
|
let padr = 1.0 - padl;
|
||||||
let mut height = 1.0 - topbottom * 2.0;
|
let padp = (self.idle * 1.5).min(1.0);
|
||||||
if width * info.pos.width() < height * info.pos.height() {
|
self.cover_pos_s =
|
||||||
height = width * info.pos.width() / info.pos.height();
|
Rectangle::from_tuples((cw * padl, padl + 0.7 * padp), (cw * padr, padr));
|
||||||
} else {
|
self.text_pos_s = Rectangle::from_tuples((cw, 0.0), (1.0, 1.0));
|
||||||
width = height * info.pos.height() / info.pos.width();
|
// to resize the text
|
||||||
}
|
self.set_idle_mode(self.idle);
|
||||||
let right = leftright + width + leftright;
|
self.idle_changed = false;
|
||||||
self.cover_pos = Rectangle::from_tuples(
|
// cover pos
|
||||||
(leftright, 0.5 - 0.5 * height),
|
let pixel_pos = adjust_area(
|
||||||
(leftright + width, 0.5 + 0.5 * height),
|
&info.pos,
|
||||||
|
&morph_rect(&self.cover_pos_s, &self.cover_pos_l, self.idle),
|
||||||
);
|
);
|
||||||
for el in self.children.iter_mut().take(2) {
|
let pad = 0.5 * (pixel_pos.width() - pixel_pos.height());
|
||||||
let pos = &mut el.inner.config_mut().pos;
|
self.cover_pos = if pad >= 0.0 {
|
||||||
*pos = Rectangle::new(Vec2::new(right, pos.top_left().y), *pos.bottom_right());
|
Rectangle::from_tuples(
|
||||||
}
|
(
|
||||||
|
pixel_pos.top_left().x + pad - info.pos.top_left().x,
|
||||||
|
pixel_pos.top_left().y - info.pos.top_left().y,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
pixel_pos.bottom_right().x - pad - info.pos.top_left().x,
|
||||||
|
pixel_pos.bottom_right().y - info.pos.top_left().y,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Rectangle::from_tuples(
|
||||||
|
(
|
||||||
|
pixel_pos.top_left().x - info.pos.top_left().x,
|
||||||
|
pixel_pos.top_left().y - pad - info.pos.top_left().y,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
pixel_pos.bottom_right().x - info.pos.top_left().x,
|
||||||
|
pixel_pos.bottom_right().y + pad - info.pos.top_left().y,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
let mut cover_to_remove = None;
|
let mut cover_to_remove = None;
|
||||||
for (cover_index, (cover_id, time)) in self.covers.iter_mut().enumerate() {
|
for (cover_index, (cover_id, time)) in self.covers.iter_mut().enumerate() {
|
||||||
@ -214,14 +261,12 @@ impl GuiElemTrait for CurrentSong {
|
|||||||
if let Some(cover) = cover.get_init(g) {
|
if let Some(cover) = cover.get_init(g) {
|
||||||
let rect = Rectangle::new(
|
let rect = Rectangle::new(
|
||||||
Vec2::new(
|
Vec2::new(
|
||||||
info.pos.top_left().x + info.pos.width() * self.cover_pos.top_left().x,
|
info.pos.top_left().x + self.cover_pos.top_left().x,
|
||||||
info.pos.top_left().y + info.pos.height() * self.cover_pos.top_left().y,
|
info.pos.top_left().y + self.cover_pos.top_left().y,
|
||||||
),
|
),
|
||||||
Vec2::new(
|
Vec2::new(
|
||||||
info.pos.top_left().x
|
info.pos.top_left().x + self.cover_pos.bottom_right().x,
|
||||||
+ info.pos.width() * self.cover_pos.bottom_right().x,
|
info.pos.top_left().y + self.cover_pos.bottom_right().y,
|
||||||
info.pos.top_left().y
|
|
||||||
+ info.pos.height() * self.cover_pos.bottom_right().y,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if pos == 1.0 {
|
if pos == 1.0 {
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
use std::{
|
use std::time::Instant;
|
||||||
io::{Read, Write},
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use musicdb_lib::server::get;
|
use musicdb_lib::data::queue::QueueContent;
|
||||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D};
|
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
|
gui::{morph_rect, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
|
||||||
gui_base::{Button, Panel},
|
gui_base::{Button, Panel},
|
||||||
gui_library::LibraryBrowser,
|
gui_library::LibraryBrowser,
|
||||||
gui_playback::{CurrentSong, PlayPauseToggle},
|
gui_playback::{CurrentSong, PlayPauseToggle},
|
||||||
@ -239,7 +236,12 @@ impl GuiElemTrait for GuiScreen {
|
|||||||
// resizing prevents idle, but doesn't un-idle
|
// resizing prevents idle, but doesn't un-idle
|
||||||
self.not_idle();
|
self.not_idle();
|
||||||
}
|
}
|
||||||
self.idle_check();
|
if !(!info.database.playing
|
||||||
|
|| matches!(info.database.queue.content(), QueueContent::Folder(_, v, _) if v.is_empty()))
|
||||||
|
{
|
||||||
|
// skip idle_check if paused or queue is empty
|
||||||
|
self.idle_check();
|
||||||
|
}
|
||||||
// request_redraw for animations
|
// request_redraw for animations
|
||||||
if self.idle.1.is_some() || self.settings.1.is_some() || self.edit_panel.1.is_some() {
|
if self.idle.1.is_some() || self.settings.1.is_some() || self.edit_panel.1.is_some() {
|
||||||
if let Some(h) = &info.helper {
|
if let Some(h) = &info.helper {
|
||||||
@ -272,7 +274,7 @@ impl GuiElemTrait for GuiScreen {
|
|||||||
.any_mut()
|
.any_mut()
|
||||||
.downcast_mut::<StatusBar>()
|
.downcast_mut::<StatusBar>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.idle_mode = p1;
|
.idle_mode = p;
|
||||||
}
|
}
|
||||||
// animations: settings
|
// animations: settings
|
||||||
if self.settings.1.is_some() {
|
if self.settings.1.is_some() {
|
||||||
@ -317,24 +319,33 @@ pub struct StatusBar {
|
|||||||
children: Vec<GuiElem>,
|
children: Vec<GuiElem>,
|
||||||
idle_mode: f32,
|
idle_mode: f32,
|
||||||
idle_prev: f32,
|
idle_prev: f32,
|
||||||
|
pos_current_song_s: Rectangle,
|
||||||
|
pos_current_song_l: Rectangle,
|
||||||
|
pos_play_pause_s: Rectangle,
|
||||||
|
pos_play_pause_l: Rectangle,
|
||||||
}
|
}
|
||||||
impl StatusBar {
|
impl StatusBar {
|
||||||
pub fn new(config: GuiElemCfg, playing: bool) -> Self {
|
pub fn new(config: GuiElemCfg, playing: bool) -> Self {
|
||||||
|
let pos_current_song_s = Rectangle::new(Vec2::ZERO, Vec2::new(0.8, 1.0));
|
||||||
|
let pos_current_song_l = Rectangle::new(Vec2::ZERO, Vec2::new(1.0, 1.0));
|
||||||
|
let pos_play_pause_s = Rectangle::from_tuples((0.85, 0.0), (0.95, 1.0));
|
||||||
|
let pos_play_pause_l = Rectangle::from_tuples((0.85, 0.8), (0.95, 1.0));
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
children: vec![
|
children: vec![
|
||||||
GuiElem::new(CurrentSong::new(GuiElemCfg::at(Rectangle::new(
|
GuiElem::new(CurrentSong::new(GuiElemCfg::at(pos_current_song_s.clone()))),
|
||||||
Vec2::ZERO,
|
|
||||||
Vec2::new(0.8, 1.0),
|
|
||||||
)))),
|
|
||||||
GuiElem::new(PlayPauseToggle::new(
|
GuiElem::new(PlayPauseToggle::new(
|
||||||
GuiElemCfg::at(Rectangle::from_tuples((0.85, 0.0), (0.95, 1.0))),
|
GuiElemCfg::at(pos_play_pause_s.clone()),
|
||||||
playing,
|
playing,
|
||||||
)),
|
)),
|
||||||
GuiElem::new(Panel::new(GuiElemCfg::default(), vec![])),
|
GuiElem::new(Panel::new(GuiElemCfg::default(), vec![])),
|
||||||
],
|
],
|
||||||
idle_mode: 0.0,
|
idle_mode: 0.0,
|
||||||
idle_prev: 0.0,
|
idle_prev: 0.0,
|
||||||
|
pos_current_song_s,
|
||||||
|
pos_current_song_l,
|
||||||
|
pos_play_pause_s,
|
||||||
|
pos_play_pause_l,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const fn index_current_song() -> usize {
|
const fn index_current_song() -> usize {
|
||||||
@ -394,17 +405,22 @@ impl GuiElemTrait for StatusBar {
|
|||||||
self.set_background(Some(Color::BLACK));
|
self.set_background(Some(Color::BLACK));
|
||||||
}
|
}
|
||||||
// position the text
|
// position the text
|
||||||
|
let l = self.idle_mode;
|
||||||
let current_song = self.children[Self::index_current_song()]
|
let current_song = self.children[Self::index_current_song()]
|
||||||
.inner
|
.inner
|
||||||
.any_mut()
|
.any_mut()
|
||||||
.downcast_mut::<CurrentSong>()
|
.downcast_mut::<CurrentSong>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
current_song.set_idle_mode(self.idle_mode);
|
current_song.set_idle_mode(self.idle_mode);
|
||||||
|
current_song.config_mut().pos =
|
||||||
|
morph_rect(&self.pos_current_song_s, &self.pos_current_song_l, l);
|
||||||
let play_pause = self.children[Self::index_play_pause_toggle()]
|
let play_pause = self.children[Self::index_play_pause_toggle()]
|
||||||
.inner
|
.inner
|
||||||
.any_mut()
|
.any_mut()
|
||||||
.downcast_mut::<PlayPauseToggle>()
|
.downcast_mut::<PlayPauseToggle>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
play_pause.config_mut().pos =
|
||||||
|
morph_rect(&self.pos_play_pause_s, &self.pos_play_pause_l, l);
|
||||||
// - - - - -
|
// - - - - -
|
||||||
self.idle_prev = self.idle_mode;
|
self.idle_prev = self.idle_mode;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,15 @@ 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<(Content, f32, f32)>> {
|
||||||
let mut out = vec![];
|
let mut out = vec![];
|
||||||
let mut line = vec![];
|
let mut line = vec![];
|
||||||
self.gen_to(db, current_song, &mut out, &mut line, &mut 1.0, &mut 1.0);
|
self.gen_to(
|
||||||
|
db,
|
||||||
|
current_song,
|
||||||
|
&mut out,
|
||||||
|
&mut line,
|
||||||
|
&mut 1.0,
|
||||||
|
&mut 1.0,
|
||||||
|
&mut Color::WHITE,
|
||||||
|
);
|
||||||
if !line.is_empty() {
|
if !line.is_empty() {
|
||||||
out.push(line)
|
out.push(line)
|
||||||
}
|
}
|
||||||
@ -53,11 +61,11 @@ impl TextBuilder {
|
|||||||
line: &mut Vec<(Content, f32, f32)>,
|
line: &mut Vec<(Content, f32, f32)>,
|
||||||
scale: &mut f32,
|
scale: &mut f32,
|
||||||
align: &mut f32,
|
align: &mut f32,
|
||||||
|
color: &mut Color,
|
||||||
) {
|
) {
|
||||||
let mut color = Color::WHITE;
|
|
||||||
macro_rules! push {
|
macro_rules! push {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
line.push((Content::new($e, color), *scale, *align))
|
line.push((Content::new($e, *color), *scale, *align))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn all_general<'a>(
|
fn all_general<'a>(
|
||||||
@ -83,7 +91,7 @@ impl TextBuilder {
|
|||||||
for part in &self.0 {
|
for part in &self.0 {
|
||||||
match part {
|
match part {
|
||||||
TextPart::LineBreak => out.push(std::mem::replace(line, vec![])),
|
TextPart::LineBreak => out.push(std::mem::replace(line, vec![])),
|
||||||
TextPart::SetColor(c) => color = *c,
|
TextPart::SetColor(c) => *color = *c,
|
||||||
TextPart::SetScale(v) => *scale = *v,
|
TextPart::SetScale(v) => *scale = *v,
|
||||||
TextPart::SetHeightAlign(v) => *align = *v,
|
TextPart::SetHeightAlign(v) => *align = *v,
|
||||||
TextPart::Literal(s) => push!(s.to_owned()),
|
TextPart::Literal(s) => push!(s.to_owned()),
|
||||||
@ -141,9 +149,9 @@ impl TextBuilder {
|
|||||||
}
|
}
|
||||||
TextPart::If(condition, yes, no) => {
|
TextPart::If(condition, yes, no) => {
|
||||||
if !condition.gen(db, current_song).is_empty() {
|
if !condition.gen(db, current_song).is_empty() {
|
||||||
yes.gen_to(db, current_song, out, line, scale, align);
|
yes.gen_to(db, current_song, out, line, scale, align, color);
|
||||||
} else {
|
} else {
|
||||||
no.gen_to(db, current_song, out, line, scale, align);
|
no.gen_to(db, current_song, out, line, scale, align, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user