├── .gitignore ├── asset ├── icon.ico ├── icon.png ├── Manual.png ├── music │ └── example.ogg ├── setting │ └── setting.ron └── shader │ └── shader.wgsl ├── src ├── entity.rs ├── entity │ ├── slider.rs │ ├── button.rs │ └── resource.rs ├── icon.rs ├── renderer.rs ├── buffer_player.rs └── main.rs ├── Cargo.toml ├── LICENSE └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | Cargo.lock 3 | /target 4 | **/*.rs.bk 5 | /bin -------------------------------------------------------------------------------- /asset/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OTOMariK/yee_player/HEAD/asset/icon.ico -------------------------------------------------------------------------------- /asset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OTOMariK/yee_player/HEAD/asset/icon.png -------------------------------------------------------------------------------- /asset/Manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OTOMariK/yee_player/HEAD/asset/Manual.png -------------------------------------------------------------------------------- /asset/music/example.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OTOMariK/yee_player/HEAD/asset/music/example.ogg -------------------------------------------------------------------------------- /src/entity.rs: -------------------------------------------------------------------------------- 1 | pub mod button; 2 | 3 | pub mod slider; 4 | 5 | 6 | 7 | pub mod render { 8 | pub use crate::renderer::Transform; 9 | } 10 | 11 | use legion::{Entity, Resources, World}; 12 | use std::sync::Arc; 13 | pub type ButtonFn = Arc; 14 | 15 | pub struct TargetValue(pub f32); 16 | 17 | pub mod resource; 18 | -------------------------------------------------------------------------------- /asset/setting/setting.ron: -------------------------------------------------------------------------------- 1 | ( 2 | // path to your music file 音乐文件路径 3 | music_path: "asset/music/example.ogg", 4 | // width of the window 窗口的宽度 5 | window_width: 512.0, 6 | // height of the window 窗口的高度 7 | window_height: 512.0, 8 | // max playback speed when moving the speed slider 移动速度滑块时可以调节的最大播放速度 9 | max_speed: 2.0, 10 | // min playback speed when moving the speed slider 移动速度滑块时可以调节的最低播放速度 11 | min_speed: -2.0, 12 | ) -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | resolver = "2" 3 | name = "yee_player" 4 | version = "0.1.1" 5 | authors = ["OTOMariK"] 6 | edition = "2018" 7 | 8 | [profile.release] 9 | lto = true 10 | panic = 'abort' 11 | 12 | [dependencies] 13 | log = "0.4" 14 | env_logger = "0.9" 15 | 16 | winit = "0.26" 17 | raw-window-handle = "0.4" 18 | wgpu = "0.12" 19 | 20 | legion = "0.4" 21 | 22 | rodio = "0.15" 23 | 24 | serde= "*" 25 | ron = "0.7" 26 | zerocopy= "0.6" 27 | 28 | futures="0.3" 29 | 30 | [target.'cfg(windows)'.build-dependencies] 31 | winres = "0.1" -------------------------------------------------------------------------------- /asset/shader/shader.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexOutput { 2 | [[location(0)]] uv: vec2; 3 | [[location(1)]] color: vec4; 4 | [[builtin(position)]] position: vec4; 5 | }; 6 | 7 | [[stage(vertex)]] 8 | fn vs_main( 9 | [[location(0)]] in_pos: vec2, 10 | [[location(1)]] in_uv_vs: vec2, 11 | [[location(2)]] in_instance_loc: vec2, 12 | [[location(3)]] in_instance_size: vec2, 13 | [[location(4)]] in_instance_color: vec3, 14 | ) -> VertexOutput { 15 | var pos2: vec2 = in_pos * in_instance_size + in_instance_loc; 16 | var out: VertexOutput; 17 | out.uv = in_uv_vs * in_instance_size; 18 | out.color = vec4(in_instance_color, 1.0); 19 | out.position = vec4(pos2, 0.0, 1.0); 20 | return out; 21 | } 22 | 23 | [[stage(fragment)]] 24 | fn fs_main(in: VertexOutput) -> [[location(0)]] vec4{ 25 | return in.color; 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 OTOMariK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Yee Player 6 | 7 | simple music player written in Rust 8 | 9 | 使用Rust编写的简易音乐播放器 10 | 11 | # Feature 功能 12 | 13 | * audio loading and playing 音频加载与播放 14 | * playing audio at any speed 任意速度播放音频 15 | * looping 循环播放 16 | 17 | # Usage 使用方法 18 | 19 |

20 | 21 | `play/pause button` and `reverse button` will changed the playback speed to acheive their function 22 | 23 | `播放/暂停按钮` 和 `倒放按钮` 通过改变播放速度来实现其功能 24 | 25 | `load setting button` will read [`asset/setting/setting.ron`](asset/setting/setting.ron) to reload the setting 26 | 27 | `加载设置按钮`会读取[`asset/setting/setting.ron`](asset/setting/setting.ron)以重新加载设置 28 | 29 | this program will load the whole audio data into memory, loading long audio file may crash 30 | 31 | 本程序将加载整个音频数据到内存中,加载过长的音频文件可能会导致崩溃 32 | 33 | # Setting 设置 34 | 35 | you can change some setting by editing `asset/setting/setting.ron` 36 | 37 | 你可以在`asset/setting/setting.ron`文件中修改程序的一些设置 38 | 39 | feilds of [`setting.ron`](asset/setting/setting.ron): 40 | 41 | [`setting.ron`](asset/setting/setting.ron)中的参数: 42 | 43 | // path to your music file 音乐文件路径 44 | music_path: String 45 | // width of the window 窗口的宽度 46 | window_width: f32 47 | // height of the window 窗口的高度 48 | window_height: f32 49 | // max playback speed when moving the speed slider 移动速度滑块时可以调节的最大播放速度 50 | max_play_speed: f32 51 | // min playback speed when moving the speed slider 移动速度滑块时可以调节的最低播放速度 52 | min_play_speed: f32 53 | 54 | # Main Dependencies 主要依赖库 55 | 56 | this project mainly uses the following crates 57 | 58 | 本工程主要使用了以下库 59 | 60 | * [`winit`](https://crates.io/crates/winit): input handling and window creating 处理用户输入以及创建窗口 61 | 62 | * [`wgpu`](https://crates.io/crates/wgpu): graphics rendering 图像渲染 63 | 64 | * [`legion`](https://crates.io/crates/legion): logic construction 逻辑处理 65 | 66 | * [`rodio`](https://crates.io/crates/rodio): audio decoding and playing 音频解码与播放 67 | -------------------------------------------------------------------------------- /src/entity/slider.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | pub struct Slider { 3 | // 0.0 .. 1.0 4 | value: f32, 5 | // real value that was not mapped 6 | input_value: Option, 7 | // mapped value range 8 | value_range: Range, 9 | } 10 | 11 | impl Slider { 12 | pub fn new(value: f32, value_range: Range) -> Self { 13 | let value = map_value(value, &value_range).min(1.0).max(0.0); 14 | Self { 15 | value, 16 | input_value: None, 17 | value_range, 18 | } 19 | } 20 | pub fn take_input_value(&mut self) -> Option { 21 | self.input_value.take() 22 | } 23 | pub fn input_value(&mut self, value: f32) { 24 | self.input_value = Some(value.min(self.value_range.end).max(self.value_range.start)); 25 | } 26 | pub fn get_value(&self) -> f32 { 27 | self.map_value_back(self.value) 28 | } 29 | pub fn get_value_mapped(&self) -> f32 { 30 | self.value 31 | } 32 | pub fn set_value(&mut self, value: f32) { 33 | self.value = self.map_value(value).min(1.0).max(0.0); 34 | } 35 | pub fn set_range(&mut self, value_range: Range) { 36 | self.value_range = value_range; 37 | } 38 | pub fn get_range(&self) -> &Range { 39 | &self.value_range 40 | } 41 | pub fn map_value_back(&self, value: f32) -> f32 { 42 | map_value_back(value, &self.value_range) 43 | } 44 | pub fn map_value(&self, value: f32) -> f32 { 45 | map_value(value, &self.value_range) 46 | } 47 | } 48 | 49 | // 0.0 .. 1.0 to range.end .. range.start 50 | fn map_value_back(value: f32, range: &Range) -> f32 { 51 | range.start + (range.end - range.start) * value 52 | } 53 | // range.end .. range.start to 0.0 .. 1.0 54 | fn map_value(value: f32, range: &Range) -> f32 { 55 | if (range.end - range.start).is_normal() { 56 | (value - range.start) / (range.end - range.start) 57 | } else { 58 | 0.0 59 | } 60 | } 61 | 62 | use super::button::ButtonColors; 63 | pub struct SliderColors { 64 | pub current_color: [f32; 3], 65 | pub state_colors: ButtonColors, 66 | } 67 | -------------------------------------------------------------------------------- /src/entity/button.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub enum ButtonState { 3 | Unhover, 4 | Hover, 5 | Press, 6 | } 7 | 8 | #[derive(Debug, Clone, Copy)] 9 | pub enum ButtonResponse { 10 | Hover, 11 | Unhover, 12 | Press, 13 | Release, 14 | } 15 | 16 | pub struct StateButton { 17 | state: ButtonState, 18 | response: Option, 19 | } 20 | 21 | impl StateButton { 22 | pub fn new() -> Self { 23 | StateButton { 24 | state: ButtonState::Unhover, 25 | response: None, 26 | } 27 | } 28 | pub fn update_with_input(&mut self, hovering: bool, pressing: bool) { 29 | use ButtonState::*; 30 | self.response = None; 31 | match self.state { 32 | Unhover => match (hovering, pressing) { 33 | (true, false) => { 34 | self.response = Some(ButtonResponse::Hover); 35 | self.state = Hover; 36 | } 37 | (_, _) => {} 38 | }, 39 | Hover => match (hovering, pressing) { 40 | (false, _) => { 41 | self.response = Some(ButtonResponse::Unhover); 42 | self.state = Unhover; 43 | } 44 | (true, false) => {} 45 | (true, true) => { 46 | self.response = Some(ButtonResponse::Press); 47 | self.state = Press; 48 | } 49 | }, 50 | Press => match (hovering, pressing) { 51 | (false, false) => { 52 | self.response = Some(ButtonResponse::Unhover); 53 | self.state = Unhover; 54 | } 55 | (true, false) => { 56 | self.response = Some(ButtonResponse::Release); 57 | self.state = Hover; 58 | } 59 | (_, true) => {} 60 | }, 61 | } 62 | } 63 | pub fn get_state(&self) -> &ButtonState { 64 | &self.state 65 | } 66 | pub fn get_response(&self) -> Option { 67 | self.response 68 | } 69 | } 70 | 71 | pub struct ButtonColors { 72 | pub base_color: [f32; 3], 73 | pub hover_color: [f32; 3], 74 | pub press_color: [f32; 3], 75 | } 76 | -------------------------------------------------------------------------------- /src/entity/resource.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | #[derive(Clone, Default, Debug)] 4 | pub struct Input { 5 | pub mouse_location: Option<(f32, f32)>, 6 | pub mouse_pressing: bool, 7 | pub ctrl_pressing: bool, 8 | pub hover_file: bool, 9 | pub drop_file: Option, 10 | pub exit: bool, 11 | } 12 | 13 | pub struct SettingPath(pub PathBuf); 14 | 15 | use serde::Deserialize; 16 | #[derive(Debug, Clone, Deserialize, PartialEq)] 17 | pub struct Setting { 18 | pub music_path: String, 19 | pub window_width: f32, 20 | pub window_height: f32, 21 | pub max_speed: f32, 22 | pub min_speed: f32, 23 | } 24 | 25 | impl Default for Setting { 26 | fn default() -> Self { 27 | Self { 28 | music_path: "./asset/music/example.ogg".to_string(), 29 | window_width: 512.0, 30 | window_height: 512.0, 31 | max_speed: 2.0, 32 | min_speed: -2.0, 33 | } 34 | } 35 | } 36 | impl Setting { 37 | pub fn load(path: &PathBuf) -> Result { 38 | let string = std::fs::read_to_string(path).map_err(|e| { 39 | let err = format!("error opening {:?}: {:?}", path, e); 40 | log::error!("{}", err); 41 | err 42 | })?; 43 | ron::de::from_str(string.as_str()).map_err(|e| { 44 | let err = format!("error parsing {:?}: {:?}", path, e); 45 | log::error!("{}", err); 46 | err 47 | }) 48 | } 49 | } 50 | 51 | pub type MusicFileMetaData = Option; 52 | 53 | use super::ButtonFn; 54 | pub struct ButtonFunctions { 55 | pub play_fn: ButtonFn, 56 | pub loop_fn: ButtonFn, 57 | pub unloop_fn: ButtonFn, 58 | pub load_fn: ButtonFn, 59 | pub stop_load_fn: ButtonFn, 60 | } 61 | 62 | pub mod audio { 63 | pub use crate::buffer_player::{AudioBufferLoader, AudioController}; 64 | use legion::Entity; 65 | pub struct AudioLoader { 66 | pub loader: AudioBufferLoader, 67 | pub path: String, 68 | pub load_button_entity: Entity, 69 | } 70 | pub type AudioLoaderRes = Option; 71 | } 72 | pub struct PlayingSpeed(pub f32); 73 | 74 | use legion::Entity; 75 | pub struct ControlledSliders { 76 | pub time_slider: Entity, 77 | pub speed_slider: Entity, 78 | pub volume_slider: Entity, 79 | } 80 | -------------------------------------------------------------------------------- /src/icon.rs: -------------------------------------------------------------------------------- 1 | use super::{LOOP_BUTTON_COLOR, NORMAL_BUTTON_COLOR, SLIDER_COLOR}; 2 | pub fn create_icon_data() -> Vec { 3 | let nb = vec![ 4 | (NORMAL_BUTTON_COLOR.base_color[0] * 255.0) as u8, 5 | (NORMAL_BUTTON_COLOR.base_color[1] * 255.0) as u8, 6 | (NORMAL_BUTTON_COLOR.base_color[2] * 255.0) as u8, 7 | 255, 8 | ]; 9 | let nh = vec![ 10 | (NORMAL_BUTTON_COLOR.hover_color[0] * 255.0) as u8, 11 | (NORMAL_BUTTON_COLOR.hover_color[1] * 255.0) as u8, 12 | (NORMAL_BUTTON_COLOR.hover_color[2] * 255.0) as u8, 13 | 255, 14 | ]; 15 | let lb = vec![ 16 | (LOOP_BUTTON_COLOR.base_color[0] * 255.0) as u8, 17 | (LOOP_BUTTON_COLOR.base_color[1] * 255.0) as u8, 18 | (LOOP_BUTTON_COLOR.base_color[2] * 255.0) as u8, 19 | 255, 20 | ]; 21 | let sb = vec![ 22 | (SLIDER_COLOR.base_color[0] * 255.0) as u8, 23 | (SLIDER_COLOR.base_color[1] * 255.0) as u8, 24 | (SLIDER_COLOR.base_color[2] * 255.0) as u8, 25 | 255, 26 | ]; 27 | let sh = vec![ 28 | (SLIDER_COLOR.hover_color[0] * 255.0) as u8, 29 | (SLIDER_COLOR.hover_color[1] * 255.0) as u8, 30 | (SLIDER_COLOR.hover_color[2] * 255.0) as u8, 31 | 255, 32 | ]; 33 | #[rustfmt::skip] 34 | let pixels = vec![ 35 | nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), lb.clone(), lb.clone(), lb.clone(), lb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 36 | nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), lb.clone(), lb.clone(), lb.clone(), lb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 37 | nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), lb.clone(), lb.clone(), lb.clone(), lb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 38 | nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), lb.clone(), lb.clone(), lb.clone(), lb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 39 | sh.clone(), sh.clone(), sh.clone(), sh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), 40 | sh.clone(), sh.clone(), sh.clone(), sh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), 41 | sh.clone(), sh.clone(), sh.clone(), sh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), 42 | sh.clone(), sh.clone(), sh.clone(), sh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), nh.clone(), 43 | sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 44 | sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 45 | sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 46 | sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), sb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 47 | sb.clone(), sb.clone(), sb.clone(), sb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 48 | sb.clone(), sb.clone(), sb.clone(), sb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 49 | sb.clone(), sb.clone(), sb.clone(), sb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 50 | sb.clone(), sb.clone(), sb.clone(), sb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), nb.clone(), 51 | ]; 52 | pixels.into_iter().flatten().collect::>() 53 | } 54 | -------------------------------------------------------------------------------- /src/renderer.rs: -------------------------------------------------------------------------------- 1 | use zerocopy::{AsBytes, FromBytes}; 2 | 3 | #[derive(Debug, Clone, Copy, AsBytes, FromBytes)] 4 | #[repr(C)] 5 | struct Vertex { 6 | position: [f32; 2], 7 | uv: [f32; 2], 8 | } 9 | 10 | const VERTEX: [Vertex; 4] = [ 11 | Vertex { 12 | position: [0.0, 0.0], 13 | uv: [0.0, 0.0], 14 | }, 15 | Vertex { 16 | position: [0.0, 1.0], 17 | uv: [0.0, 1.0], 18 | }, 19 | Vertex { 20 | position: [1.0, 1.0], 21 | uv: [1.0, 1.0], 22 | }, 23 | Vertex { 24 | position: [1.0, 0.0], 25 | uv: [1.0, 0.0], 26 | }, 27 | ]; 28 | 29 | const INDECES: [u16; 6] = [1, 2, 0, 0, 2, 3]; 30 | 31 | #[derive(Debug, Clone, Copy, AsBytes, FromBytes)] 32 | #[repr(C)] 33 | pub struct Transform { 34 | pub location: [f32; 2], 35 | pub size: [f32; 2], 36 | pub color: [f32; 3], 37 | } 38 | 39 | impl Default for Transform { 40 | fn default() -> Self { 41 | Transform { 42 | location: [0.0; 2], 43 | size: [1.0, 1.0], 44 | color: [1.0; 3], 45 | } 46 | } 47 | } 48 | use std::path::PathBuf; 49 | use wgpu::util::DeviceExt; 50 | 51 | pub struct PiplineSetting { 52 | pub shader_path: PathBuf, 53 | } 54 | 55 | pub struct Renderer { 56 | surface: wgpu::Surface, 57 | _adapter: wgpu::Adapter, 58 | device: wgpu::Device, 59 | queue: wgpu::Queue, 60 | surface_config: wgpu::SurfaceConfiguration, 61 | vertex_buffer: wgpu::Buffer, 62 | index_buffer: wgpu::Buffer, 63 | bind_group: wgpu::BindGroup, 64 | bind_group_layout: wgpu::BindGroupLayout, 65 | } 66 | 67 | impl Renderer { 68 | pub fn init( 69 | window: &W, 70 | size: winit::dpi::PhysicalSize, 71 | ) -> Result { 72 | let instance = wgpu::Instance::new(wgpu::Backends::all()); 73 | let surface = unsafe { instance.create_surface(window) }; 74 | 75 | let adapter = 76 | futures::executor::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { 77 | power_preference: Default::default(), 78 | compatible_surface: Some(&surface), 79 | force_fallback_adapter: false, 80 | })) 81 | .ok_or("no adapter found!")?; 82 | 83 | let (device, queue) = futures::executor::block_on(adapter.request_device( 84 | &wgpu::DeviceDescriptor { 85 | label: None, 86 | features: wgpu::Features::empty(), 87 | limits: wgpu::Limits::default(), 88 | }, 89 | None, 90 | )) 91 | .map_err(|e| format!("can not request device: {}", e))?; 92 | 93 | let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 94 | label: None, 95 | entries: &[], 96 | }); 97 | let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { 98 | label: None, 99 | layout: &bind_group_layout, 100 | entries: &[], 101 | }); 102 | 103 | let surface_config = wgpu::SurfaceConfiguration { 104 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT, 105 | format: wgpu::TextureFormat::Bgra8Unorm, 106 | width: size.width, 107 | height: size.height, 108 | present_mode: wgpu::PresentMode::Mailbox, 109 | }; 110 | surface.configure(&device, &surface_config); 111 | 112 | let (vertex_buffer, index_buffer) = { 113 | let vb = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 114 | label: Some("vertex buffer"), 115 | contents: VERTEX.as_bytes(), 116 | usage: wgpu::BufferUsages::VERTEX, 117 | }); 118 | 119 | let ib = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 120 | label: Some("index buffer"), 121 | contents: INDECES.as_bytes(), 122 | usage: wgpu::BufferUsages::INDEX, 123 | }); 124 | (vb, ib) 125 | }; 126 | 127 | Ok(Renderer { 128 | surface, 129 | _adapter: adapter, 130 | device, 131 | queue, 132 | surface_config, 133 | vertex_buffer, 134 | index_buffer, 135 | bind_group, 136 | bind_group_layout, 137 | }) 138 | } 139 | 140 | pub fn resize(&mut self, size: winit::dpi::PhysicalSize) { 141 | self.surface_config.width = size.width; 142 | self.surface_config.height = size.height; 143 | self.surface.configure(&self.device, &self.surface_config); 144 | } 145 | 146 | pub fn create_render_pipline( 147 | &self, 148 | setting: &PiplineSetting, 149 | ) -> Result { 150 | let shader_module = { 151 | let source_string = 152 | std::fs::read_to_string(&setting.shader_path).map_err(|e| e.to_string())?; 153 | self.device 154 | .create_shader_module(&wgpu::ShaderModuleDescriptor { 155 | label: None, 156 | source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&source_string)), 157 | }) 158 | }; 159 | 160 | let pipeline_layout = self 161 | .device 162 | .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 163 | label: None, 164 | bind_group_layouts: &[&self.bind_group_layout], 165 | push_constant_ranges: &[], 166 | }); 167 | Ok(self 168 | .device 169 | .create_render_pipeline(&wgpu::RenderPipelineDescriptor { 170 | label: None, 171 | layout: Some(&pipeline_layout), 172 | vertex: wgpu::VertexState { 173 | module: &shader_module, 174 | entry_point: "vs_main", 175 | buffers: &[ 176 | wgpu::VertexBufferLayout { 177 | array_stride: std::mem::size_of::() as wgpu::BufferAddress, 178 | step_mode: wgpu::VertexStepMode::Vertex, 179 | attributes: &[ 180 | wgpu::VertexAttribute { 181 | offset: 0, 182 | format: wgpu::VertexFormat::Float32x2, 183 | shader_location: 0, 184 | }, 185 | wgpu::VertexAttribute { 186 | offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, 187 | format: wgpu::VertexFormat::Float32x2, 188 | shader_location: 1, 189 | }, 190 | ], 191 | }, 192 | wgpu::VertexBufferLayout { 193 | array_stride: std::mem::size_of::() as wgpu::BufferAddress, 194 | step_mode: wgpu::VertexStepMode::Instance, 195 | attributes: &[ 196 | wgpu::VertexAttribute { 197 | offset: 0, 198 | format: wgpu::VertexFormat::Float32x2, 199 | shader_location: 2, 200 | }, 201 | wgpu::VertexAttribute { 202 | offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, 203 | format: wgpu::VertexFormat::Float32x2, 204 | shader_location: 3, 205 | }, 206 | wgpu::VertexAttribute { 207 | offset: (std::mem::size_of::<[f32; 2]>() 208 | + std::mem::size_of::<[f32; 2]>()) 209 | as wgpu::BufferAddress, 210 | format: wgpu::VertexFormat::Float32x3, 211 | shader_location: 4, 212 | }, 213 | ], 214 | }, 215 | ], 216 | }, 217 | primitive: wgpu::PrimitiveState::default(), 218 | depth_stencil: None, 219 | multisample: wgpu::MultisampleState::default(), 220 | fragment: Some(wgpu::FragmentState { 221 | module: &shader_module, 222 | entry_point: "fs_main", 223 | targets: &[self.surface_config.format.into()], 224 | }), 225 | multiview: None, 226 | })) 227 | } 228 | 229 | pub fn render( 230 | &mut self, 231 | transforms: &[Transform], 232 | render_pipeline: &wgpu::RenderPipeline, 233 | ) -> Result<(), String> { 234 | let frame = self 235 | .surface 236 | .get_current_texture() 237 | .map_err(|e| format!("{:?}", e))?; 238 | 239 | let mut encoder = self 240 | .device 241 | .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); 242 | let transform_buffer = self 243 | .device 244 | .create_buffer_init(&wgpu::util::BufferInitDescriptor { 245 | label: Some("transforms buffer"), 246 | contents: transforms.as_bytes(), 247 | usage: wgpu::BufferUsages::VERTEX, 248 | }); 249 | 250 | { 251 | let view = frame 252 | .texture 253 | .create_view(&wgpu::TextureViewDescriptor::default()); 254 | let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 255 | label: None, 256 | color_attachments: &[wgpu::RenderPassColorAttachment { 257 | view: &view, 258 | resolve_target: None, 259 | ops: wgpu::Operations { 260 | load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), 261 | store: true, 262 | }, 263 | }], 264 | depth_stencil_attachment: None, 265 | }); 266 | render_pass.set_pipeline(render_pipeline); 267 | render_pass.set_bind_group(0, &self.bind_group, &[]); 268 | render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); 269 | render_pass.set_vertex_buffer(1, transform_buffer.slice(..)); 270 | render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); 271 | render_pass.draw_indexed(0..INDECES.len() as u32, 0, 0..transforms.len() as u32); 272 | } 273 | self.queue.submit(Some(encoder.finish())); 274 | frame.present(); 275 | Ok(()) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/buffer_player.rs: -------------------------------------------------------------------------------- 1 | use rodio::{source::Source, Decoder, OutputStreamHandle, Sample}; 2 | use std::{ 3 | path::Path, 4 | sync::{Arc, RwLock}, 5 | time::Duration, 6 | }; 7 | 8 | /// A buffer of samples treated as a source. 9 | pub struct SamplesBuffer { 10 | data: Vec, 11 | channels: u16, 12 | sample_rate: u32, 13 | duration: Duration, 14 | } 15 | 16 | impl SamplesBuffer 17 | where 18 | S: Sample, 19 | { 20 | pub fn new(channels: u16, sample_rate: u32, data: D) -> SamplesBuffer 21 | where 22 | D: Into>, 23 | { 24 | assert!(channels != 0); 25 | assert!(sample_rate != 0); 26 | 27 | let mut data = data.into(); 28 | let remain_count = data.len() % channels as usize; 29 | if remain_count != 0 { 30 | let add_count = channels as usize - remain_count; 31 | let mut zeros = vec![S::zero_value(); add_count]; 32 | data.append(&mut zeros); 33 | } 34 | let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap() 35 | / sample_rate as u64 36 | / channels as u64; 37 | let duration = Duration::new( 38 | duration_ns / 1_000_000_000, 39 | (duration_ns % 1_000_000_000) as u32, 40 | ); 41 | 42 | SamplesBuffer { 43 | data, 44 | channels, 45 | sample_rate, 46 | duration, 47 | } 48 | } 49 | 50 | pub fn get_duration(&self) -> Duration { 51 | self.duration 52 | } 53 | } 54 | 55 | impl SamplesBuffer { 56 | pub fn load_from_file_async_stoppable>( 57 | path: P, 58 | stop_loading: Arc>, 59 | progress: Arc>, 60 | ) -> Result { 61 | let mut decoder = Decoder::new(std::io::BufReader::new( 62 | std::fs::File::open(path).map_err(|e| format!("error opening file: {:?}", e))?, 63 | )) 64 | .map_err(|e| format!("error decode audio: {:?}", e))?; 65 | 66 | let channels = decoder.channels(); 67 | let sample_rate = decoder.sample_rate(); 68 | 69 | let possible_data_duration = decoder.total_duration(); 70 | 71 | let mut data = match decoder.size_hint().1 { 72 | Some(size) => Vec::with_capacity(size), 73 | None => Vec::new(), 74 | }; 75 | 76 | let mut decoded = Duration::from_secs(0); 77 | loop { 78 | let frame_len = decoder.current_frame_len(); 79 | if frame_len == Some(0) { 80 | break; 81 | } 82 | let mut frame = decoder 83 | .by_ref() 84 | .take(frame_len.unwrap_or(32768).min(32768)) 85 | .collect::>(); 86 | if frame.is_empty() { 87 | break; 88 | } 89 | if *stop_loading.read().unwrap() { 90 | return Err("user stopped".to_string()); 91 | } 92 | 93 | if let Some(duration) = possible_data_duration { 94 | let duration_ns = 1_000_000_000u64.checked_mul(frame.len() as u64).unwrap() 95 | / sample_rate as u64 96 | / channels as u64; 97 | decoded += Duration::new( 98 | duration_ns / 1_000_000_000, 99 | (duration_ns % 1_000_000_000) as u32, 100 | ); 101 | if let Ok(mut progress) = progress.try_write() { 102 | *progress = decoded.as_secs_f32() / duration.as_secs_f32(); 103 | }; 104 | } 105 | 106 | data.append(&mut frame); 107 | } 108 | 109 | Ok(Self::new(channels, sample_rate, data)) 110 | } 111 | } 112 | 113 | // impl Drop for SamplesBuffer { 114 | // fn drop(&mut self) { 115 | // println!( 116 | // "SamplesBuffer dropped, length: {}s", 117 | // self.duration.as_secs_f32() 118 | // ); 119 | // } 120 | // } 121 | 122 | pub struct AudioBufferLoader( 123 | Arc, String>>>>, 124 | Arc>, 125 | Arc>, 126 | ); 127 | 128 | impl AudioBufferLoader { 129 | pub fn try_get_value(&mut self) -> Option, String>> { 130 | let mut v = self.0.write().unwrap(); 131 | v.take() 132 | } 133 | 134 | pub fn stop_loading(&self) { 135 | let mut stop = self.1.write().unwrap(); 136 | *stop = true; 137 | } 138 | 139 | pub fn get_progress(&self) -> f32 { 140 | *self.2.read().unwrap() 141 | } 142 | } 143 | 144 | impl AudioBufferLoader { 145 | pub fn load + Send + Sync + 'static>(path: P) -> Self { 146 | let value = Arc::new(RwLock::new(None)); 147 | let value2 = Arc::clone(&value); 148 | let stop_loading = Arc::new(RwLock::new(false)); 149 | let stop_loading2 = Arc::clone(&stop_loading); 150 | let progress = Arc::new(RwLock::new(0.0)); 151 | let progress2 = Arc::clone(&progress); 152 | std::thread::spawn(move || { 153 | let buffer = 154 | SamplesBuffer::load_from_file_async_stoppable(path, stop_loading2, progress2); 155 | let mut value = value2.write().unwrap(); 156 | *value = Some(buffer); 157 | }); 158 | Self(value, stop_loading, progress) 159 | } 160 | } 161 | 162 | /// A source that plays the SamplesBuffer at any speed. 163 | pub struct BufferPlayer { 164 | buffer: Arc>, 165 | channel: u16, 166 | location: usize, 167 | interval: f32, 168 | speed: f32, 169 | loop_mode: bool, 170 | } 171 | 172 | impl BufferPlayer { 173 | pub fn new(buffer: Arc>) -> Self { 174 | // let end = buffer.data.len(); 175 | Self { 176 | buffer, 177 | channel: 0, 178 | location: 0, 179 | interval: 0.0, 180 | speed: 1.0, 181 | loop_mode: false, 182 | } 183 | } 184 | 185 | #[inline] 186 | fn current_sample_location(&self) -> usize { 187 | (self.location * self.buffer.channels as usize + self.channel as usize) 188 | .min(self.buffer.data.len().saturating_sub(1)) 189 | } 190 | 191 | pub fn get_time(&self) -> Duration { 192 | let duration_ns = 1_000_000_000u64.checked_mul(self.location as u64).unwrap() 193 | / self.buffer.sample_rate as u64; 194 | Duration::new( 195 | duration_ns / 1_000_000_000, 196 | (duration_ns % 1_000_000_000) as u32, 197 | ) 198 | } 199 | 200 | pub fn set_time(&mut self, time: Duration) { 201 | self.location = ((time.as_secs() * self.buffer.sample_rate as u64 202 | + (time.subsec_nanos() as u64 * self.buffer.sample_rate as u64 / 1_000_000_000)) 203 | as usize) 204 | .min((self.buffer.data.len() / self.buffer.channels as usize).saturating_sub(1)); 205 | } 206 | 207 | pub fn get_speed(&self) -> f32 { 208 | self.speed 209 | } 210 | 211 | pub fn set_speed(&mut self, speed: f32) { 212 | self.speed = speed; 213 | } 214 | 215 | pub fn set_buffer(&mut self, buffer: Arc>) { 216 | self.buffer = buffer; 217 | } 218 | 219 | pub fn set_loop_mode(&mut self, loop_mode: bool) { 220 | self.loop_mode = loop_mode; 221 | } 222 | } 223 | 224 | impl Iterator for BufferPlayer 225 | where 226 | S: Sample, 227 | { 228 | type Item = S; 229 | 230 | // resample the samples when speed is not integer 231 | #[inline] 232 | fn next(&mut self) -> Option { 233 | if self.buffer.data.is_empty() 234 | || (self.buffer.sample_rate as f32 * self.speed.abs()) as u32 == 0 235 | { 236 | Some(S::zero_value()) 237 | } else { 238 | let fract = self.speed.abs().fract(); 239 | // check if the fract is too small, which indicates that the speed is integer. 240 | let value = if (self.buffer.sample_rate as f32 * fract) as u32 == 0 { 241 | let value = self.buffer.data[self.current_sample_location()]; 242 | if self.channel >= self.channels() - 1 { 243 | let increment = self.speed.round() as isize; 244 | 245 | let max_location = 246 | (self.buffer.data.len() / self.buffer.channels as usize).saturating_sub(1); 247 | if self.loop_mode { 248 | let v = (self.location as isize + increment) % (max_location + 1) as isize; 249 | self.location = if v < 0 { 250 | (v + (max_location + 1) as isize) as usize 251 | } else { 252 | v as usize 253 | } 254 | } else { 255 | self.location = (self.location as isize + increment) 256 | .min(max_location as isize) 257 | .max(0) as usize; 258 | } 259 | } 260 | value 261 | } else { 262 | let value = { 263 | // resample location is aligned based on fractional part of the speed 264 | let denominator = 1.0 / fract; 265 | let numerator = denominator * self.interval; 266 | let value0 = self.buffer.data[self.current_sample_location()]; 267 | let value1 = self.buffer.data[(self.current_sample_location() 268 | + self.buffer.channels as usize) 269 | % self.buffer.data.len()]; 270 | Sample::lerp(value0, value1, numerator as u32, denominator as u32) 271 | }; 272 | 273 | if self.channel >= self.channels() - 1 { 274 | let interval = self.interval + self.speed; 275 | // keep self.interval positive 276 | self.interval = if interval < 0.0 { 277 | interval.fract() + 1.0 278 | } else { 279 | interval.fract() 280 | }; 281 | let proceed = interval.floor() as isize; 282 | 283 | let max_location = 284 | (self.buffer.data.len() / self.buffer.channels as usize).saturating_sub(1); 285 | if self.loop_mode { 286 | let v = (self.location as isize + proceed) % (max_location + 1) as isize; 287 | self.location = if v < 0 { 288 | (v + (max_location + 1) as isize) as usize 289 | } else { 290 | v as usize 291 | } 292 | } else { 293 | self.location = (self.location as isize + proceed) 294 | .min(max_location as isize) 295 | .max(0) as usize; 296 | } 297 | } 298 | value 299 | }; 300 | 301 | self.channel = (self.channel + 1) % self.channels(); 302 | Some(value) 303 | } 304 | } 305 | } 306 | 307 | impl Source for BufferPlayer 308 | where 309 | S: Sample, 310 | { 311 | #[inline] 312 | fn current_frame_len(&self) -> Option { 313 | None 314 | } 315 | 316 | #[inline] 317 | fn channels(&self) -> u16 { 318 | self.buffer.channels 319 | } 320 | 321 | #[inline] 322 | fn sample_rate(&self) -> u32 { 323 | self.buffer.sample_rate 324 | } 325 | 326 | #[inline] 327 | fn total_duration(&self) -> Option { 328 | None 329 | } 330 | } 331 | 332 | pub struct AudioController { 333 | sink: rodio::Sink, 334 | changed_target_buffer: Arc>>>>, 335 | target_buffer: Arc>, 336 | changed_time: Arc>>, 337 | time: Arc>, 338 | speed: Arc>, 339 | loop_mode: Arc>, 340 | } 341 | impl AudioController 342 | where 343 | S: Sample + Send + Sync + 'static, 344 | { 345 | fn new(sink: rodio::Sink, buffer: Arc>) -> Self { 346 | sink.set_volume(0.25); 347 | Self { 348 | sink, 349 | changed_target_buffer: Arc::new(RwLock::new(None)), 350 | target_buffer: Arc::clone(&buffer), 351 | changed_time: Arc::new(RwLock::new(None)), 352 | time: Arc::new(RwLock::new(0.0)), 353 | speed: Arc::new(RwLock::new(1.0)), 354 | loop_mode: Arc::new(RwLock::new(false)), 355 | } 356 | } 357 | 358 | pub fn new_with_buffer( 359 | audio_device: &OutputStreamHandle, 360 | buffer: Arc>, 361 | ) -> Self { 362 | let sink = rodio::Sink::try_new(&audio_device).unwrap(); 363 | let controller = Self::new(sink, Arc::clone(&buffer)); 364 | let (target_buffer2, changed_time2, time2, speed2, loop_mode2) = ( 365 | Arc::clone(&controller.changed_target_buffer), 366 | Arc::clone(&controller.changed_time), 367 | Arc::clone(&controller.time), 368 | Arc::clone(&controller.speed), 369 | Arc::clone(&controller.loop_mode), 370 | ); 371 | let source = BufferPlayer::new(buffer).periodic_access( 372 | std::time::Duration::from_secs_f32(0.001), 373 | move |player| { 374 | { 375 | let mut target_buffer = target_buffer2.write().unwrap(); 376 | if let Some(buffer) = target_buffer.take() { 377 | player.set_buffer(buffer); 378 | } 379 | } 380 | { 381 | let mut changed_time = changed_time2.write().unwrap(); 382 | if let Some(time) = changed_time.take() { 383 | player.set_time(std::time::Duration::from_secs_f32(time.max(0.0))); 384 | } 385 | } 386 | { 387 | let mut time = time2.write().unwrap(); 388 | *time = player.get_time().as_secs_f32(); 389 | } 390 | { 391 | let speed = speed2.read().unwrap(); 392 | player.set_speed(*speed); 393 | } 394 | { 395 | let loop_mode = loop_mode2.read().unwrap(); 396 | player.set_loop_mode(*loop_mode); 397 | } 398 | }, 399 | ); 400 | controller.sink.append(source); 401 | controller 402 | } 403 | 404 | pub fn get_volume(&self) -> f32 { 405 | self.sink.volume() 406 | } 407 | 408 | pub fn set_volume(&self, volume: f32) { 409 | self.sink.set_volume(volume); 410 | } 411 | 412 | pub fn set_target_buffer(&mut self, buffer: Arc>) { 413 | self.target_buffer = Arc::clone(&buffer); 414 | { 415 | let mut target_buffer = self.changed_target_buffer.write().unwrap(); 416 | *target_buffer = Some(buffer); 417 | } 418 | } 419 | 420 | pub fn get_target_buffer(&self) -> &Arc> { 421 | &self.target_buffer 422 | } 423 | 424 | pub fn get_speed(&self) -> f32 { 425 | *self.speed.read().unwrap() 426 | } 427 | 428 | pub fn set_speed(&self, speed: f32) { 429 | let mut dst_speed = self.speed.write().unwrap(); 430 | *dst_speed = speed; 431 | } 432 | 433 | pub fn get_time(&self) -> f32 { 434 | *self.time.read().unwrap() 435 | } 436 | 437 | pub fn change_time(&self, time: f32) { 438 | *self.changed_time.write().unwrap() = Some(time); 439 | *self.time.write().unwrap() = time; 440 | } 441 | 442 | pub fn get_loop_mode(&self) -> bool { 443 | *self.loop_mode.read().unwrap() 444 | } 445 | 446 | pub fn set_loop_mode(&self, loop_mode: bool) { 447 | let mut dst = self.loop_mode.write().unwrap(); 448 | *dst = loop_mode; 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | 3 | use std::{path::PathBuf, sync::Arc}; 4 | 5 | pub mod entity; 6 | use entity::{ 7 | button::{ButtonColors, ButtonResponse, ButtonState, StateButton}, 8 | render::Transform, 9 | resource::{ 10 | audio::{AudioController, AudioLoader, AudioLoaderRes}, 11 | ButtonFunctions, ControlledSliders, Input, MusicFileMetaData, PlayingSpeed, Setting, 12 | SettingPath, 13 | }, 14 | slider::{Slider, SliderColors}, 15 | ButtonFn, TargetValue, 16 | }; 17 | 18 | pub mod buffer_player; 19 | use buffer_player::{AudioBufferLoader, SamplesBuffer}; 20 | 21 | pub mod renderer; 22 | use renderer::{PiplineSetting, Renderer}; 23 | 24 | mod icon; 25 | use icon::create_icon_data; 26 | 27 | use legion::{ 28 | query::{IntoQuery, Read, Write}, 29 | world::EntityStore, 30 | Entity, Resources, Schedule, SystemBuilder, World, 31 | }; 32 | // MARK: consts 33 | const FRAME_GAP: std::time::Duration = std::time::Duration::from_nanos(0_016_666_667); 34 | 35 | pub const NORMAL_BUTTON_COLOR: ButtonColors = ButtonColors { 36 | base_color: [0.0, 0.27, 0.5], 37 | hover_color: [0.6, 0.9, 1.0], 38 | press_color: [0.0, 0.1, 0.2], 39 | }; 40 | pub const SLIDER_COLOR: ButtonColors = ButtonColors { 41 | base_color: [0.8, 0.5, 0.0], 42 | hover_color: [0.9, 0.8, 0.5], 43 | press_color: [0.7, 0.4, 0.0], 44 | }; 45 | pub const LOOP_BUTTON_COLOR: ButtonColors = ButtonColors { 46 | base_color: [0.8, 0.5, 0.0], 47 | hover_color: [0.9, 0.8, 0.5], 48 | press_color: [0.7, 0.4, 0.0], 49 | }; 50 | pub const LOADING_BUTTON_COLOR: ButtonColors = ButtonColors { 51 | base_color: [0.0, 0.05, 0.1], 52 | hover_color: [0.0, 0.04, 0.15], 53 | press_color: [0.0, 0.03, 0.05], 54 | }; 55 | 56 | // MARK: main 57 | fn main() -> Result<(), String> { 58 | env_logger::builder() 59 | .filter_level(log::LevelFilter::Error) 60 | .filter_module("yee_player", log::LevelFilter::Trace) 61 | .init(); 62 | 63 | // use another thread to create the OutputStream of rodio. avoid winit conflict. 64 | let (sender, receiver) = std::sync::mpsc::channel(); 65 | let (sender_end, receiver_end) = std::sync::mpsc::channel(); 66 | let _audio_stream_thread = std::thread::spawn(move || { 67 | let (_stream, stream_handle) = match rodio::OutputStream::try_default() { 68 | Err(e) => { 69 | let error_msg = format!("error getting default OutputStream: {:?}", e); 70 | sender.send(Err(error_msg)).unwrap(); 71 | return; 72 | } 73 | Ok(output) => output, 74 | }; 75 | sender.send(Ok(stream_handle)).unwrap(); 76 | drop(sender); 77 | receiver_end.recv().unwrap(); 78 | log::info!("audio_stream_thread end"); 79 | }); 80 | 81 | let stream_handle = receiver.recv().unwrap()?; 82 | drop(receiver); 83 | 84 | let shader_path: PathBuf = function::execute_or_relative_path("./asset/shader/shader.wgsl")?; 85 | let setting_path = 86 | SettingPath(function::execute_or_relative_path("./asset/setting/setting.ron").unwrap()); 87 | let setting = Setting::load(&setting_path.0).unwrap_or_default(); 88 | 89 | let event_loop = winit::event_loop::EventLoop::new(); 90 | 91 | let (window, size) = { 92 | let winit_window_builder = winit::window::WindowBuilder::new(); 93 | 94 | let window = winit_window_builder 95 | .with_title("yee player") 96 | .with_inner_size(winit::dpi::LogicalSize { 97 | width: setting.window_width, 98 | height: setting.window_height, 99 | }) 100 | .with_visible(false) 101 | .with_window_icon(Some( 102 | winit::window::Icon::from_rgba(create_icon_data(), 16, 16).unwrap(), 103 | )) 104 | .build(&event_loop) 105 | .map_err(|e| e.to_string())?; 106 | let center_position = { 107 | let window_size = window.outer_size(); 108 | let srceen_size = { 109 | if let Some(monitor) = window.current_monitor() { 110 | monitor.size() 111 | } else { 112 | window_size 113 | } 114 | }; 115 | winit::dpi::PhysicalPosition::new( 116 | (srceen_size.width - window_size.width) / 2, 117 | (srceen_size.height - window_size.height) / 2, 118 | ) 119 | }; 120 | window.set_outer_position(center_position); 121 | window.set_visible(true); 122 | 123 | let size = window.inner_size(); 124 | (window, size) 125 | }; 126 | 127 | let renderer = Renderer::init(&window, size)?; 128 | let render_pipeline = renderer.create_render_pipline(&PiplineSetting { shader_path })?; 129 | 130 | let (mut world, mut resources, mut schedule) = { 131 | let mut world = World::default(); 132 | 133 | let play_fn: ButtonFn = Arc::new( 134 | |_world: &mut World, res: &mut Resources, _self_entity: Entity| { 135 | let controller = res.get::>().unwrap(); 136 | let speed = controller.get_speed(); 137 | if speed == 0.0 { 138 | let value = res.get::().unwrap().0; 139 | controller.set_speed(value); 140 | } else { 141 | res.get_mut::().unwrap().0 = speed; 142 | controller.set_speed(0.0); 143 | } 144 | }, 145 | ); 146 | 147 | let loop_fn: ButtonFn = Arc::new( 148 | |world: &mut World, res: &mut Resources, self_entity: Entity| { 149 | let controller = res.get::>().unwrap(); 150 | controller.set_loop_mode(true); 151 | 152 | let unloop_fn: &ButtonFn = &res.get::().unwrap().unloop_fn; 153 | if let Some(mut entry) = world.entry(self_entity) { 154 | if let Ok(self_fn) = entry.get_component_mut::() { 155 | *self_fn = Arc::clone(unloop_fn); 156 | } 157 | if let Ok(colors) = entry.get_component_mut::() { 158 | *colors = LOOP_BUTTON_COLOR; 159 | } 160 | } 161 | }, 162 | ); 163 | 164 | let unloop_fn: ButtonFn = Arc::new( 165 | |world: &mut World, res: &mut Resources, self_entity: Entity| { 166 | let controller = res.get::>().unwrap(); 167 | controller.set_loop_mode(false); 168 | 169 | let loop_fn: &ButtonFn = &res.get::().unwrap().loop_fn; 170 | if let Some(mut entry) = world.entry(self_entity) { 171 | if let Ok(self_fn) = entry.get_component_mut::() { 172 | *self_fn = Arc::clone(loop_fn); 173 | } 174 | if let Ok(colors) = entry.get_component_mut::() { 175 | *colors = NORMAL_BUTTON_COLOR; 176 | } 177 | } 178 | }, 179 | ); 180 | 181 | let load_fn: ButtonFn = Arc::new( 182 | |world: &mut World, res: &mut Resources, self_entity: Entity| { 183 | let setting_path = res.get::().unwrap(); 184 | let mut setting = res.get_mut::().unwrap(); 185 | let new_setting = Setting::load(&setting_path.0).unwrap_or_default(); 186 | if new_setting.window_width != setting.window_width 187 | || new_setting.window_height != setting.window_height 188 | { 189 | let window = res.get_mut::().unwrap(); 190 | window.set_inner_size(winit::dpi::LogicalSize { 191 | width: new_setting.window_width, 192 | height: new_setting.window_height, 193 | }); 194 | setting.window_width = new_setting.window_width; 195 | setting.window_height = new_setting.window_height; 196 | } 197 | 198 | if new_setting.max_speed != setting.max_speed 199 | || new_setting.min_speed != setting.min_speed 200 | { 201 | let speed_slider_entity = res.get::().unwrap().speed_slider; 202 | if let Some(mut entry) = world.entry(speed_slider_entity) { 203 | let speed_slider = entry.get_component_mut::().unwrap(); 204 | speed_slider.set_range(new_setting.min_speed..new_setting.max_speed); 205 | } 206 | setting.max_speed = new_setting.max_speed; 207 | setting.min_speed = new_setting.min_speed; 208 | } 209 | 210 | let new_music_path = function::execute_or_relative_path(&new_setting.music_path); 211 | match new_music_path { 212 | Err(e) => log::error!("error getting music path: {}", e), 213 | Ok(path) => match std::fs::metadata(&path) { 214 | Err(e) => log::error!("error getting music metadata: {}", e), 215 | Ok(meta) => { 216 | let mut should_load = true; 217 | if let Some(old_meta) = res.get::().unwrap().as_ref() 218 | { 219 | if let (Ok(old_date), Ok(date)) = 220 | (old_meta.accessed(), meta.accessed()) 221 | { 222 | if old_date == date 223 | && setting.music_path == new_setting.music_path 224 | { 225 | should_load = false; 226 | } 227 | } 228 | } 229 | if should_load { 230 | function::load_music( 231 | world, 232 | res, 233 | self_entity, 234 | &new_setting.music_path, 235 | ); 236 | } 237 | } 238 | }, 239 | } 240 | }, 241 | ); 242 | 243 | let stop_load_fn: ButtonFn = Arc::new( 244 | |world: &mut World, res: &mut Resources, self_entity: Entity| { 245 | if let Some(loader) = res.get::().unwrap().as_ref() { 246 | loader.loader.stop_loading(); 247 | } 248 | 249 | let load_fn: &ButtonFn = &res.get::().unwrap().load_fn; 250 | if let Some(mut entry) = world.entry(self_entity) { 251 | if let Ok(self_fn) = entry.get_component_mut::() { 252 | *self_fn = Arc::clone(load_fn); 253 | } 254 | if let Ok(colors) = entry.get_component_mut::() { 255 | *colors = NORMAL_BUTTON_COLOR; 256 | } 257 | } 258 | }, 259 | ) as ButtonFn; 260 | 261 | // MARK: entity 262 | world.extend(vec![ 263 | ( 264 | StateButton::new(), 265 | NORMAL_BUTTON_COLOR, 266 | Transform { 267 | location: [-1.0, 0.5], 268 | size: [0.5, 0.5], 269 | color: NORMAL_BUTTON_COLOR.base_color, 270 | }, 271 | Arc::clone(&play_fn), 272 | ), 273 | ( 274 | StateButton::new(), 275 | NORMAL_BUTTON_COLOR, 276 | Transform { 277 | location: [-0.5, 0.5], 278 | size: [0.5, 0.5], 279 | color: NORMAL_BUTTON_COLOR.base_color, 280 | }, 281 | Arc::new( 282 | |_world: &mut World, res: &mut Resources, _self_entity: Entity| { 283 | let controller = res.get::>().unwrap(); 284 | controller.set_speed(-controller.get_speed()); 285 | }, 286 | ), 287 | ), 288 | ( 289 | StateButton::new(), 290 | NORMAL_BUTTON_COLOR, 291 | Transform { 292 | location: [0.0, 0.5], 293 | size: [0.5, 0.5], 294 | color: NORMAL_BUTTON_COLOR.base_color, 295 | }, 296 | Arc::clone(&loop_fn), 297 | ), 298 | ]); 299 | let load_button_entity = world.push(( 300 | StateButton::new(), 301 | Slider::new(0.0, 0.0..1.0), 302 | TargetValue(0.0), 303 | LOADING_BUTTON_COLOR, 304 | SliderColors { 305 | current_color: NORMAL_BUTTON_COLOR.base_color, 306 | state_colors: NORMAL_BUTTON_COLOR, 307 | }, 308 | Transform { 309 | location: [0.5, 0.5], 310 | size: [0.5, 0.5], 311 | color: LOADING_BUTTON_COLOR.base_color, 312 | }, 313 | Arc::clone(&stop_load_fn), 314 | )); 315 | 316 | let slider_entities = world.extend(vec![ 317 | ( 318 | StateButton::new(), 319 | Slider::new(0.0, 0.0..1.0), 320 | NORMAL_BUTTON_COLOR, 321 | SliderColors { 322 | current_color: SLIDER_COLOR.base_color, 323 | state_colors: SLIDER_COLOR, 324 | }, 325 | Transform { 326 | location: [-1.0, 0.0], 327 | size: [2.0, 0.5], 328 | color: NORMAL_BUTTON_COLOR.base_color, 329 | }, 330 | ), 331 | ( 332 | StateButton::new(), 333 | Slider::new(1.0, setting.min_speed..setting.max_speed), 334 | NORMAL_BUTTON_COLOR, 335 | SliderColors { 336 | current_color: SLIDER_COLOR.base_color, 337 | state_colors: SLIDER_COLOR, 338 | }, 339 | Transform { 340 | location: [-1.0, -0.5], 341 | size: [2.0, 0.5], 342 | color: NORMAL_BUTTON_COLOR.base_color, 343 | }, 344 | ), 345 | ( 346 | StateButton::new(), 347 | Slider::new(0.25, 0.0..1.0), 348 | NORMAL_BUTTON_COLOR, 349 | SliderColors { 350 | current_color: SLIDER_COLOR.base_color, 351 | state_colors: SLIDER_COLOR, 352 | }, 353 | Transform { 354 | location: [-1.0, -1.0], 355 | size: [2.0, 0.5], 356 | color: NORMAL_BUTTON_COLOR.base_color, 357 | }, 358 | ), 359 | ]); 360 | 361 | // Resources 362 | let mut resources = Resources::default(); 363 | resources.insert(Input::default()); 364 | let empty_buffer = Arc::new(SamplesBuffer::new(1, 48000, Vec::::new())); 365 | resources.insert(Arc::clone(&empty_buffer)); 366 | resources.insert(window); 367 | resources.insert(renderer); 368 | resources.insert(ButtonFunctions { 369 | play_fn, 370 | loop_fn, 371 | unloop_fn, 372 | load_fn, 373 | stop_load_fn, 374 | }); 375 | // setting 376 | resources.insert(setting_path); 377 | resources.insert(setting); 378 | resources.insert::(None); 379 | // controller 380 | resources.insert(AudioController::new_with_buffer( 381 | &stream_handle, 382 | empty_buffer, 383 | )); 384 | let controlled_sliders = ControlledSliders { 385 | time_slider: slider_entities[0], 386 | speed_slider: slider_entities[1], 387 | volume_slider: slider_entities[2], 388 | }; 389 | resources.insert(controlled_sliders); 390 | resources.insert(PlayingSpeed(1.0)); 391 | // buffer loader 392 | resources.insert::(None); 393 | 394 | { 395 | // command line support 396 | let args: Vec = std::env::args().collect(); 397 | if let Some(path) = args.get(1) { 398 | function::load_music(&mut world, &resources, load_button_entity, path); 399 | } else { 400 | let setting = resources.get::().unwrap(); 401 | function::load_music( 402 | &mut world, 403 | &resources, 404 | load_button_entity, 405 | &setting.music_path, 406 | ); 407 | } 408 | } 409 | 410 | // MARK: systems 411 | let update_button_and_slider_color = SystemBuilder::new("update_button_and_slider_color") 412 | .with_query(<(Write, Read)>::query()) 413 | .with_query(<(Read, Read, Write)>::query()) 414 | .build(|_, world, _, (slider_query, button_query)| { 415 | button_query.for_each_mut(world, |(button, colors, mut transform)| { 416 | match button.get_state() { 417 | ButtonState::Unhover => { 418 | let speed = 0.17; 419 | let r = smooth_to(transform.color[0], colors.base_color[0], speed); 420 | let g = smooth_to(transform.color[1], colors.base_color[1], speed); 421 | let b = smooth_to(transform.color[2], colors.base_color[2], speed); 422 | transform.color = [r, g, b]; 423 | } 424 | ButtonState::Hover => { 425 | let speed = 0.2; 426 | let r = smooth_to(transform.color[0], colors.hover_color[0], speed); 427 | let g = smooth_to(transform.color[1], colors.hover_color[1], speed); 428 | let b = smooth_to(transform.color[2], colors.hover_color[2], speed); 429 | transform.color = [r, g, b]; 430 | } 431 | ButtonState::Press => { 432 | let speed = 0.6; 433 | let r = smooth_to(transform.color[0], colors.press_color[0], speed); 434 | let g = smooth_to(transform.color[1], colors.press_color[1], speed); 435 | let b = smooth_to(transform.color[2], colors.press_color[2], speed); 436 | transform.color = [r, g, b]; 437 | } 438 | } 439 | }); 440 | 441 | slider_query.for_each_mut(world, |(mut color, button)| match button.get_state() { 442 | ButtonState::Unhover => { 443 | let speed = 0.17; 444 | let r = smooth_to( 445 | color.current_color[0], 446 | color.state_colors.base_color[0], 447 | speed, 448 | ); 449 | let g = smooth_to( 450 | color.current_color[1], 451 | color.state_colors.base_color[1], 452 | speed, 453 | ); 454 | let b = smooth_to( 455 | color.current_color[2], 456 | color.state_colors.base_color[2], 457 | speed, 458 | ); 459 | color.current_color = [r, g, b]; 460 | } 461 | ButtonState::Hover => { 462 | let speed = 0.2; 463 | let r = smooth_to( 464 | color.current_color[0], 465 | color.state_colors.hover_color[0], 466 | speed, 467 | ); 468 | let g = smooth_to( 469 | color.current_color[1], 470 | color.state_colors.hover_color[1], 471 | speed, 472 | ); 473 | let b = smooth_to( 474 | color.current_color[2], 475 | color.state_colors.hover_color[2], 476 | speed, 477 | ); 478 | color.current_color = [r, g, b]; 479 | } 480 | ButtonState::Press => { 481 | let speed = 0.6; 482 | let r = smooth_to( 483 | color.current_color[0], 484 | color.state_colors.press_color[0], 485 | speed, 486 | ); 487 | let g = smooth_to( 488 | color.current_color[1], 489 | color.state_colors.press_color[1], 490 | speed, 491 | ); 492 | let b = smooth_to( 493 | color.current_color[2], 494 | color.state_colors.press_color[2], 495 | speed, 496 | ); 497 | color.current_color = [r, g, b]; 498 | } 499 | }); 500 | }); 501 | 502 | let check_loader = SystemBuilder::new("check_loader") 503 | .write_component::() 504 | .write_component::() 505 | .write_component::() 506 | .write_component::() 507 | .read_resource::() 508 | .write_resource::>>() 509 | .write_resource::>() 510 | .read_resource::() 511 | .write_resource::() 512 | .write_resource::() 513 | .write_resource::() 514 | .build( 515 | |_, 516 | world, 517 | (funcs, audio_buffer, controller, sliders, setting, meta_data, loader), 518 | _| { 519 | // query.for_each_mut(&mut query_world, |(entity, buffer_loader, caller)| { 520 | let mut audio_buffer_loaded = false; 521 | let mut drop_loader = false; 522 | if let Some(loader) = loader.as_mut() { 523 | if let Some(value) = loader.loader.try_get_value() { 524 | drop_loader = true; 525 | let value = match value { 526 | Err(e) => { 527 | log::error!("error loading audio: {}", e); 528 | 0.0 529 | } 530 | Ok(value) => { 531 | log::info!( 532 | "load success, audio length: {}s", 533 | value.get_duration().as_secs_f32() 534 | ); 535 | **audio_buffer = Arc::new(value); 536 | audio_buffer_loaded = true; 537 | setting.music_path = loader.path.clone(); 538 | **meta_data = std::fs::metadata(&setting.music_path).ok(); 539 | 1.0 540 | } 541 | }; 542 | // load_button visual stuff 543 | if let Ok(mut entry) = world.entry_mut(loader.load_button_entity) { 544 | if let Ok(target_value) = entry.get_component_mut::() { 545 | target_value.0 = value; 546 | } 547 | if let Ok(colors) = entry.get_component_mut::() { 548 | *colors = NORMAL_BUTTON_COLOR; 549 | } 550 | if let Ok(caller_fn) = entry.get_component_mut::() { 551 | *caller_fn = Arc::clone(&funcs.load_fn); 552 | } 553 | } 554 | } else if let Ok(mut entry) = world.entry_mut(loader.load_button_entity) { 555 | if let Ok(target_value) = entry.get_component_mut::() { 556 | target_value.0 = loader.loader.get_progress(); 557 | } 558 | } 559 | } 560 | if drop_loader { 561 | **loader = None; 562 | } 563 | if audio_buffer_loaded { 564 | if !Arc::ptr_eq(controller.get_target_buffer(), audio_buffer) { 565 | controller.set_target_buffer(Arc::clone(audio_buffer)); 566 | let buffer_duartion = 567 | controller.get_target_buffer().get_duration().as_secs_f32(); 568 | 569 | if let Ok(mut entry) = world.entry_mut(sliders.time_slider) { 570 | if let Ok(time_slider) = entry.get_component_mut::() { 571 | time_slider.set_range(0.0..buffer_duartion); 572 | } 573 | } 574 | 575 | if controller.get_speed() < 0.0 { 576 | controller.change_time(buffer_duartion); 577 | } else { 578 | controller.change_time(0.0); 579 | } 580 | } 581 | } 582 | }, 583 | ); 584 | 585 | let update_button = SystemBuilder::new("update_button") 586 | .read_resource::() 587 | .with_query(<(Write, Read)>::query()) 588 | .build(|_commands, world, input, query| { 589 | query.for_each_mut(world, |(button, transform)| { 590 | let hover = if let Some(location) = &input.mouse_location { 591 | is_in_box(&transform, location) 592 | } else { 593 | false 594 | }; 595 | button.update_with_input(hover, input.mouse_pressing); 596 | }); 597 | }); 598 | 599 | let update_slider = SystemBuilder::new("update_slider") 600 | .read_resource::() 601 | .with_query(<(Write, Read, Read)>::query()) 602 | .build(|_commands, world, resource, query| { 603 | let mouse_location = &resource.mouse_location; 604 | query.for_each_mut(world, |(slider, button, transform)| { 605 | match button.get_state() { 606 | ButtonState::Unhover => {} 607 | ButtonState::Hover => {} 608 | ButtonState::Press => { 609 | if let Some(location) = mouse_location { 610 | let value = if transform.size[0].is_normal() { 611 | let (x, _) = relative_to_box(&transform, location); 612 | let v = slider.map_value_back(x / transform.size[0]); 613 | if resource.ctrl_pressing { 614 | (v * 20.0).round() / 20.0 615 | } else { 616 | v 617 | } 618 | } else { 619 | slider.map_value(0.0) 620 | }; 621 | slider.input_value(value); 622 | } 623 | } 624 | } 625 | }); 626 | }); 627 | let update_slider_with_target_value = SystemBuilder::new("update_slider_with_target_value") 628 | .with_query(<(Write, Read)>::query()) 629 | .build(|_, world, _, query| { 630 | for (slider, value) in query.iter_mut(world) { 631 | let value = smooth_to(slider.get_value(), value.0, 0.4); 632 | slider.set_value(value); 633 | } 634 | }); 635 | 636 | let update_controller = SystemBuilder::new("update_controller") 637 | .read_component::() 638 | .write_component::() 639 | .write_resource::>() 640 | .read_resource::() 641 | .build(|_, world, (controller, sliders), _| { 642 | if let Ok(mut entry) = world.entry_mut(sliders.time_slider) { 643 | if let Ok(time_slider) = entry.get_component_mut::() { 644 | if let Some(v) = time_slider.take_input_value() { 645 | controller.change_time(v); 646 | } 647 | time_slider.set_value(controller.get_time()); 648 | } 649 | } 650 | 651 | if let Ok(mut entry) = world.entry_mut(sliders.speed_slider) { 652 | if let Ok(speed_slider) = entry.get_component_mut::() { 653 | if let Some(v) = speed_slider.take_input_value() { 654 | controller.set_speed(v); 655 | } 656 | speed_slider.set_value(controller.get_speed()); 657 | } 658 | } 659 | 660 | if let Ok(mut entry) = world.entry_mut(sliders.volume_slider) { 661 | if let Ok(volume_slider) = entry.get_component_mut::() { 662 | if let Some(v) = volume_slider.take_input_value() { 663 | controller.set_volume(v); 664 | } 665 | volume_slider.set_value(controller.get_volume()); 666 | } 667 | } 668 | }); 669 | 670 | let check_file_hover = SystemBuilder::new("check_file_hover") 671 | .write_component::() 672 | .write_component::() 673 | .write_component::() 674 | .write_component::() 675 | .write_component::() 676 | .write_resource::() 677 | .read_resource::() 678 | .write_resource::() 679 | .build(move |_, world, (input, funcs, loader), _| { 680 | // when there is no AudioBufferLoader exist, load the dropped file 681 | if loader.is_none() { 682 | if let Ok(mut entry) = world.entry_mut(load_button_entity) { 683 | // highlight the load_button when hovering file 684 | if input.hover_file { 685 | if let Ok(button) = entry.get_component_mut::() { 686 | button.update_with_input(true, false); 687 | } 688 | } 689 | if let Some(path) = input.drop_file.take() { 690 | match path.into_os_string().into_string() { 691 | Err(e) => { 692 | log::error!("dropped file has invalid path: {:?}", e); 693 | } 694 | Ok(path) => { 695 | log::info!("loading {:?}", path); 696 | **loader = Some(AudioLoader { 697 | loader: AudioBufferLoader::load(path.clone()), 698 | path: path.clone(), 699 | load_button_entity, 700 | }); 701 | 702 | if let Ok(self_fn) = entry.get_component_mut::() { 703 | *self_fn = Arc::clone(&funcs.stop_load_fn); 704 | } 705 | 706 | if let Ok(colors) = entry.get_component_mut::() { 707 | *colors = LOADING_BUTTON_COLOR; 708 | } 709 | 710 | if let Ok(slider) = entry.get_component_mut::() { 711 | slider.set_value(0.0); 712 | } 713 | 714 | if let Ok(mut target_value) = 715 | entry.get_component_mut::() 716 | { 717 | target_value.0 = 0.0; 718 | } 719 | } 720 | } 721 | } 722 | } 723 | } else { 724 | input.drop_file.take(); 725 | } 726 | }); 727 | 728 | let execute_button = Box::new(|world: &mut World, res: &mut Resources| { 729 | let mut query = <(Entity, Read, Read)>::query(); 730 | let mut funcs_entities = Vec::new(); 731 | // collect funcs to call 732 | for (entity, button, func) in query.iter(world) { 733 | if let Some(response) = button.get_response() { 734 | match response { 735 | ButtonResponse::Hover => {} 736 | ButtonResponse::Unhover => {} 737 | ButtonResponse::Press => {} 738 | ButtonResponse::Release => { 739 | funcs_entities.push((Arc::clone(&func), *entity)); 740 | } 741 | } 742 | } 743 | } 744 | for (func, entity) in funcs_entities { 745 | func(world, res, entity); 746 | } 747 | }); 748 | let schedule = Schedule::builder() 749 | .add_system(update_button_and_slider_color) 750 | .add_system(check_loader) 751 | .add_system(update_button) 752 | .add_system(update_slider) 753 | .add_system(update_slider_with_target_value) 754 | .add_system(update_controller) 755 | .add_system(check_file_hover) 756 | .flush() 757 | .add_thread_local_fn(execute_button) 758 | .build(); 759 | (world, resources, schedule) 760 | }; 761 | 762 | // MARK: run 763 | let mut next_time = std::time::Instant::now(); 764 | let mut should_tick = false; 765 | event_loop.run(move |event, _, control_flow| { 766 | use winit::{ 767 | event::{Event, KeyboardInput, StartCause, WindowEvent}, 768 | event_loop::ControlFlow, 769 | }; 770 | *control_flow = ControlFlow::Poll; 771 | 772 | match event { 773 | Event::NewEvents(StartCause::ResumeTimeReached { .. }) => { 774 | let now = std::time::Instant::now(); 775 | should_tick = true; 776 | next_time += FRAME_GAP; 777 | while now > next_time + FRAME_GAP { 778 | next_time += FRAME_GAP; 779 | } 780 | } 781 | // MouseInput 782 | Event::WindowEvent { 783 | window_id, 784 | event: WindowEvent::CursorMoved { position, .. }, 785 | } if window_id == resources.get::().unwrap().id() => { 786 | let inner_size = resources 787 | .get::() 788 | .unwrap() 789 | .inner_size(); 790 | let x = (position.x as f32 / inner_size.width as f32) * 2.0 - 1.0; 791 | let y = 1.0 - (position.y as f32 / inner_size.height as f32) * 2.0; 792 | resources.get_mut::().unwrap().mouse_location = Some((x, y)); 793 | } 794 | Event::WindowEvent { 795 | window_id, 796 | event: WindowEvent::CursorLeft { .. }, 797 | } if window_id == resources.get::().unwrap().id() => { 798 | resources.get_mut::().unwrap().mouse_location = None; 799 | } 800 | Event::WindowEvent { 801 | window_id, 802 | event: 803 | WindowEvent::MouseInput { 804 | state, 805 | button: winit::event::MouseButton::Left, 806 | .. 807 | }, 808 | } if window_id == resources.get::().unwrap().id() => { 809 | let mouse_pressing = &mut resources.get_mut::().unwrap().mouse_pressing; 810 | match state { 811 | winit::event::ElementState::Pressed => *mouse_pressing = true, 812 | winit::event::ElementState::Released => *mouse_pressing = false, 813 | } 814 | } 815 | // KeyboardInput 816 | Event::WindowEvent { 817 | window_id, 818 | event: 819 | WindowEvent::KeyboardInput { 820 | input: 821 | KeyboardInput { 822 | state, 823 | virtual_keycode: Some(keycode), 824 | .. 825 | }, 826 | .. 827 | }, 828 | } if window_id == resources.get::().unwrap().id() => { 829 | use winit::event::VirtualKeyCode; 830 | match keycode { 831 | VirtualKeyCode::Escape => { 832 | *control_flow = ControlFlow::Exit; 833 | } 834 | VirtualKeyCode::LControl | VirtualKeyCode::RControl => { 835 | let ctrl_pressing = 836 | &mut resources.get_mut::().unwrap().ctrl_pressing; 837 | match state { 838 | winit::event::ElementState::Pressed => *ctrl_pressing = true, 839 | winit::event::ElementState::Released => *ctrl_pressing = false, 840 | } 841 | } 842 | _ => {} 843 | } 844 | } 845 | Event::WindowEvent { 846 | window_id, 847 | event: WindowEvent::HoveredFile(_), 848 | } if window_id == resources.get::().unwrap().id() => { 849 | resources.get_mut::().unwrap().hover_file = true; 850 | } 851 | Event::WindowEvent { 852 | window_id, 853 | event: WindowEvent::HoveredFileCancelled, 854 | } if window_id == resources.get::().unwrap().id() => { 855 | resources.get_mut::().unwrap().hover_file = false; 856 | } 857 | Event::WindowEvent { 858 | window_id, 859 | event: WindowEvent::DroppedFile(path), 860 | } if window_id == resources.get::().unwrap().id() => { 861 | let mut input = resources.get_mut::().unwrap(); 862 | input.drop_file = Some(path); 863 | input.hover_file = false; 864 | } 865 | Event::WindowEvent { 866 | window_id, 867 | event: WindowEvent::Resized(size), 868 | } if window_id == resources.get::().unwrap().id() => { 869 | if size.width & size.height != 0 { 870 | resources.get_mut::().unwrap().resize(size); 871 | } 872 | } 873 | Event::MainEventsCleared if should_tick => { 874 | schedule.execute(&mut world, &mut resources); 875 | if resources.get::().unwrap().exit { 876 | *control_flow = ControlFlow::Exit; 877 | } else { 878 | resources 879 | .get::() 880 | .unwrap() 881 | .request_redraw(); 882 | } 883 | } 884 | Event::RedrawRequested(window_id) 885 | if window_id == resources.get::().unwrap().id() => 886 | { 887 | let inner_size = resources 888 | .get::() 889 | .unwrap() 890 | .inner_size(); 891 | if inner_size.width & inner_size.height != 0 { 892 | let mut transforms = Vec::new(); 893 | { 894 | for (_, transform) in 895 | <(Read, Read)>::query().iter(&world) 896 | { 897 | transforms.push(*transform); 898 | } 899 | for (slider, slider_colors, transform) in 900 | <(Read, Read, Read)>::query() 901 | .iter(&world) 902 | { 903 | let progress = Transform { 904 | location: [transform.location[0], transform.location[1]], 905 | size: [ 906 | transform.size[0] * slider.get_value_mapped(), 907 | transform.size[1], 908 | ], 909 | color: slider_colors.current_color, 910 | }; 911 | transforms.push(progress); 912 | } 913 | } 914 | if let Err(e) = resources 915 | .get_mut::() 916 | .unwrap() 917 | .render(&transforms, &render_pipeline) 918 | { 919 | log::error!("render error: {}", e); 920 | } 921 | } 922 | } 923 | Event::RedrawEventsCleared => { 924 | *control_flow = ControlFlow::WaitUntil(next_time); 925 | if should_tick { 926 | should_tick = false; 927 | } 928 | } 929 | Event::WindowEvent { 930 | event: WindowEvent::CloseRequested, 931 | .. 932 | } => *control_flow = ControlFlow::Exit, 933 | Event::LoopDestroyed => { 934 | sender_end.send(()).unwrap(); 935 | log::info!("exit"); 936 | } 937 | _ => {} 938 | } 939 | }); 940 | } 941 | 942 | fn is_in_box(transform: &Transform, position: &(f32, f32)) -> bool { 943 | let left = transform.location[0]; 944 | let right = transform.location[0] + transform.size[0]; 945 | let bottom = transform.location[1]; 946 | let top = transform.location[1] + transform.size[1]; 947 | position.0 > left && position.0 < right && position.1 > bottom && position.1 < top 948 | } 949 | 950 | fn relative_to_box(transform: &Transform, position: &(f32, f32)) -> (f32, f32) { 951 | let left = transform.location[0]; 952 | let top = transform.location[1]; 953 | (position.0 - left, position.1 - top) 954 | } 955 | 956 | fn smooth_to(current_value: f32, target_value: f32, change_speed: f32) -> f32 { 957 | current_value + (target_value - current_value) * change_speed 958 | } 959 | 960 | mod function { 961 | use super::LOADING_BUTTON_COLOR; 962 | use legion::{Entity, Resources, World}; 963 | use std::{path::PathBuf, str::FromStr}; 964 | 965 | use super::entity::{ 966 | button::ButtonColors, 967 | resource::{audio::AudioBufferLoader, ButtonFunctions}, 968 | slider::Slider, 969 | ButtonFn, TargetValue, 970 | }; 971 | use crate::entity::resource::audio::{AudioLoader, AudioLoaderRes}; 972 | 973 | pub fn execute_or_relative_path(path: &str) -> Result { 974 | if let Ok(relative_path) = PathBuf::from_str(path) { 975 | if relative_path.is_absolute() { 976 | return Ok(relative_path); 977 | } 978 | if let Ok(exe) = std::env::current_exe() { 979 | if let Some(exe_path) = exe.parent() { 980 | let exe_path = exe_path.join(relative_path.clone()); 981 | if exe_path.exists() { 982 | return Ok(exe_path); 983 | } 984 | } 985 | } 986 | Ok(relative_path) 987 | } else { 988 | Err("not a valid path".to_string()) 989 | } 990 | } 991 | pub fn load_music( 992 | world: &mut World, 993 | res: &Resources, 994 | load_button_entity: Entity, 995 | path: &String, 996 | ) { 997 | let path_buf = execute_or_relative_path(path); 998 | match path_buf { 999 | Err(e) => { 1000 | log::error!("error on getting path {}", e); 1001 | } 1002 | Ok(path_buf) => { 1003 | log::info!("loading {:?}", path_buf); 1004 | let mut loader = res.get_mut::().unwrap(); 1005 | *loader = Some(AudioLoader { 1006 | loader: AudioBufferLoader::load(path_buf), 1007 | path: path.clone(), 1008 | load_button_entity, 1009 | }); 1010 | let stop_load_fn = &res.get::().unwrap().stop_load_fn; 1011 | if let Some(mut entry) = world.entry(load_button_entity) { 1012 | if let Ok(self_fn) = entry.get_component_mut::() { 1013 | *self_fn = std::sync::Arc::clone(stop_load_fn); 1014 | } 1015 | 1016 | if let Ok(colors) = entry.get_component_mut::() { 1017 | *colors = LOADING_BUTTON_COLOR; 1018 | } 1019 | 1020 | if let Ok(slider) = entry.get_component_mut::() { 1021 | slider.set_value(0.0); 1022 | } 1023 | 1024 | if let Ok(mut target_value) = entry.get_component_mut::() { 1025 | target_value.0 = 0.0; 1026 | } 1027 | } 1028 | } 1029 | } 1030 | } 1031 | } 1032 | --------------------------------------------------------------------------------