#![windows_subsystem = "windows"] use gio::prelude::*; use gtk::prelude::*; use gtk::Orientation; use rodio::{Sink, Source}; use std::env::args; use std::fs::File; use std::io::BufReader; use std::path::PathBuf; use std::process::Command; const SPACING: i32 = 16; #[cfg(target_os = "windows")] fn file_manager() -> &'static str { "explorer" } #[cfg(not(target_os = "windows"))] fn file_manager() -> &'static str { "xdg-open" } fn error_popup(message: &str) { let popup = gtk::Window::new(gtk::WindowType::Toplevel); popup.set_title("error"); popup.set_border_width(SPACING as u32); popup.set_position(gtk::WindowPosition::Center); popup.set_default_size(256, 64); let vertical = gtk::Box::new(Orientation::Vertical, SPACING); popup.add(&vertical); let message = gtk::Label::new(Some(message)); vertical.add(&message); let button_ok = gtk::Button::with_label("OK"); vertical.add(&button_ok); popup.show_all(); button_ok.connect_clicked(move |_| unsafe { popup.destroy(); }); } fn get_data_dir() -> PathBuf { let mut data_dir = dirs::data_dir().expect("Couldn't find user data directory"); data_dir.push("ruin/lull"); if !data_dir.exists() { std::fs::create_dir_all(&data_dir).expect("Couldn't create lull data directory"); } data_dir } fn build_ui(application: >k::Application) { let window = gtk::ApplicationWindow::new(application); window.set_title("lull"); window.set_border_width(SPACING as u32); window.set_position(gtk::WindowPosition::Center); window.set_default_size(256, 256); let vertical = gtk::Box::new(Orientation::Vertical, SPACING); vertical.set_homogeneous(true); window.add(&vertical); let device = rodio::default_output_device().unwrap(); let paths = std::fs::read_dir(get_data_dir()) .expect("Couldn't read from lull data directory"); for path in paths { let path = path.unwrap().path(); let name: &str = path.file_stem().unwrap().to_str().unwrap(); let file = File::open(&path) .expect("Couldn't open audio file"); let source = rodio::Decoder::new( BufReader::new(file) ); if source.is_err() { error_popup(&format!( "Couldn't parse file {}. \n{}.", path.to_str().unwrap(), source.err().unwrap() )); continue; } let source = source.unwrap().repeat_infinite(); let sink = Sink::new(&device); sink.append(source); sink.pause(); let row = gtk::Box::new(Orientation::Horizontal, SPACING); row.set_homogeneous(true); let label = gtk::Label::new(Some(name)); row.add(&label); let adjustment = gtk::Adjustment::new( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0 ); let slider = gtk::Scale::new( Orientation::Horizontal, Some(&adjustment) ); slider.set_draw_value(false); slider.connect_value_changed(move |scale| { let volume = scale.get_value(); if volume == 0. { sink.pause(); } else { sink.play(); sink.set_volume(volume as f32); } }); row.add(&slider); vertical.add(&row); } let row_add = gtk::Box::new(Orientation::Horizontal, SPACING); row_add.set_homogeneous(true); let button_manage_sounds = gtk::Button::with_label("manage sounds"); button_manage_sounds.connect_clicked(|_| { let mut file_manager = Command::new(file_manager()); file_manager.arg(get_data_dir()); file_manager.output().unwrap(); }); row_add.add(&button_manage_sounds); vertical.add(&row_add); window.show_all(); } fn main() { let application = gtk::Application::new( Some("dev.tinybird.max.lull"), Default::default() ).expect("Initialization failed..."); application.connect_activate(|app| { build_ui(app); }); application.run(&args().collect::>()); }