use std::fs::File; use std::io::{BufReader}; use std::process::Command; use gio::prelude::*; use gtk::prelude::*; use gtk::Orientation; use rodio::{Sink, Source}; const SPACING: i32 = 16; fn popup(title: &str, message: &str) -> gtk::Window { let popup = gtk::Window::new(gtk::WindowType::Toplevel); popup.set_title(title); popup.set_border_width(SPACING as u32); popup.set_position(gtk::WindowPosition::Center); popup.set_default_size(256, 64); popup.set_type_hint(gdk::WindowTypeHint::Dialog); popup.set_resizable(false); let message = gtk::Label::new(Some(message)); popup.add(&message); popup } 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, 128); if let Some(config) = crate::config::load_config() { window.move_(config.position.0, config.position.1); window.resize(config.size.0, config.size.1); } let button_manage_sounds = gtk::Button::with_label("open sounds folder"); button_manage_sounds.connect_clicked(|_| { popup( "info", concat!( "after changing the sounds in this folder, ", "you will need to restart lull to see your changes." ) ); let mut file_manager = Command::new(crate::file_manager()); file_manager.arg(crate::get_sounds_dir()); file_manager.output().unwrap(); }); let sounds_manage = gtk::Box::new(Orientation::Vertical, SPACING); let columns = gtk::Box::new(Orientation::Horizontal, SPACING); let column_labels = gtk::Box::new(Orientation::Vertical, SPACING); let column_sliders = gtk::Box::new(Orientation::Vertical, SPACING); columns.set_homogeneous(false); column_labels.set_homogeneous(true); column_sliders.set_homogeneous(true); column_labels.set_expand(false); column_sliders.set_expand(true); column_sliders.set_width_request(128); window.add(&sounds_manage); sounds_manage.add(&columns); sounds_manage.add(&button_manage_sounds); columns.add(&column_labels); columns.add(&column_sliders); let device = rodio::default_output_device().unwrap(); let mut paths = std::fs::read_dir(crate::get_sounds_dir()) .expect("Couldn't read lull sounds directory") .map(|res| res.map(|e| e.path())) .collect::, std::io::Error>>() .expect("Couldn't read files from lull sounds directory"); paths.sort(); for path in paths { let name = path.file_stem().unwrap().to_str().unwrap(); let extension = path.extension().unwrap().to_str().unwrap(); // on Windows the data dir and config dir are the same, // so avoid parsing the config file as an audio file if extension == "toml" { continue; } let file = File::open(&path) .expect("Couldn't open audio file"); let source = rodio::Decoder::new( BufReader::new(file) ); if source.is_err() { popup("error", &format!( "Couldn't parse file {}. \n{}.", path.to_str().unwrap(), source.err().unwrap() )).show_all(); continue; } let source = source.unwrap().repeat_infinite(); let sink = Sink::new(&device); sink.append(source); sink.pause(); let label = gtk::Label::new(Some(name)); label.set_halign(gtk::Align::End); column_labels.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.value(); if volume == 0. { sink.pause(); } else { sink.play(); sink.set_volume(volume as f32); } }); column_sliders.add(&slider); } window.show_all(); window.connect_delete_event(|window, _event| { crate::config::save_config(crate::config::Config { position: window.position(), size: window.size() }); Inhibit(false) }); } pub(crate) fn instantiate() { #[cfg(target_os = "windows")] { // instantiate rodio before gtk and don't do anything with it // this is silly but it's the easiest way to prevent cpal crashing on windows rodio::default_output_device().unwrap(); } let application = gtk::Application::new( Some("dev.tinybird.max.lull"), Default::default() ); application.connect_activate(|app| { build_ui(app); }); application.run(); }