Impl info text
Doesn't yet handle flags to disable some info text.
This commit is contained in:
parent
432a487d32
commit
1dd1f49bec
2 changed files with 297 additions and 29 deletions
303
src/display.rs
303
src/display.rs
|
@ -3,19 +3,50 @@ use crate::mpd_handler::{InfoFromShared, MPDHandler};
|
||||||
use crate::Opt;
|
use crate::Opt;
|
||||||
use ggez::event::{self, EventHandler};
|
use ggez::event::{self, EventHandler};
|
||||||
use ggez::graphics::{
|
use ggez::graphics::{
|
||||||
self, Color, DrawParam, Drawable, Image, Rect, Text, TextFragment, Transform,
|
self, Color, DrawMode, DrawParam, Drawable, Font, Image, Mesh, MeshBuilder, PxScale, Rect,
|
||||||
|
Text, TextFragment, Transform,
|
||||||
};
|
};
|
||||||
|
use ggez::timer;
|
||||||
use ggez::Context;
|
use ggez::Context;
|
||||||
use ggez::GameError;
|
use ggez::GameError;
|
||||||
use image::io::Reader as ImageReader;
|
use image::io::Reader as ImageReader;
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{atomic::Ordering, Arc, RwLock};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
const POLL_TIME: Duration = Duration::from_millis(333);
|
const POLL_TIME: Duration = Duration::from_millis(333);
|
||||||
|
const INIT_FONT_SIZE_X: f32 = 24.0;
|
||||||
|
const INIT_FONT_SIZE_Y: f32 = 34.0;
|
||||||
|
const TEXT_X_OFFSET: f32 = 0.3;
|
||||||
|
const TEXT_OFFSET_Y_SPACING: f32 = 0.4;
|
||||||
|
const TEXT_HEIGHT_LIMIT: f32 = 55.0;
|
||||||
|
const ARTIST_HEIGHT_LIMIT: f32 = 40.0;
|
||||||
|
|
||||||
|
fn seconds_to_time(seconds: f64) -> String {
|
||||||
|
let seconds_int: u64 = seconds.floor() as u64;
|
||||||
|
let minutes = seconds_int / 60;
|
||||||
|
let new_seconds: f64 = seconds - (minutes * 60) as f64;
|
||||||
|
let mut result: String;
|
||||||
|
if minutes > 0 {
|
||||||
|
result = minutes.to_string();
|
||||||
|
result.push(':');
|
||||||
|
if new_seconds < 10.0 {
|
||||||
|
result.push('0');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = String::new();
|
||||||
|
}
|
||||||
|
result.push_str(&new_seconds.to_string());
|
||||||
|
let idx_result = result.find('.');
|
||||||
|
if let Some(idx) = idx_result {
|
||||||
|
result.truncate(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MPDDisplay {
|
pub struct MPDDisplay {
|
||||||
opts: Opt,
|
opts: Opt,
|
||||||
|
@ -30,8 +61,19 @@ pub struct MPDDisplay {
|
||||||
album_art: Option<Image>,
|
album_art: Option<Image>,
|
||||||
album_art_draw_transform: Option<Transform>,
|
album_art_draw_transform: Option<Transform>,
|
||||||
filename_text: Text,
|
filename_text: Text,
|
||||||
|
filename_transform: Transform,
|
||||||
|
filename_y: f32,
|
||||||
artist_text: Text,
|
artist_text: Text,
|
||||||
|
artist_transform: Transform,
|
||||||
|
artist_y: f32,
|
||||||
title_text: Text,
|
title_text: Text,
|
||||||
|
title_transform: Transform,
|
||||||
|
title_y: f32,
|
||||||
|
timer_text: Text,
|
||||||
|
timer_transform: Transform,
|
||||||
|
timer_y: f32,
|
||||||
|
timer: f64,
|
||||||
|
length: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MPDDisplay {
|
impl MPDDisplay {
|
||||||
|
@ -49,8 +91,19 @@ impl MPDDisplay {
|
||||||
album_art: None,
|
album_art: None,
|
||||||
album_art_draw_transform: None,
|
album_art_draw_transform: None,
|
||||||
filename_text: Text::new(""),
|
filename_text: Text::new(""),
|
||||||
|
filename_transform: Transform::default(),
|
||||||
|
filename_y: 1.0,
|
||||||
artist_text: Text::new(""),
|
artist_text: Text::new(""),
|
||||||
|
artist_transform: Transform::default(),
|
||||||
|
artist_y: 1.0,
|
||||||
title_text: Text::new(""),
|
title_text: Text::new(""),
|
||||||
|
title_transform: Transform::default(),
|
||||||
|
title_y: 1.0,
|
||||||
|
timer_text: Text::new("0"),
|
||||||
|
timer_transform: Transform::default(),
|
||||||
|
timer_y: 1.0,
|
||||||
|
timer: 0.0,
|
||||||
|
length: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +182,122 @@ impl MPDDisplay {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn refresh_text_transforms(&mut self, ctx: &mut Context) {
|
||||||
|
let screen_coords: Rect = graphics::screen_coordinates(ctx);
|
||||||
|
|
||||||
|
let mut offset_y: f32 = screen_coords.h;
|
||||||
|
|
||||||
|
let set_transform = |text: &mut Text,
|
||||||
|
transform: &mut Transform,
|
||||||
|
offset_y: &mut f32,
|
||||||
|
y: &mut f32,
|
||||||
|
is_string: bool,
|
||||||
|
is_artist: bool| {
|
||||||
|
let mut current_x = INIT_FONT_SIZE_X;
|
||||||
|
let mut current_y = INIT_FONT_SIZE_Y;
|
||||||
|
let mut width: f32;
|
||||||
|
let mut height: f32 = 0.0;
|
||||||
|
let mut iteration_count: u8 = 0;
|
||||||
|
loop {
|
||||||
|
iteration_count += 1;
|
||||||
|
if iteration_count > 8 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
text.set_font(
|
||||||
|
Font::default(),
|
||||||
|
PxScale {
|
||||||
|
x: current_x,
|
||||||
|
y: current_y,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
width = text.width(ctx);
|
||||||
|
height = text.height(ctx);
|
||||||
|
|
||||||
|
if is_string {
|
||||||
|
if screen_coords.w < width
|
||||||
|
|| height
|
||||||
|
>= (if is_artist {
|
||||||
|
ARTIST_HEIGHT_LIMIT
|
||||||
|
} else {
|
||||||
|
TEXT_HEIGHT_LIMIT
|
||||||
|
})
|
||||||
|
{
|
||||||
|
current_x = current_x * 4.0f32 / 5.0f32;
|
||||||
|
current_y = current_y * 4.0f32 / 5.0f32;
|
||||||
|
continue;
|
||||||
|
} else if screen_coords.w * 2.0 / 3.0 > width {
|
||||||
|
current_x = current_x * 5.0f32 / 4.0f32;
|
||||||
|
current_y = current_y * 5.0f32 / 4.0f32;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*y = *offset_y - height;
|
||||||
|
*transform = Transform::Values {
|
||||||
|
dest: [TEXT_X_OFFSET, *offset_y - height].into(),
|
||||||
|
rotation: 0.0,
|
||||||
|
scale: [1.0, 1.0].into(),
|
||||||
|
offset: [0.0, 0.0].into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
*offset_y -= height + TEXT_OFFSET_Y_SPACING;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.filename_text.contents().is_empty() {
|
||||||
|
set_transform(
|
||||||
|
&mut self.filename_text,
|
||||||
|
&mut self.filename_transform,
|
||||||
|
&mut offset_y,
|
||||||
|
&mut self.filename_y,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log("filename text is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.artist_text.contents().is_empty() {
|
||||||
|
set_transform(
|
||||||
|
&mut self.artist_text,
|
||||||
|
&mut self.artist_transform,
|
||||||
|
&mut offset_y,
|
||||||
|
&mut self.artist_y,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log("artist text is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.title_text.contents().is_empty() {
|
||||||
|
set_transform(
|
||||||
|
&mut self.title_text,
|
||||||
|
&mut self.title_transform,
|
||||||
|
&mut offset_y,
|
||||||
|
&mut self.title_y,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log("title text is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
set_transform(
|
||||||
|
&mut self.timer_text,
|
||||||
|
&mut self.timer_transform,
|
||||||
|
&mut offset_y,
|
||||||
|
&mut self.timer_y,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler for MPDDisplay {
|
impl EventHandler for MPDDisplay {
|
||||||
|
@ -158,17 +327,44 @@ impl EventHandler for MPDDisplay {
|
||||||
.dirty_flag
|
.dirty_flag
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.swap(false, std::sync::atomic::Ordering::Relaxed)
|
.swap(false, Ordering::Relaxed)
|
||||||
{
|
{
|
||||||
|
log("dirty_flag cleared, acquiring shared data...");
|
||||||
self.shared = MPDHandler::get_current_song_info(self.mpd_handler.clone().unwrap())
|
self.shared = MPDHandler::get_current_song_info(self.mpd_handler.clone().unwrap())
|
||||||
.map_or(None, |f| Some(f));
|
.map_or(None, |f| Some(f));
|
||||||
if let Some(shared) = &self.shared {
|
if let Some(shared) = &self.shared {
|
||||||
if self.notice_text.contents() != shared.error_text {
|
if self.notice_text.contents() != shared.error_text {
|
||||||
self.notice_text = Text::new(TextFragment::new(shared.error_text.clone()));
|
self.notice_text = Text::new(TextFragment::new(shared.error_text.clone()));
|
||||||
self.title_text = Text::new(TextFragment::new(shared.title.clone()));
|
|
||||||
self.artist_text = Text::new(TextFragment::new(shared.artist.clone()));
|
|
||||||
self.filename_text = Text::new(TextFragment::new(shared.filename.clone()));
|
|
||||||
}
|
}
|
||||||
|
if !shared.title.is_empty() {
|
||||||
|
self.title_text = Text::new(shared.title.clone());
|
||||||
|
} else {
|
||||||
|
self.dirty_flag
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
if !shared.artist.is_empty() {
|
||||||
|
self.artist_text = Text::new(shared.artist.clone());
|
||||||
|
} else {
|
||||||
|
self.dirty_flag
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
if !shared.filename.is_empty() {
|
||||||
|
self.filename_text = Text::new(shared.filename.clone());
|
||||||
|
} else {
|
||||||
|
self.dirty_flag
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
self.timer = shared.pos;
|
||||||
|
self.length = shared.length;
|
||||||
|
self.refresh_text_transforms(ctx);
|
||||||
|
} else {
|
||||||
|
log("Failed to acquire read lock for getting shared data");
|
||||||
}
|
}
|
||||||
let album_art_data_result =
|
let album_art_data_result =
|
||||||
MPDHandler::get_art_data(self.mpd_handler.clone().unwrap());
|
MPDHandler::get_art_data(self.mpd_handler.clone().unwrap());
|
||||||
|
@ -188,6 +384,18 @@ impl EventHandler for MPDDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let delta = timer::delta(ctx);
|
||||||
|
self.timer += delta.as_secs_f64();
|
||||||
|
let timer_diff = seconds_to_time(self.length - self.timer);
|
||||||
|
self.timer_text = Text::new(timer_diff);
|
||||||
|
self.timer_text.set_font(
|
||||||
|
Font::default(),
|
||||||
|
PxScale {
|
||||||
|
x: INIT_FONT_SIZE_X,
|
||||||
|
y: INIT_FONT_SIZE_Y,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,6 +415,88 @@ impl EventHandler for MPDDisplay {
|
||||||
|
|
||||||
self.notice_text.draw(ctx, DrawParam::default())?;
|
self.notice_text.draw(ctx, DrawParam::default())?;
|
||||||
|
|
||||||
|
if self.is_valid && self.is_initialized {
|
||||||
|
let filename_dimensions = self.filename_text.dimensions(ctx);
|
||||||
|
let artist_dimensions = self.artist_text.dimensions(ctx);
|
||||||
|
let title_dimensions = self.title_text.dimensions(ctx);
|
||||||
|
let timer_dimensions = self.timer_text.dimensions(ctx);
|
||||||
|
let mesh: Mesh = MeshBuilder::new()
|
||||||
|
.rectangle(
|
||||||
|
DrawMode::fill(),
|
||||||
|
Rect {
|
||||||
|
x: TEXT_X_OFFSET,
|
||||||
|
y: self.filename_y,
|
||||||
|
w: filename_dimensions.w,
|
||||||
|
h: filename_dimensions.h,
|
||||||
|
},
|
||||||
|
Color::from_rgba(0, 0, 0, 160),
|
||||||
|
)?
|
||||||
|
.rectangle(
|
||||||
|
DrawMode::fill(),
|
||||||
|
Rect {
|
||||||
|
x: TEXT_X_OFFSET,
|
||||||
|
y: self.artist_y,
|
||||||
|
w: artist_dimensions.w,
|
||||||
|
h: artist_dimensions.h,
|
||||||
|
},
|
||||||
|
Color::from_rgba(0, 0, 0, 160),
|
||||||
|
)?
|
||||||
|
.rectangle(
|
||||||
|
DrawMode::fill(),
|
||||||
|
Rect {
|
||||||
|
x: TEXT_X_OFFSET,
|
||||||
|
y: self.title_y,
|
||||||
|
w: title_dimensions.w,
|
||||||
|
h: title_dimensions.h,
|
||||||
|
},
|
||||||
|
Color::from_rgba(0, 0, 0, 160),
|
||||||
|
)?
|
||||||
|
.rectangle(
|
||||||
|
DrawMode::fill(),
|
||||||
|
Rect {
|
||||||
|
x: TEXT_X_OFFSET,
|
||||||
|
y: self.timer_y,
|
||||||
|
w: timer_dimensions.w,
|
||||||
|
h: timer_dimensions.h,
|
||||||
|
},
|
||||||
|
Color::from_rgba(0, 0, 0, 160),
|
||||||
|
)?
|
||||||
|
.build(ctx)?;
|
||||||
|
mesh.draw(ctx, DrawParam::default())?;
|
||||||
|
|
||||||
|
self.filename_text.draw(
|
||||||
|
ctx,
|
||||||
|
DrawParam {
|
||||||
|
trans: self.filename_transform,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.artist_text.draw(
|
||||||
|
ctx,
|
||||||
|
DrawParam {
|
||||||
|
trans: self.artist_transform,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.title_text.draw(
|
||||||
|
ctx,
|
||||||
|
DrawParam {
|
||||||
|
trans: self.title_transform,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.timer_text.draw(
|
||||||
|
ctx,
|
||||||
|
DrawParam {
|
||||||
|
trans: self.timer_transform,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
graphics::present(ctx)
|
graphics::present(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,5 +540,6 @@ impl EventHandler for MPDDisplay {
|
||||||
|
|
||||||
fn resize_event(&mut self, ctx: &mut Context, _width: f32, _height: f32) {
|
fn resize_event(&mut self, ctx: &mut Context, _width: f32, _height: f32) {
|
||||||
self.get_album_art_transform(ctx, false);
|
self.get_album_art_transform(ctx, false);
|
||||||
|
self.refresh_text_transforms(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,29 +217,6 @@ fn read_line(
|
||||||
Err((String::from("Newline not reached"), result))
|
Err((String::from("Newline not reached"), result))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn seconds_to_time(seconds: f64) -> String {
|
|
||||||
let seconds_int: u64 = seconds.floor() as u64;
|
|
||||||
let minutes = seconds_int / 60;
|
|
||||||
let new_seconds: f64 = seconds - (minutes * 60) as f64;
|
|
||||||
let mut result: String;
|
|
||||||
if minutes > 0 {
|
|
||||||
result = minutes.to_string();
|
|
||||||
result.push(':');
|
|
||||||
if new_seconds < 10.0 {
|
|
||||||
result.push('0');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = String::new();
|
|
||||||
}
|
|
||||||
result.push_str(&new_seconds.to_string());
|
|
||||||
let idx_result = result.find('.');
|
|
||||||
if let Some(idx) = idx_result {
|
|
||||||
result.truncate(idx + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MPDHandler {
|
impl MPDHandler {
|
||||||
pub fn new(host: Ipv4Addr, port: u16, password: String) -> Result<Arc<RwLock<Self>>, String> {
|
pub fn new(host: Ipv4Addr, port: u16, password: String) -> Result<Arc<RwLock<Self>>, String> {
|
||||||
let stream = TcpStream::connect_timeout(
|
let stream = TcpStream::connect_timeout(
|
||||||
|
|
Loading…
Reference in a new issue