Compare commits
6 Commits
c39b545029
...
eddf56ae64
Author | SHA1 | Date |
---|---|---|
Emi Simpson | eddf56ae64 | |
Emi Simpson | 18572a6eff | |
Emi Simpson | 020e6c4f92 | |
Emi Simpson | ace47d3225 | |
Emi Simpson | 5dd4841e55 | |
Emi Simpson | 34f548748a |
|
@ -41,5 +41,5 @@ version = "0.3.0"
|
||||||
[dependencies.rodio]
|
[dependencies.rodio]
|
||||||
# Playing audio
|
# Playing audio
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["symphonia"]
|
features = ["symphonia-all"]
|
||||||
path = "./rodio/"
|
path = "./rodio/"
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
SIL Open Font License v1.1
|
||||||
|
====================================================
|
||||||
|
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
----------
|
||||||
|
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
|
||||||
|
Definitions
|
||||||
|
-------------
|
||||||
|
|
||||||
|
`"Font Software"` refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
`"Reserved Font Name"` refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
`"Original Version"` refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
`"Modified Version"` refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
`"Author"` refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
|
||||||
|
Permission & Conditions
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1. Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2. Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3. No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5. The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
|
||||||
|
Termination
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
Binary file not shown.
|
@ -69,6 +69,7 @@ impl Application for DelyriumApp {
|
||||||
},
|
},
|
||||||
Message::PasteSent => {
|
Message::PasteSent => {
|
||||||
if let Some(lyrics) = self.lyrics_component.as_mut() {
|
if let Some(lyrics) = self.lyrics_component.as_mut() {
|
||||||
|
#[allow(clippy::or_fun_call)] // This is a const
|
||||||
let clip_text = clipboard.read().unwrap_or(String::new());
|
let clip_text = clipboard.read().unwrap_or(String::new());
|
||||||
let clip_pasted_len = clip_text.chars()
|
let clip_pasted_len = clip_text.chars()
|
||||||
.filter(|c| *c != '\r' && *c != '\n')
|
.filter(|c| *c != '\r' && *c != '\n')
|
||||||
|
@ -138,7 +139,7 @@ impl Application for DelyriumApp {
|
||||||
(
|
(
|
||||||
keyboard::KeyCode::V,
|
keyboard::KeyCode::V,
|
||||||
keyboard::Modifiers { control, .. }
|
keyboard::Modifiers { control, .. }
|
||||||
) if control == true => {
|
) if control => {
|
||||||
Some(Message::PasteSent)
|
Some(Message::PasteSent)
|
||||||
}
|
}
|
||||||
_ => { None }
|
_ => { None }
|
||||||
|
|
|
@ -36,25 +36,29 @@ impl Editor {
|
||||||
let theme = cover.as_ref()
|
let theme = cover.as_ref()
|
||||||
.map(|cover| {
|
.map(|cover| {
|
||||||
Theme::from_palette(
|
Theme::from_palette(
|
||||||
Palette::generate(&cover)
|
Palette::generate(cover)
|
||||||
)
|
)
|
||||||
}).unwrap_or_else(|| Theme::default());
|
}).unwrap_or_else(Theme::default);
|
||||||
|
|
||||||
let cover = cover.expect("TODO");
|
let cover = cover.expect("TODO");
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
let cover = cover.blur((cover.width() / 50) as f32);
|
let cover = cover.blur((cover.width() / 100) as f32);
|
||||||
|
|
||||||
let bg_img = DynamicImage::ImageBgra8(cover.into_bgra8());
|
let bg_img = DynamicImage::ImageBgra8(cover.into_bgra8());
|
||||||
|
|
||||||
let (controls, cmd) = Controls::new(song);
|
let (controls, cmd) = Controls::new(song);
|
||||||
|
|
||||||
(Self {
|
let mut editor = Self {
|
||||||
lyrics: Lyrics::new(),
|
lyrics: Lyrics::new(),
|
||||||
dimensions: size,
|
dimensions: size,
|
||||||
cached_resized_bg: None,
|
cached_resized_bg: None,
|
||||||
controls, theme, bg_img,
|
controls, theme, bg_img,
|
||||||
}, cmd)
|
};
|
||||||
|
|
||||||
|
editor.notify_resized(size.0, size.1);
|
||||||
|
|
||||||
|
(editor, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: work on untangling this mess
|
// TODO: work on untangling this mess
|
||||||
|
@ -67,12 +71,6 @@ impl Editor {
|
||||||
pub fn handle_lyric_event(&mut self, line_no: usize, kind: LyricEvent) {
|
pub fn handle_lyric_event(&mut self, line_no: usize, kind: LyricEvent) {
|
||||||
self.lyrics.handle_event(line_no, kind)
|
self.lyrics.handle_event(line_no, kind)
|
||||||
}
|
}
|
||||||
pub fn update_line(&mut self, new_content: String, line_no: usize) {
|
|
||||||
self.lyrics.update_line(new_content, line_no);
|
|
||||||
}
|
|
||||||
pub fn advance_line(&mut self, current_line: usize) {
|
|
||||||
self.lyrics.advance_line(current_line);
|
|
||||||
}
|
|
||||||
pub fn current_line_mut(&mut self) -> (usize, &mut Lyric) {
|
pub fn current_line_mut(&mut self) -> (usize, &mut Lyric) {
|
||||||
self.lyrics.current_line_mut()
|
self.lyrics.current_line_mut()
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ const COLORS: [Color; 6] = [
|
||||||
Color { r: 0., g: 0., b: 1., a: 1. },
|
Color { r: 0., g: 0., b: 1., a: 1. },
|
||||||
Color { r: 1., g: 0., b: 1., a: 1. },
|
Color { r: 1., g: 0., b: 1., a: 1. },
|
||||||
];*/
|
];*/
|
||||||
|
#[allow(clippy::eq_op)]
|
||||||
const COLORS: [Color; 6] = [
|
const COLORS: [Color; 6] = [
|
||||||
Color { r: 255./255., g: 129./255., b: 126./255., a: 1. },
|
Color { r: 255./255., g: 129./255., b: 126./255., a: 1. },
|
||||||
Color { r: 239./255., g: 190./255., b: 125./255., a: 1. },
|
Color { r: 239./255., g: 190./255., b: 125./255., a: 1. },
|
||||||
|
@ -44,7 +45,7 @@ const FONT_MR_PIXEL: Font = Font::External {
|
||||||
bytes: include_bytes!("../fonts/mister-pixel/mister-pixel.otf"),
|
bytes: include_bytes!("../fonts/mister-pixel/mister-pixel.otf"),
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct FileSelector {
|
pub struct FileSelector {
|
||||||
tick: usize,
|
tick: usize,
|
||||||
}
|
}
|
||||||
|
@ -62,17 +63,9 @@ impl FileSelector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FileSelector {
|
|
||||||
fn default() -> Self {
|
|
||||||
FileSelector {
|
|
||||||
tick: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program<Message> for FileSelector {
|
impl Program<Message> for FileSelector {
|
||||||
fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {
|
fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {
|
||||||
let offset_per_index = MAX_TICKS / &COLORS.len();
|
let offset_per_index = MAX_TICKS / COLORS.len();
|
||||||
|
|
||||||
const TEXT_RECT_W: f32 = 350.;
|
const TEXT_RECT_W: f32 = 350.;
|
||||||
const TEXT_RECT_H: f32 = 50.;
|
const TEXT_RECT_H: f32 = 50.;
|
||||||
|
@ -87,7 +80,6 @@ impl Program<Message> for FileSelector {
|
||||||
x: bounds.x + bounds.width,
|
x: bounds.x + bounds.width,
|
||||||
y: bounds.y + bounds.height,
|
y: bounds.y + bounds.height,
|
||||||
};
|
};
|
||||||
let screen_top_left = Point { x: bounds.x, y: bounds.y };
|
|
||||||
|
|
||||||
let mut frame = Frame::new(bounds.size());
|
let mut frame = Frame::new(bounds.size());
|
||||||
|
|
||||||
|
@ -104,13 +96,13 @@ impl Program<Message> for FileSelector {
|
||||||
((self.tick + offset_per_index * index) % MAX_TICKS) as f32 /
|
((self.tick + offset_per_index * index) % MAX_TICKS) as f32 /
|
||||||
MAX_TICKS as f32;
|
MAX_TICKS as f32;
|
||||||
|
|
||||||
let top_left = interpolate(text_rect.0, screen_top_left, size);
|
let top_left = interpolate(text_rect.0, Point::ORIGIN, size);
|
||||||
let far_corner = interpolate(text_rect_far_point, bound_far_point, size);
|
let far_corner = interpolate(text_rect_far_point, bound_far_point, size);
|
||||||
let rect_size = Size::new(far_corner.x - top_left.x, far_corner.y - top_left.y);
|
let rect_size = Size::new(far_corner.x - top_left.x, far_corner.y - top_left.y);
|
||||||
|
|
||||||
(Path::rectangle(top_left, rect_size), color, size)
|
(Path::rectangle(top_left, rect_size), color)
|
||||||
})
|
})
|
||||||
.for_each(|(rect, color, size)|
|
.for_each(|(rect, color)|
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&rect,
|
&rect,
|
||||||
Stroke::default()
|
Stroke::default()
|
||||||
|
@ -131,7 +123,6 @@ impl Program<Message> for FileSelector {
|
||||||
bounds.x + bounds.width * 0.5,
|
bounds.x + bounds.width * 0.5,
|
||||||
bounds.y + bounds.height * 0.5,
|
bounds.y + bounds.height * 0.5,
|
||||||
),
|
),
|
||||||
..Default::default()
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -39,11 +39,12 @@ pub fn load_song(path: &Path) -> Result<ProbeResult, LoadError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_cover(format: &mut dyn FormatReader) -> Option<DynamicImage>{
|
pub fn extract_cover(format: &mut dyn FormatReader) -> Option<DynamicImage>{
|
||||||
|
#[allow(clippy::zero_prefixed_literal)]
|
||||||
format.metadata()
|
format.metadata()
|
||||||
.current()
|
.current()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
// Replace this whole closure with MetadataRef::usage once we update
|
// Replace this whole closure with MetadataRef::usage once we update
|
||||||
.flat_map(|meta| meta.visuals().iter().map(|v|(v.data.clone(), v.usage.clone())).collect::<Vec<_>>())
|
.flat_map(|meta| meta.visuals().iter().map(|v|(v.data.clone(), v.usage)).collect::<Vec<_>>())
|
||||||
.filter_map(|(data, usage)| image::load_from_memory(&data).ok().map(|img| (usage, img)))
|
.filter_map(|(data, usage)| image::load_from_memory(&data).ok().map(|img| (usage, img)))
|
||||||
.max_by_key(|(usage, _)|
|
.max_by_key(|(usage, _)|
|
||||||
usage.map(|usage| match usage {
|
usage.map(|usage| match usage {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use iced::Space;
|
||||||
use iced_native::text_input::Value;
|
use iced_native::text_input::Value;
|
||||||
use core::ops::RangeInclusive;
|
use core::ops::RangeInclusive;
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
|
@ -6,7 +7,7 @@ use iced::Text;
|
||||||
use iced::Container;
|
use iced::Container;
|
||||||
use iced::Length;
|
use iced::Length;
|
||||||
use iced::Element;
|
use iced::Element;
|
||||||
use crate::styles::Theme;
|
use crate::styles::{Theme, FONT_VG5000};
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
|
|
||||||
use iced::widget::text_input::{self, TextInput};
|
use iced::widget::text_input::{self, TextInput};
|
||||||
|
@ -22,7 +23,7 @@ pub enum LyricEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LyricEvent {
|
impl LyricEvent {
|
||||||
fn to_msg(self, line_no: usize) -> Message {
|
fn into_msg(self, line_no: usize) -> Message {
|
||||||
Message::LyricEvent {
|
Message::LyricEvent {
|
||||||
kind: self,
|
kind: self,
|
||||||
line_no,
|
line_no,
|
||||||
|
@ -116,18 +117,22 @@ impl Lyrics {
|
||||||
self.lines
|
self.lines
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, l)| l.is_selected())
|
.find(|(_, l)| l.is_selected())
|
||||||
.next()
|
|
||||||
.expect("no line currently selected")
|
.expect("no line currently selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&mut self, theme: Theme) -> Element<Message> {
|
pub fn view(&mut self, theme: Theme) -> Element<Message> {
|
||||||
let is_sole_line = self.lines.len() == 1;
|
let is_sole_line = self.lines.len() == 1;
|
||||||
|
let spacers = (
|
||||||
|
Space::new(Length::Fill, Length::Units(30)),
|
||||||
|
Space::new(Length::Fill, Length::Units(30)),
|
||||||
|
);
|
||||||
|
|
||||||
let scroller = self.lines.iter_mut()
|
let scroller = self.lines.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, l)| l.view(is_sole_line, i, theme))
|
.map(|(i, l)| l.view(is_sole_line, i, theme))
|
||||||
.fold(Scrollable::new(&mut self.scroll_state), |s, l| s.push(l))
|
.fold(Scrollable::new(&mut self.scroll_state).push(spacers.0), |s, l| s.push(l))
|
||||||
|
.push(spacers.1)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.align_items(Align::Center);
|
.align_items(Align::Center);
|
||||||
|
|
||||||
|
@ -166,6 +171,12 @@ impl Lyric {
|
||||||
|
|
||||||
pub fn view(&mut self, show_placeholder: bool, line_no: usize, theme: Theme) -> Element<Message> {
|
pub fn view(&mut self, show_placeholder: bool, line_no: usize, theme: Theme) -> Element<Message> {
|
||||||
|
|
||||||
|
const SMALL_SIZE: u16 = 20;
|
||||||
|
const LARGE_SIZE: u16 = 25;
|
||||||
|
const TIMESTAMP_W: u16 = 67;
|
||||||
|
const LINE_HEIGHT: u16 = 26;
|
||||||
|
const TOTAL_W: u16 = 400;
|
||||||
|
|
||||||
let is_focused = self.is_selected();
|
let is_focused = self.is_selected();
|
||||||
|
|
||||||
let placeholder = if show_placeholder {
|
let placeholder = if show_placeholder {
|
||||||
|
@ -174,41 +185,48 @@ impl Lyric {
|
||||||
"..."
|
"..."
|
||||||
} else { "" };
|
} else { "" };
|
||||||
|
|
||||||
let size = if is_focused { 30 } else { 20 };
|
let size = if is_focused { LARGE_SIZE } else { SMALL_SIZE };
|
||||||
|
|
||||||
let timestamp_input = TextInput::new(
|
let timestamp_input = TextInput::new(
|
||||||
&mut self.timestamp_state,
|
&mut self.timestamp_state,
|
||||||
"",
|
"",
|
||||||
&self.timestamp_raw,
|
&self.timestamp_raw,
|
||||||
move|new_value| LyricEvent::TimestampChanged(new_value).to_msg(line_no),
|
move|new_value| LyricEvent::TimestampChanged(new_value).into_msg(line_no),
|
||||||
)
|
)
|
||||||
.style(theme)
|
.style(theme)
|
||||||
.size(size - 5)
|
.size(SMALL_SIZE)
|
||||||
.width(Length::Units(97))
|
.width(Length::Units(TIMESTAMP_W))
|
||||||
.on_submit(LyricEvent::LineAdvanced.to_msg(line_no))
|
.on_submit(LyricEvent::LineAdvanced.into_msg(line_no))
|
||||||
|
.font(FONT_VG5000)
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let text_input = TextInput::new(
|
let text_input = TextInput::new(
|
||||||
&mut self.main_state,
|
&mut self.main_state,
|
||||||
placeholder,
|
placeholder,
|
||||||
&self.value,
|
&self.value,
|
||||||
move|new_value| LyricEvent::LyricChanged(new_value).to_msg(line_no),
|
move|new_value| LyricEvent::LyricChanged(new_value).into_msg(line_no),
|
||||||
)
|
)
|
||||||
.style(theme)
|
.style(theme.active_lyric(is_focused))
|
||||||
.size(size)
|
.size(size)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.on_submit(LyricEvent::LineAdvanced.to_msg(line_no))
|
.on_submit(LyricEvent::LineAdvanced.into_msg(line_no))
|
||||||
|
.font(FONT_VG5000)
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let l_bracket = Text::new("[")
|
let l_bracket = Text::new("[")
|
||||||
.size(size)
|
.size(SMALL_SIZE)
|
||||||
|
.color(theme.reduced_text_color())
|
||||||
|
.font(FONT_VG5000)
|
||||||
.into();
|
.into();
|
||||||
let r_bracket = Text::new("]")
|
let r_bracket = Text::new("] ")
|
||||||
.size(size)
|
.size(SMALL_SIZE)
|
||||||
|
.color(theme.reduced_text_color())
|
||||||
|
.font(FONT_VG5000)
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
Row::with_children(vec![l_bracket, timestamp_input, r_bracket, text_input])
|
Row::with_children(vec![l_bracket, timestamp_input, r_bracket, text_input])
|
||||||
.width(Length::Units(400))
|
.width(Length::Units(TOTAL_W))
|
||||||
|
.height(Length::Units(LINE_HEIGHT))
|
||||||
.align_items(Align::Center)
|
.align_items(Align::Center)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
@ -303,7 +321,7 @@ impl Lyric {
|
||||||
while
|
while
|
||||||
digit_counts[section] > MIN_DIGIT_COUNTS[section]
|
digit_counts[section] > MIN_DIGIT_COUNTS[section]
|
||||||
&& if section == 2 {
|
&& if section == 2 {
|
||||||
raw.chars().next_back().unwrap() == '0'
|
raw.ends_with('0')
|
||||||
} else {
|
} else {
|
||||||
raw.chars().nth(i).unwrap() == '0'
|
raw.chars().nth(i).unwrap() == '0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,6 @@ impl Palette {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
return &self.raw_colors[max_index];
|
&self.raw_colors[max_index]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ impl Player {
|
||||||
pub fn new(song: Box<dyn FormatReader>) -> Result<Self, PlayerError> {
|
pub fn new(song: Box<dyn FormatReader>) -> Result<Self, PlayerError> {
|
||||||
|
|
||||||
let song = Decoder::new_from_format_reader(song)
|
let song = Decoder::new_from_format_reader(song)
|
||||||
.map_err(PlayerError::DecoderError)?
|
.map_err(PlayerError::Decoder)?
|
||||||
.buffered();
|
.buffered();
|
||||||
|
|
||||||
let duration = Duration::from_secs(150);
|
let duration = Duration::from_secs(150);
|
||||||
|
@ -74,10 +74,10 @@ impl Player {
|
||||||
Some(result) // We'll report this error
|
Some(result) // We'll report this error
|
||||||
})
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
.map_err(PlayerError::StreamError)?
|
.map_err(PlayerError::Stream)?
|
||||||
.map(|(stream, handle)| Sink::try_new(&handle).map(|sink| (sink, stream)))
|
.map(|(stream, handle)| Sink::try_new(&handle).map(|sink| (sink, stream)))
|
||||||
.transpose()
|
.transpose()
|
||||||
.map_err(PlayerError::PlayError)?;
|
.map_err(PlayerError::Play)?;
|
||||||
|
|
||||||
Ok(self.sink.as_ref().map(|(s, _)| s))
|
Ok(self.sink.as_ref().map(|(s, _)| s))
|
||||||
}
|
}
|
||||||
|
@ -155,6 +155,7 @@ impl Player {
|
||||||
/// Return the duration of this track
|
/// Return the duration of this track
|
||||||
///
|
///
|
||||||
/// This is cheap and exact. All processing is done before hand.
|
/// This is cheap and exact. All processing is done before hand.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn get_duration(&self) -> Duration {
|
pub fn get_duration(&self) -> Duration {
|
||||||
self.duration
|
self.duration
|
||||||
}
|
}
|
||||||
|
@ -273,17 +274,17 @@ impl Player {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PlayerError {
|
pub enum PlayerError {
|
||||||
DecoderError(rodio::decoder::DecoderError),
|
Decoder(rodio::decoder::DecoderError),
|
||||||
PlayError(rodio::PlayError),
|
Play(rodio::PlayError),
|
||||||
StreamError(rodio::StreamError),
|
Stream(rodio::StreamError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for PlayerError {
|
impl fmt::Display for PlayerError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::DecoderError(e) => write!(f, "Could not decode the provided song: {}", e),
|
Self::Decoder(e) => write!(f, "Could not decode the provided song: {}", e),
|
||||||
Self::PlayError(e) => write!(f, "Problem playing to the audio output: {}", e),
|
Self::Play(e) => write!(f, "Problem playing to the audio output: {}", e),
|
||||||
Self::StreamError(e) => write!(f, "Problem connecting to the audio output: {}", e),
|
Self::Stream(e) => write!(f, "Problem connecting to the audio output: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use iced::Font;
|
||||||
use image::Rgb;
|
use image::Rgb;
|
||||||
use crate::palette::Palette;
|
use crate::palette::Palette;
|
||||||
use iced::Background;
|
use iced::Background;
|
||||||
|
@ -5,19 +6,62 @@ use iced::Color;
|
||||||
use iced::widget::container;
|
use iced::widget::container;
|
||||||
use iced::widget::text_input;
|
use iced::widget::text_input;
|
||||||
|
|
||||||
const TRANSPARENT: Color = Color { r: 0., g: 0., b: 0., a: 0. };
|
pub const FONT_VG5000: Font = Font::External {
|
||||||
|
name: "VG5000",
|
||||||
|
bytes: include_bytes!("../fonts/vg5000/VG5000.otf"),
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
pub base_color: Color,
|
pub base_color: Color,
|
||||||
pub text_color: Color,
|
pub text_color: Color,
|
||||||
|
subtype: Subtype,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
enum Subtype {
|
||||||
|
Base,
|
||||||
|
ActiveLyric,
|
||||||
|
}
|
||||||
|
use Subtype::*;
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
pub fn from_palette(palette: Palette) -> Self {
|
pub fn from_palette(palette: Palette) -> Self {
|
||||||
|
let base_color = img_color_to_iced(palette.dominant_color());
|
||||||
|
let luma = relative_lum(base_color);
|
||||||
|
let text_color = if luma > 0.2 {
|
||||||
|
Color {
|
||||||
|
a: 0.8,
|
||||||
|
..Color::BLACK
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color::WHITE
|
||||||
|
};
|
||||||
Theme {
|
Theme {
|
||||||
base_color: img_color_to_iced(palette.dominant_color()),
|
subtype: Base,
|
||||||
text_color: Color::WHITE,
|
base_color, text_color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reduced_text_color(&self) -> Color {
|
||||||
|
Color {
|
||||||
|
a: 0.7,
|
||||||
|
..self.text_color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_lyric(&self, active: bool) -> Self {
|
||||||
|
if active {
|
||||||
|
self.set_subtype(ActiveLyric)
|
||||||
|
} else {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_subtype(&self, subtype: Subtype) -> Self {
|
||||||
|
Theme {
|
||||||
|
subtype,
|
||||||
|
..*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +71,7 @@ impl Default for Theme {
|
||||||
Theme {
|
Theme {
|
||||||
base_color: Color {r: 236. / 255., g: 63. / 255., b: 53. / 255., a: 1.},
|
base_color: Color {r: 236. / 255., g: 63. / 255., b: 53. / 255., a: 1.},
|
||||||
text_color: Color {r: 1., g: 1., b: 1., a: 1.},
|
text_color: Color {r: 1., g: 1., b: 1., a: 1.},
|
||||||
|
subtype: Base,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,13 +101,18 @@ impl text_input::StyleSheet for Theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn placeholder_color(&self) -> Color {
|
fn placeholder_color(&self) -> Color {
|
||||||
let mut color = self.text_color;
|
Color {
|
||||||
color.a = 0.5;
|
a: 0.3,
|
||||||
color
|
..self.text_color
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_color(&self) -> Color {
|
fn value_color(&self) -> Color {
|
||||||
self.text_color
|
if self.subtype == ActiveLyric {
|
||||||
|
self.text_color
|
||||||
|
} else {
|
||||||
|
self.reduced_text_color()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selection_color(&self) -> Color {
|
fn selection_color(&self) -> Color {
|
||||||
|
@ -78,3 +128,17 @@ fn img_color_to_iced(color: &Rgb<u8>) -> Color {
|
||||||
a: 1.
|
a: 1.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn relative_lum(color: Color) -> f32 {
|
||||||
|
let mut t = [color.r, color.g, color.b]
|
||||||
|
.into_iter()
|
||||||
|
.map(|val| {
|
||||||
|
if val < 0.03928 {
|
||||||
|
val / 12.92
|
||||||
|
} else {
|
||||||
|
((val+0.055)/1.055).powf(2.4)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
0.2126 * t.next().unwrap() + 0.7152 * t.next().unwrap() + 0.0722 * t.next().unwrap()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue