Add support for selecting a file
This commit is contained in:
parent
febc0737bf
commit
5724b25a88
|
@ -0,0 +1,185 @@
|
||||||
|
use iced::canvas::event::Status;
|
||||||
|
use iced::Font;
|
||||||
|
use iced::canvas::Text;
|
||||||
|
use iced::canvas::Stroke;
|
||||||
|
use iced::Size;
|
||||||
|
use iced::Point;
|
||||||
|
use iced::canvas::Path;
|
||||||
|
use iced::canvas::Frame;
|
||||||
|
use iced::canvas::Geometry;
|
||||||
|
use iced::canvas::Cursor;
|
||||||
|
use iced::Element;
|
||||||
|
use crate::app::Message;
|
||||||
|
use iced::canvas::Program;
|
||||||
|
use iced::mouse;
|
||||||
|
use iced::Color;
|
||||||
|
use iced::Rectangle;
|
||||||
|
use iced::Length;
|
||||||
|
use iced::HorizontalAlignment;
|
||||||
|
use iced::VerticalAlignment;
|
||||||
|
use iced::widget::canvas::{self, Canvas};
|
||||||
|
|
||||||
|
/* RYGCBM
|
||||||
|
const COLORS: [Color; 6] = [
|
||||||
|
Color { r: 1., g: 0., b: 0., a: 1. },
|
||||||
|
Color { r: 1., g: 1., b: 0., a: 1. },
|
||||||
|
Color { r: 0., g: 1., b: 0., a: 1. },
|
||||||
|
Color { r: 0., g: 1., b: 1., a: 1. },
|
||||||
|
Color { r: 0., g: 0., b: 1., a: 1. },
|
||||||
|
Color { r: 1., g: 0., b: 1., a: 1. },
|
||||||
|
];*/
|
||||||
|
const COLORS: [Color; 6] = [
|
||||||
|
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: 233./255., g: 236./255., b: 107./255., a: 1. },
|
||||||
|
Color { r: 119./255., g: 221./255., b: 119./255., a: 1. },
|
||||||
|
Color { r: 139./255., g: 211./255., b: 230./255., a: 1. },
|
||||||
|
Color { r: 177./255., g: 162./255., b: 202./255., a: 1. },
|
||||||
|
];
|
||||||
|
|
||||||
|
const MAX_TICKS: usize = 900;
|
||||||
|
|
||||||
|
const FONT_MR_PIXEL: Font = Font::External {
|
||||||
|
name: "Mister Pixel",
|
||||||
|
bytes: include_bytes!("../../fonts/mister-pixel/mister-pixel.otf"),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct FileSelector {
|
||||||
|
tick: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSelector {
|
||||||
|
pub fn tick(&mut self) {
|
||||||
|
self.tick = (self.tick + 1) % MAX_TICKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&mut self) -> Element<Message> {
|
||||||
|
Canvas::new(self)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FileSelector {
|
||||||
|
fn default() -> Self {
|
||||||
|
FileSelector {
|
||||||
|
tick: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Program<Message> for FileSelector {
|
||||||
|
fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {
|
||||||
|
let offset_per_index = MAX_TICKS / &COLORS.len();
|
||||||
|
|
||||||
|
const TEXT_RECT_W: f32 = 350.;
|
||||||
|
const TEXT_RECT_H: f32 = 50.;
|
||||||
|
const RECT_RATIO: f32 = TEXT_RECT_W / TEXT_RECT_H;
|
||||||
|
let text_rect_scale = TEXT_RECT_W / bounds.width;
|
||||||
|
let text_rect: (Point, Size) = centered_rectangle(&bounds, RECT_RATIO, text_rect_scale);
|
||||||
|
let text_rect_far_point = Point {
|
||||||
|
x: text_rect.0.x + text_rect.1.width,
|
||||||
|
y: text_rect.0.y + text_rect.1.height,
|
||||||
|
};
|
||||||
|
let bound_far_point: Point = Point {
|
||||||
|
x: bounds.x + bounds.width,
|
||||||
|
y: bounds.y + bounds.height,
|
||||||
|
};
|
||||||
|
let screen_top_left = Point { x: bounds.x, y: bounds.y };
|
||||||
|
|
||||||
|
let mut frame = Frame::new(bounds.size());
|
||||||
|
|
||||||
|
frame.fill_rectangle(
|
||||||
|
Point::new(bounds.x, bounds.y),
|
||||||
|
Size::new(bounds.width, bounds.height),
|
||||||
|
Color::WHITE,
|
||||||
|
);
|
||||||
|
|
||||||
|
COLORS.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, color)| {
|
||||||
|
let size =
|
||||||
|
((self.tick + offset_per_index * index) % MAX_TICKS) as f32 /
|
||||||
|
MAX_TICKS as f32;
|
||||||
|
|
||||||
|
let top_left = interpolate(text_rect.0, screen_top_left, 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);
|
||||||
|
|
||||||
|
(Path::rectangle(top_left, rect_size), color, size)
|
||||||
|
})
|
||||||
|
.for_each(|(rect, color, size)|
|
||||||
|
frame.stroke(
|
||||||
|
&rect,
|
||||||
|
Stroke::default()
|
||||||
|
.with_color(*color)
|
||||||
|
.with_width(5.)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
frame.fill_text(
|
||||||
|
Text {
|
||||||
|
content: String::from("select a song to start"),
|
||||||
|
horizontal_alignment: HorizontalAlignment::Center,
|
||||||
|
vertical_alignment: VerticalAlignment::Center,
|
||||||
|
size: 32.,
|
||||||
|
color: Color::WHITE,
|
||||||
|
font: FONT_MR_PIXEL,
|
||||||
|
position: Point::new(
|
||||||
|
bounds.x + bounds.width * 0.5,
|
||||||
|
bounds.y + bounds.height * 0.5,
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
frame.fill(
|
||||||
|
&Path::rectangle(text_rect.0, text_rect.1),
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
frame.stroke(
|
||||||
|
&Path::rectangle(text_rect.0, text_rect.1),
|
||||||
|
Stroke::default()
|
||||||
|
.with_color(Color::BLACK)
|
||||||
|
.with_width(2.)
|
||||||
|
);
|
||||||
|
|
||||||
|
vec![frame.into_geometry()]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
event: canvas::Event,
|
||||||
|
_: Rectangle<f32>,
|
||||||
|
_: Cursor
|
||||||
|
) -> (Status, Option<Message>) {
|
||||||
|
match event {
|
||||||
|
canvas::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
|
||||||
|
(Status::Captured, Some(Message::PromptForFile))
|
||||||
|
},
|
||||||
|
_ => (Status::Ignored, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn centered_rectangle(bounds: &Rectangle, ratio: f32, scale: f32) -> (Point, Size) {
|
||||||
|
let center_x = bounds.x + 0.5 * bounds.width;
|
||||||
|
let center_y = bounds.y + 0.5 * bounds.height;
|
||||||
|
let width = bounds.width * scale;
|
||||||
|
let height = width / ratio;
|
||||||
|
let top_x = center_x - width * 0.5;
|
||||||
|
let top_y = center_y - height * 0.5;
|
||||||
|
(
|
||||||
|
Point::new(top_x, top_y),
|
||||||
|
Size::new(width, height),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interpolate(from: Point, to: Point, amount: f32) -> Point {
|
||||||
|
Point {
|
||||||
|
x: from.x + amount * (to.x - from.x),
|
||||||
|
y: from.y + amount * (to.y - from.y),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
use rfd::{AsyncFileDialog, FileHandle};
|
||||||
|
use std::path::PathBuf;
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
use iced::Subscription;
|
use iced::Subscription;
|
||||||
use iced::Clipboard;
|
use iced::Clipboard;
|
||||||
|
@ -12,6 +14,7 @@ use iced::executor;
|
||||||
use iced_futures::time;
|
use iced_futures::time;
|
||||||
use iced_native::subscription;
|
use iced_native::subscription;
|
||||||
use iced_native::keyboard;
|
use iced_native::keyboard;
|
||||||
|
use iced_native::window;
|
||||||
use iced_native::event::Event;
|
use iced_native::event::Event;
|
||||||
|
|
||||||
mod lyrics;
|
mod lyrics;
|
||||||
|
@ -43,6 +46,9 @@ pub enum Message {
|
||||||
LineAdvanced(usize),
|
LineAdvanced(usize),
|
||||||
PasteSent,
|
PasteSent,
|
||||||
Tick,
|
Tick,
|
||||||
|
PromptForFile,
|
||||||
|
FileOpened(PathBuf),
|
||||||
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for DelyriumApp {
|
impl Application for DelyriumApp {
|
||||||
|
@ -67,6 +73,7 @@ impl Application for DelyriumApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message, clipboard: &mut Clipboard) -> Command<Message>{
|
fn update(&mut self, message: Message, clipboard: &mut Clipboard) -> Command<Message>{
|
||||||
|
let mut command = None;
|
||||||
match message {
|
match message {
|
||||||
Message::LyricChanged { line_no, new_value } => {
|
Message::LyricChanged { line_no, new_value } => {
|
||||||
self.lyrics_component.update_line(new_value, line_no);
|
self.lyrics_component.update_line(new_value, line_no);
|
||||||
|
@ -90,9 +97,29 @@ impl Application for DelyriumApp {
|
||||||
},
|
},
|
||||||
_ => { },
|
_ => { },
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
Message::FileOpened(path) => {
|
||||||
|
println!("File opened! {}", path.display());
|
||||||
|
},
|
||||||
|
Message::PromptForFile => {
|
||||||
|
let task = async {
|
||||||
|
let handle = AsyncFileDialog::new()
|
||||||
|
.add_filter("Song Files", &["mp3", "flac", "ogg", "opus", "wav"])
|
||||||
|
.set_title("Select a song")
|
||||||
|
.pick_file()
|
||||||
|
.await;
|
||||||
|
if let Some(h) = handle {
|
||||||
|
Message::FileOpened(h.path().to_owned())
|
||||||
|
} else {
|
||||||
|
Message::Null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
command = Some(task.into());
|
||||||
|
},
|
||||||
|
Message::Null => { },
|
||||||
}
|
}
|
||||||
Command::none()
|
|
||||||
|
command.unwrap_or_else(Command::none)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Message> {
|
fn view(&mut self) -> Element<Message> {
|
||||||
|
@ -126,7 +153,10 @@ impl Application for DelyriumApp {
|
||||||
}
|
}
|
||||||
_ => { None }
|
_ => { None }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
Event::Window(window::Event::FileDropped(path)) => {
|
||||||
|
Some(Message::FileOpened(path))
|
||||||
|
},
|
||||||
_ => { None }
|
_ => { None }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue