diff --git a/Cargo.lock b/Cargo.lock index 41da58d..284923f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -714,20 +714,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crossbeam" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - [[package]] name = "crossbeam-channel" version = "0.5.2" @@ -762,16 +748,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b979d76c9fcb84dffc80a73f7290da0f83e4c95773494674cb44b76d13a7a110" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.6" @@ -996,13 +972,16 @@ dependencies = [ "anyhow", "bass-sys", "framework", + "gfx_core", + "gfx_device_gl", "ggez", "image", "imgui", + "imgui-gfx-renderer", "imgui-winit-support", "libosu", "log", - "num 0.4.0", + "num", "ordered-float 2.10.0", "stderrlog", "structopt", @@ -1014,16 +993,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "log", - "regex", -] - [[package]] name = "error-chain" version = "0.12.4" @@ -1611,6 +1580,17 @@ dependencies = [ "parking_lot 0.11.2", ] +[[package]] +name = "imgui-gfx-renderer" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a61820483efd1d881b5bbc205def957bb2c148d9daf956ae7029f3399c03eab" +dependencies = [ + "gfx", + "imgui", + "winapi 0.3.9", +] + [[package]] name = "imgui-sys" version = "0.8.2" @@ -1628,7 +1608,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d8cb426a6ee9f1be8e379b18b9085585887056fcfbc2d66bddb2c355944624" dependencies = [ "imgui", - "winit 0.25.0", + "winit 0.26.1", ] [[package]] @@ -1816,23 +1796,18 @@ dependencies = [ [[package]] name = "libosu" -version = "0.0.16" -source = "git+https://github.com/iptq/libosu?rev=81677d5ed88936c4a3e64af951ff0ae523c2d403#81677d5ed88936c4a3e64af951ff0ae523c2d403" +version = "0.0.26" dependencies = [ "bitflags", + "byteorder", "derive_more", "lazy_static", "log", - "num 0.3.1", + "num", "num-derive", - "num-rational 0.3.2", "num-traits", "ordered-float 2.10.0", - "quickcheck", - "quickcheck_macros", "regex", - "serde", - "serde_json", "thiserror", ] @@ -2113,19 +2088,6 @@ dependencies = [ "winapi 0.2.8", ] -[[package]] -name = "mio" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" -dependencies = [ - "libc", - "log", - "miow 0.3.7", - "ntapi", - "winapi 0.3.9", -] - [[package]] name = "mio" version = "0.8.0" @@ -2151,18 +2113,6 @@ dependencies = [ "slab", ] -[[package]] -name = "mio-misc" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47412f3a52115b936ff2a229b803498c7b4d332adeb87c2f1498c9da54c398c" -dependencies = [ - "crossbeam", - "crossbeam-queue", - "log", - "mio 0.7.14", -] - [[package]] name = "miow" version = "0.2.2" @@ -2430,45 +2380,20 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "num" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" -dependencies = [ - "num-bigint 0.3.3", - "num-complex 0.3.1", - "num-integer", - "num-iter", - "num-rational 0.3.2", - "num-traits", -] - [[package]] name = "num" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ - "num-bigint 0.4.3", - "num-complex 0.4.0", + "num-bigint", + "num-complex", "num-integer", "num-iter", "num-rational 0.4.0", "num-traits", ] -[[package]] -name = "num-bigint" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -2480,15 +2405,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-complex" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" -dependencies = [ - "num-traits", -] - [[package]] name = "num-complex" version = "0.4.0" @@ -2537,7 +2453,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ "autocfg", - "num-bigint 0.3.3", "num-integer", "num-traits", ] @@ -2549,7 +2464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ "autocfg", - "num-bigint 0.4.3", + "num-bigint", "num-integer", "num-traits", ] @@ -2729,6 +2644,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" dependencies = [ "num-traits", + "serde", ] [[package]] @@ -2975,28 +2891,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "quickcheck" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" -dependencies = [ - "env_logger", - "log", - "rand", -] - -[[package]] -name = "quickcheck_macros" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" -dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.14", - "syn", -] - [[package]] name = "quote" version = "0.6.13" @@ -4292,38 +4186,6 @@ dependencies = [ "x11-dl", ] -[[package]] -name = "winit" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79610794594d5e86be473ef7763f604f2159cbac8c94debd00df8fb41e86c2f8" -dependencies = [ - "bitflags", - "cocoa 0.24.0", - "core-foundation 0.9.2", - "core-graphics 0.22.3", - "core-video-sys", - "dispatch", - "instant", - "lazy_static", - "libc", - "log", - "mio 0.7.14", - "mio-misc", - "ndk 0.3.0", - "ndk-glue 0.3.0", - "ndk-sys", - "objc", - "parking_lot 0.11.2", - "percent-encoding", - "raw-window-handle 0.3.4", - "scopeguard", - "smithay-client-toolkit 0.12.3", - "wayland-client 0.28.6", - "winapi 0.3.9", - "x11-dl", -] - [[package]] name = "winit" version = "0.26.1" diff --git a/Cargo.toml b/Cargo.toml index c69837a..770a554 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,12 +26,14 @@ ordered-float = "2.10.0" structopt = "0.3.25" image = "0.23.14" imgui = "0.8.2" -imgui-winit-support = "0.8.2" +imgui-winit-support = { version = "0.8.2", features = ["winit-26"], default-features = false } framework = { path = "framework" } +imgui-gfx-renderer = "0.8.2" +gfx_core = "0.9.2" +gfx_device_gl = "0.16.2" [dependencies.libosu] -git = "https://github.com/iptq/libosu" -rev = "81677d5ed88936c4a3e64af951ff0ae523c2d403" +path = "../libosu" [features] clippy = [] diff --git a/run.sh b/run.sh index 8ac11f8..e8bfac9 100755 --- a/run.sh +++ b/run.sh @@ -1,4 +1,4 @@ #!/bin/bash export LD_LIBRARY_PATH=$(pwd)/bass-sys/linux/bass24/x64 echo $LD_LIBRARY_PATH -exec cargo run --release "$@" +exec mold -run cargo run --release "$@" diff --git a/src/beatmap.rs b/src/beatmap.rs index ea19c72..cc32deb 100644 --- a/src/beatmap.rs +++ b/src/beatmap.rs @@ -62,12 +62,15 @@ impl BeatmapExt { break; } - let end_time = self.inner.get_hitobject_end_time(&stack_base_obj.inner); - let stack_threshold = - self.inner.difficulty.approach_preempt() as f64 * self.inner.stack_leniency; + let end_time = self + .inner + .get_hitobject_end_time(&stack_base_obj.inner) + .unwrap(); + let stack_threshold = self.inner.difficulty.approach_preempt().as_seconds() + * self.inner.stack_leniency; // We are no longer within stacking range of the next object. - if (object_n.inner.start_time.0 - end_time.0) as f64 > stack_threshold { + if (object_n.inner.start_time.0 as f64 - end_time) > stack_threshold { break; } @@ -114,7 +117,7 @@ impl BeatmapExt { } let stack_threshold = - self.inner.difficulty.approach_preempt() as f64 * self.inner.stack_leniency; + self.inner.difficulty.approach_preempt().as_seconds() * self.inner.stack_leniency; match object_i.inner.kind { HitObjectKind::Circle => { @@ -125,9 +128,10 @@ impl BeatmapExt { let end_time = self .inner - .get_hitobject_end_time(&self.hit_objects[n].inner); + .get_hitobject_end_time(&self.hit_objects[n].inner) + .unwrap(); - if (self.hit_objects[iidx].inner.start_time.0 - end_time.0) as f64 + if (self.hit_objects[iidx].inner.start_time.0 as f64 - end_time) > stack_threshold { break; diff --git a/src/game/events.rs b/src/game/events.rs index 4c865e2..6607f77 100644 --- a/src/game/events.rs +++ b/src/game/events.rs @@ -22,6 +22,7 @@ impl EventHandler for Game { _: f32, ) -> GameResult { self.mouse_pos = (x, y); + self.imgui.update_mouse_pos(x, y); Ok(()) } @@ -32,6 +33,9 @@ impl EventHandler for Game { x: f32, y: f32, ) -> GameResult { + self.imgui.update_mouse_down(btn); + // TODO: figure out if the UI handled anything, and then whether or not to keep going into + // letting the rest of the code handle the mouse press or not match btn { MouseButton::Left => { use super::seeker::BOUNDS; @@ -57,6 +61,7 @@ impl EventHandler for Game { x: f32, y: f32, ) -> GameResult { + self.imgui.update_mouse_up(btn); match btn { MouseButton::Left => { if let Some((px, py)) = self.left_drag_start { @@ -84,8 +89,9 @@ impl EventHandler for Game { Ok(()) } - fn key_up_event(&mut self, _: &mut Context, keycode: KeyCode, _: KeyMods) -> GameResult { + fn key_up_event(&mut self, _: &mut Context, keycode: KeyCode, keymods: KeyMods) -> GameResult { use KeyCode::*; + self.imgui.update_key_up(keycode, keymods); match keycode { Space => self.toggle_playing(), @@ -103,10 +109,11 @@ impl EventHandler for Game { &mut self, _: &mut Context, keycode: KeyCode, - mods: KeyMods, + keymods: KeyMods, _: bool, ) -> GameResult { use KeyCode::*; + self.imgui.update_key_down(keycode, keymods); self.keymap.insert(keycode); match keycode { @@ -120,7 +127,7 @@ impl EventHandler for Game { .. }) = &self.current_uninherited_timing_point { - let steps = -if mods.contains(KeyMods::SHIFT) { + let steps = -if keymods.contains(KeyMods::SHIFT) { info.meter as i32 } else { 1 @@ -134,7 +141,7 @@ impl EventHandler for Game { .. }) = &self.current_uninherited_timing_point { - let steps = if mods.contains(KeyMods::SHIFT) { + let steps = if keymods.contains(KeyMods::SHIFT) { info.meter as i32 } else { 1 diff --git a/src/game/grid.rs b/src/game/grid.rs index 5244e24..fe9b39f 100644 --- a/src/game/grid.rs +++ b/src/game/grid.rs @@ -61,7 +61,7 @@ impl Game { } pub(super) fn toggle_grid(&mut self) { - use libosu::enums::GridSize::*; + use libosu::data::GridSize::*; self.beatmap.inner.grid_size = match self.beatmap.inner.grid_size { Tiny => Small, Small => Medium, diff --git a/src/game/mod.rs b/src/game/mod.rs index c96f964..9f51e32 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -22,17 +22,20 @@ use ggez::{ Context, }; use image::io::Reader as ImageReader; +use imgui::Window; +use imgui_winit_support::WinitPlatform; use libosu::{ beatmap::Beatmap, hitobject::{HitObjectKind, SliderSplineKind, SpinnerInfo}, math::Point, spline::Spline, - timing::{TimestampMillis, TimingPoint, TimingPointKind}, + timing::{Millis, TimingPoint, TimingPointKind}, }; use crate::audio::{AudioEngine, Sound}; use crate::beatmap::{BeatmapExt, STACK_DISTANCE}; use crate::hitobject::HitObjectExt; +use crate::imgui_wrapper::ImGuiWrapper; use crate::skin::Skin; use crate::utils::{self, rect_contains}; @@ -47,7 +50,7 @@ pub const DEFAULT_COLORS: &[(f32, f32, f32)] = &[ pub type SliderCache = HashMap>, Spline>; pub struct PartialSliderState { - start_time: TimestampMillis, + start_time: Millis, kind: SliderSplineKind, control_points: Vec>, pixel_length: f64, @@ -62,6 +65,7 @@ pub enum Tool { pub struct Game { is_playing: bool, + imgui: ImGuiWrapper, audio_engine: AudioEngine, song: Option, beatmap: BeatmapExt, @@ -85,7 +89,7 @@ pub struct Game { } impl Game { - pub fn new() -> Result { + pub fn new(imgui: ImGuiWrapper) -> Result { let audio_engine = AudioEngine::new()?; let skin = Skin::new(); @@ -94,6 +98,7 @@ impl Game { Ok(Game { is_playing: false, + imgui, audio_engine, beatmap, song: None, @@ -208,7 +213,7 @@ impl Game { self.draw_grid(ctx)?; let time = self.song.as_ref().unwrap().position()?; - let time_millis = TimestampMillis((time * 1000.0) as i32); + let time_millis = Millis::from_seconds(time); let text = Text::new( format!( "tool: {:?} time: {:.4}, mouse: {:?}", @@ -221,20 +226,39 @@ impl Game { struct DrawInfo<'a> { hit_object: &'a HitObjectExt, - opacity: f64, + fade_opacity: f64, end_time: f64, color: Color, + /// Whether or not the circle (slider head for sliders) should appear to have already + /// been hit in the editor after the object's time has already come. + circle_is_hit: bool, } + // figure out what objects will be visible on the screen at the current instant + // 1.5 cus editor so people can see objects for longer durations let mut playfield_hitobjects = Vec::new(); - let preempt = 1.5 * (self.beatmap.inner.difficulty.approach_preempt() as f64) / 1000.0; - let fade_in = 1.5 * (self.beatmap.inner.difficulty.approach_fade_time() as f64) / 1000.0; + let preempt = 1.5 + * self + .beatmap + .inner + .difficulty + .approach_preempt() + .as_seconds(); + let fade_in = 1.5 + * self + .beatmap + .inner + .difficulty + .approach_fade_time() + .as_seconds(); + let fade_out_time = fade_in; // TODO: figure out what this number actually is + let fade_out_offset = preempt; // TODO: figure out what this number actually is // TODO: tighten this loop even more by binary searching for the start of the timeline and // playfield hitobjects rather than looping through the entire beatmap, better yet, just // keeping track of the old index will probably be much faster for ho in self.beatmap.hit_objects.iter().rev() { - let ho_time = (ho.inner.start_time.0 as f64) / 1000.0; + let ho_time = ho.inner.start_time.as_seconds(); let color = self.combo_colors[ho.color_idx]; // draw in timeline @@ -242,29 +266,41 @@ impl Game { // draw hitobject in playfield let end_time; - let opacity = if time > ho_time - fade_in { - 1.0 - } else { - // TODO: calculate ease - (time - (ho_time - preempt)) / fade_in - }; match ho.inner.kind { HitObjectKind::Circle => end_time = ho_time, HitObjectKind::Slider(_) => { let duration = self.beatmap.inner.get_slider_duration(&ho.inner).unwrap(); - end_time = ho_time + duration / 1000.0; + end_time = ho_time + duration; } HitObjectKind::Spinner(SpinnerInfo { end_time: spinner_end, - }) => end_time = (spinner_end.0 as f64) / 1000.0, + }) => end_time = spinner_end.as_seconds(), }; - if ho_time - preempt <= time && time <= end_time { + let fade_opacity = if time <= ho_time - fade_in { + // before the hitobject's time arrives, it fades in + // TODO: calculate ease + (time - (ho_time - preempt)) / fade_in + } else if time < ho_time + fade_out_time { + // while the object is on screen the opacity should be 1 + 1.0 + } else if time < end_time + fade_out_offset { + // after the hitobject's time, it fades out + // TODO: calculate ease + ((end_time + fade_out_offset) - time) / fade_out_time + } else { + // completely faded out + 0.0 + }; + let circle_is_hit = time > ho_time; + + if ho_time - preempt <= time && time <= end_time + fade_out_offset { playfield_hitobjects.push(DrawInfo { hit_object: ho, - opacity, + fade_opacity, end_time, color, + circle_is_hit, }); } } @@ -279,21 +315,20 @@ impl Game { for draw_info in playfield_hitobjects.iter() { let ho = draw_info.hit_object; - let ho_time = (ho.inner.start_time.0 as f64) / 1000.0; + let ho_time = ho.inner.start_time.as_seconds(); let stacking = ho.stacking as f32 * STACK_DISTANCE as f32; let pos = [ PLAYFIELD_BOUNDS.x + osupx_scale_x * (ho.inner.pos.x as f32 - stacking), PLAYFIELD_BOUNDS.y + osupx_scale_y * (ho.inner.pos.y as f32 - stacking), ]; - let color = draw_info.color; + let mut color = draw_info.color; + color.a = 0.6 * draw_info.fade_opacity as f32; let mut slider_info = None; if let HitObjectKind::Slider(info) = &ho.inner.kind { let mut control_points = vec![ho.inner.pos]; control_points.extend(&info.control_points); - let mut color = color; - color.a = 0.6 * draw_info.opacity as f32; Game::render_slider_body( &mut self.slider_cache, info, @@ -323,6 +358,7 @@ impl Game { } // draw main hitcircle + let faded_color = Color::new(1.0, 1.0, 1.0, 0.6 * draw_info.fade_opacity as f32); self.skin.hitcircle.draw( ctx, (cs_real * 2.0, cs_real * 2.0), @@ -331,15 +367,15 @@ impl Game { self.skin.hitcircleoverlay.draw( ctx, (cs_real * 2.0, cs_real * 2.0), - DrawParam::default().dest(pos), + DrawParam::default().dest(pos).color(faded_color), )?; // draw numbers - self.draw_numbers_on_circle(ctx, ho.number, pos, cs_real)?; + self.draw_numbers_on_circle(ctx, ho.number, pos, cs_real, faded_color)?; if let Some((info, control_points)) = slider_info { let spline = self.slider_cache.get(&control_points).unwrap(); - Game::render_slider_wireframe(ctx, &control_points, PLAYFIELD_BOUNDS)?; + Game::render_slider_wireframe(ctx, &control_points, PLAYFIELD_BOUNDS, faded_color)?; if time > ho_time && time < draw_info.end_time { let elapsed_time = time - ho_time; @@ -381,6 +417,19 @@ impl Game { self.draw_seeker(ctx)?; + let mut show = true; + self.imgui.render(ctx, 1.0, |ui| { + // Window + Window::new("Hello world") + .size([300.0, 600.0], imgui::Condition::FirstUseEver) + .position([50.0, 50.0], imgui::Condition::FirstUseEver) + .build(&ui, || { + // Your window stuff here! + ui.text("Hi from this label!"); + }); + ui.show_demo_window(&mut show); + }); + // draw whatever tool user is using let (mx, my) = self.mouse_pos; let pos_x = (mx - PLAYFIELD_BOUNDS.x) / PLAYFIELD_BOUNDS.w * 512.0; @@ -463,7 +512,7 @@ impl Game { debug!("done rendering slider body"); } - Game::render_slider_wireframe(ctx, &nodes, PLAYFIELD_BOUNDS)?; + Game::render_slider_wireframe(ctx, &nodes, PLAYFIELD_BOUNDS, Color::WHITE)?; debug!("done rendering slider wireframe"); } else { if rect_contains(&PLAYFIELD_BOUNDS, mx, my) { @@ -498,7 +547,7 @@ impl Game { let pos = song.position()?; if let Some(timing_point) = self.beatmap.inner.timing_points.first() { - if pos < timing_point.time.as_seconds().0.into_inner() { + if pos < timing_point.time.as_seconds() { if let TimingPointKind::Uninherited(_) = &timing_point.kind { self.current_uninherited_timing_point = Some(timing_point.clone()); } @@ -508,7 +557,7 @@ impl Game { let mut found_uninherited = false; let mut found_inherited = false; for timing_point in self.beatmap.inner.timing_points.iter() { - if pos < timing_point.time.as_seconds().0.into_inner() { + if pos < timing_point.time.as_seconds() { continue; } @@ -542,7 +591,7 @@ impl Game { .. }) = &self.current_uninherited_timing_point { - let diff = pos - time.as_seconds().0.into_inner(); + let diff = pos - time.as_seconds(); let tick = info.mpb / 1000.0 / info.meter as f64; let beats = (diff / tick).round(); let frac = diff - beats * tick; @@ -571,17 +620,17 @@ impl Game { if let Some(song) = &self.song { println!("song exists! {:?} {:?}", btn, self.tool); let time = song.position()?; - let time_millis = TimestampMillis((time * 1000.0) as i32); + let time_millis = Millis::from_seconds(time); if let (MouseButton::Left, Tool::Select) = (btn, &self.tool) { } else if let (MouseButton::Left, Tool::Circle) = (btn, &self.tool) { println!("left, circle, {:?} {} {}", PLAYFIELD_BOUNDS, x, y); if rect_contains(&PLAYFIELD_BOUNDS, x, y) { - let time = (song.position()? * 1000.0) as i32; + let time = Millis::from_seconds(song.position()?); match self .beatmap .hit_objects - .binary_search_by_key(&time, |ho| ho.inner.start_time.0) + .binary_search_by_key(&time, |ho| ho.inner.start_time) { Ok(v) => { println!("unfortunately already found at idx {}", v); @@ -593,7 +642,7 @@ impl Game { }; let inner = HitObject { - start_time: TimestampMillis(time), + start_time: time, pos, kind: HitObjectKind::Circle, new_combo: false, diff --git a/src/game/numbers.rs b/src/game/numbers.rs index 0fc387c..fed5afb 100644 --- a/src/game/numbers.rs +++ b/src/game/numbers.rs @@ -1,5 +1,8 @@ use anyhow::Result; -use ggez::{graphics::DrawParam, Context}; +use ggez::{ + graphics::{Color, DrawParam}, + Context, +}; use super::Game; @@ -10,6 +13,7 @@ impl Game { number: usize, pos: [f32; 2], cs: f32, + color: Color, ) -> Result<()> { let number = number.to_string(); let digits = number.len(); @@ -53,7 +57,7 @@ impl Game { digit.draw( ctx, (w, real_height), - DrawParam::default().dest([real_x, real_y]), + DrawParam::default().dest([real_x, real_y]).color(color), )?; } diff --git a/src/game/seeker.rs b/src/game/seeker.rs index 100060a..e23c510 100644 --- a/src/game/seeker.rs +++ b/src/game/seeker.rs @@ -54,7 +54,7 @@ impl Game { TimingPointKind::Uninherited(_) => Color::new(0.8, 0.0, 0.0, 0.6), }; - let percent = timing_point.time.as_seconds().0.into_inner() / len; + let percent = timing_point.time.as_seconds() / len; let x = percent as f32 * BOUNDS.w; let line = Mesh::new_line( diff --git a/src/game/sliders.rs b/src/game/sliders.rs index 7cd2253..2682bfc 100644 --- a/src/game/sliders.rs +++ b/src/game/sliders.rs @@ -130,6 +130,7 @@ impl Game { ctx: &mut Context, control_points: &[Point], rect: Rect, + color: Color, ) -> Result<()> { let osupx_scale_x = rect.w as f32 / 512.0; let osupx_scale_y = rect.h as f32 / 384.0; @@ -151,7 +152,7 @@ impl Game { ctx, DrawMode::Stroke(StrokeOptions::default()), &points_mapped, - Color::WHITE, + color, )?; graphics::draw(ctx, &frame, DrawParam::default())?; } @@ -160,7 +161,7 @@ impl Game { let mut i = 0; while i < points_mapped.len() { let fst = points_mapped[i]; - let mut color = Color::WHITE; + let mut color = color; if i < points_mapped.len() - 1 { let snd = points_mapped[i + 1]; if fst.eq(&snd) { diff --git a/src/game/timeline.rs b/src/game/timeline.rs index 4641466..2fbb08a 100644 --- a/src/game/timeline.rs +++ b/src/game/timeline.rs @@ -56,13 +56,13 @@ impl Game { &uninherited_timing_points[i], uninherited_timing_points.get(i + 1), ); - let fst_time = fst.time.as_seconds().0.into_inner(); + let fst_time = fst.time.as_seconds(); if let TimingPointKind::Uninherited(info) = &fst.kind { last_uninherited = Some(info); } let snd_time = if let Some(snd) = snd { - let snd_time = snd.time.as_seconds().0.into_inner(); + let snd_time = snd.time.as_seconds(); if snd_time >= timeline_left && snd_time <= timeline_right { Some(snd_time) } else { @@ -85,7 +85,7 @@ impl Game { let beat = last_uninherited.mpb / 1000.0; let ticks = TICKS[last_uninherited.meter as usize]; - let mut time = fst.time.as_seconds().0.into_inner(); + let mut time = fst.time.as_seconds(); let passed_measures = ((timeline_left - time) / beat).floor(); time += passed_measures * beat; @@ -158,9 +158,7 @@ impl Game { .beatmap .inner .get_hitobject_end_time(&ho.inner) - .as_seconds() - .0 - .into_inner(); + .unwrap(); let color = self.combo_colors[ho.color_idx]; @@ -274,6 +272,7 @@ impl Game { ho.number, [head_x, timeline_y + BOUNDS.h / 2.0], BOUNDS.h / 2.0, + Color::WHITE, )?; } diff --git a/src/imgui_wrapper.rs b/src/imgui_wrapper.rs new file mode 100644 index 0000000..f7f8d3d --- /dev/null +++ b/src/imgui_wrapper.rs @@ -0,0 +1,217 @@ +// Based on https://github.com/iolivia/imgui-ggez-starter with modifications. +// +// Copyright (c) 2019 Olivia Ifrim +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use ggez::event::{KeyCode, KeyMods, MouseButton}; +use ggez::graphics; +use ggez::Context; + +use gfx_core::{handle::RenderTargetView, memory::Typed}; +use gfx_device_gl; + +use imgui::{Context as ImContext, Key, Ui, Window}; +use imgui_gfx_renderer::*; + +use std::time::Instant; + +#[derive(Copy, Clone, PartialEq, Debug, Default)] +struct MouseState { + pos: (i32, i32), + /// mouse buttons: (left, right, middle) + pressed: (bool, bool, bool), + wheel: f32, + wheel_h: f32, +} + +pub struct ImGuiWrapper { + pub imgui: ImContext, + pub renderer: Renderer, + last_frame: Instant, + mouse_state: MouseState, +} + +impl ImGuiWrapper { + pub fn new(ctx: &mut Context) -> Self { + // Create the imgui object + let mut imgui = ImContext::create(); + imgui.set_ini_filename(None); + let (factory, gfx_device, _, _, _) = graphics::gfx_objects(ctx); + + // Shaders + let shaders = { + let version = gfx_device.get_info().shading_language; + if version.is_embedded { + if version.major >= 3 { + Shaders::GlSlEs300 + } else { + Shaders::GlSlEs100 + } + } else if version.major >= 4 { + Shaders::GlSl400 + } else if version.major >= 3 { + Shaders::GlSl130 + } else { + Shaders::GlSl110 + } + }; + + // Renderer + let mut renderer = Renderer::init(&mut imgui, &mut *factory, shaders).unwrap(); + + { + let mut io = imgui.io_mut(); + io[Key::Tab] = KeyCode::Tab as _; + io[Key::LeftArrow] = KeyCode::Left as _; + io[Key::RightArrow] = KeyCode::Right as _; + io[Key::UpArrow] = KeyCode::Up as _; + io[Key::DownArrow] = KeyCode::Down as _; + io[Key::PageUp] = KeyCode::PageUp as _; + io[Key::PageDown] = KeyCode::PageDown as _; + io[Key::Home] = KeyCode::Home as _; + io[Key::End] = KeyCode::End as _; + io[Key::Insert] = KeyCode::Insert as _; + io[Key::Delete] = KeyCode::Delete as _; + io[Key::Backspace] = KeyCode::Back as _; + io[Key::Space] = KeyCode::Space as _; + io[Key::Enter] = KeyCode::Return as _; + io[Key::Escape] = KeyCode::Escape as _; + io[Key::KeyPadEnter] = KeyCode::NumpadEnter as _; + io[Key::A] = KeyCode::A as _; + io[Key::C] = KeyCode::C as _; + io[Key::V] = KeyCode::V as _; + io[Key::X] = KeyCode::X as _; + io[Key::Y] = KeyCode::Y as _; + io[Key::Z] = KeyCode::Z as _; + } + + // Create instance + Self { + imgui, + renderer, + last_frame: Instant::now(), + mouse_state: MouseState::default(), + } + } + + pub fn render(&mut self, ctx: &mut Context, hidpi_factor: f32, run_ui: F) + where + F: FnOnce(&Ui), + { + // Update mouse + self.update_mouse(); + + // Create new frame + let now = Instant::now(); + let delta = now - self.last_frame; + let delta_s = delta.as_secs() as f32 + delta.subsec_nanos() as f32 / 1_000_000_000.0; + self.last_frame = now; + + let (draw_width, draw_height) = graphics::drawable_size(ctx); + self.imgui.io_mut().display_size = [draw_width, draw_height]; + self.imgui.io_mut().display_framebuffer_scale = [hidpi_factor, hidpi_factor]; + self.imgui.io_mut().delta_time = delta_s; + + let ui = self.imgui.frame(); + run_ui(&ui); + + // Render + let (factory, _, encoder, _, render_target) = graphics::gfx_objects(ctx); + let draw_data = ui.render(); + self.renderer + .render( + &mut *factory, + encoder, + &mut RenderTargetView::new(render_target.clone()), + draw_data, + ) + .unwrap(); + } + + fn update_mouse(&mut self) { + self.imgui.io_mut().mouse_pos = + [self.mouse_state.pos.0 as f32, self.mouse_state.pos.1 as f32]; + + self.imgui.io_mut().mouse_down = [ + self.mouse_state.pressed.0, + self.mouse_state.pressed.1, + self.mouse_state.pressed.2, + false, + false, + ]; + + self.imgui.io_mut().mouse_wheel = self.mouse_state.wheel; + self.mouse_state.wheel = 0.0; + + self.imgui.io_mut().mouse_wheel_h = self.mouse_state.wheel_h; + self.mouse_state.wheel_h = 0.0; + } + + pub fn update_mouse_pos(&mut self, x: f32, y: f32) { + self.mouse_state.pos = (x as i32, y as i32); + } + + pub fn update_mouse_down(&mut self, button: MouseButton) { + match button { + MouseButton::Left => self.mouse_state.pressed.0 = true, + MouseButton::Right => self.mouse_state.pressed.1 = true, + MouseButton::Middle => self.mouse_state.pressed.2 = true, + _ => (), + } + } + + pub fn update_mouse_up(&mut self, button: MouseButton) { + match button { + MouseButton::Left => self.mouse_state.pressed.0 = false, + MouseButton::Right => self.mouse_state.pressed.1 = false, + MouseButton::Middle => self.mouse_state.pressed.2 = false, + _ => (), + } + } + + pub fn update_key_down(&mut self, key: KeyCode, mods: KeyMods) { + self.imgui.io_mut().key_shift = mods.contains(KeyMods::SHIFT); + self.imgui.io_mut().key_ctrl = mods.contains(KeyMods::CTRL); + self.imgui.io_mut().key_alt = mods.contains(KeyMods::ALT); + self.imgui.io_mut().keys_down[key as usize] = true; + } + + pub fn update_key_up(&mut self, key: KeyCode, mods: KeyMods) { + if mods.contains(KeyMods::SHIFT) { + self.imgui.io_mut().key_shift = false; + } + if mods.contains(KeyMods::CTRL) { + self.imgui.io_mut().key_ctrl = false; + } + if mods.contains(KeyMods::ALT) { + self.imgui.io_mut().key_alt = false; + } + self.imgui.io_mut().keys_down[key as usize] = false; + } + + pub fn update_text(&mut self, val: char) { + self.imgui.io_mut().add_input_character(val); + } + + pub fn update_scroll(&mut self, x: f32, y: f32) { + self.mouse_state.wheel += y; + self.mouse_state.wheel_h += x; + } +} diff --git a/src/main.rs b/src/main.rs index e48b13c..812d8ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ mod audio; mod beatmap; mod game; mod hitobject; +mod imgui_wrapper; mod skin; mod utils; @@ -19,8 +20,12 @@ use std::path::PathBuf; use anyhow::Result; use ggez::{ conf::{WindowMode, WindowSetup}, - event, ContextBuilder, + event, graphics, ContextBuilder, }; +use imgui::{Context as ImContext, FontConfig, FontSource}; +use imgui_gfx_renderer::{Renderer, Shaders}; +use imgui_winit_support::{HiDpiMode, WinitPlatform}; +use imgui_wrapper::ImGuiWrapper; use structopt::StructOpt; use crate::game::Game; @@ -52,9 +57,31 @@ fn main() -> Result<()> { .window_setup(WindowSetup::default().title("OSU editor")) .window_mode(WindowMode::default().dimensions(1024.0, 768.0)); - let (mut ctx, mut event_loop) = cb.build()?; - let mut game = Game::new()?; + let (mut ctx, event_loop) = cb.build()?; + + let imgui = ImGuiWrapper::new(&mut ctx); + + // let font_size = 13.0; + // imgui.fonts().add_font(&[FontSource::TtfData { + // data: include_bytes!("../resources/Roboto-Regular.ttf"), + // size_pixels: font_size, + // config: Some(FontConfig { + // // As imgui-glium-renderer isn't gamma-correct with + // // it's font rendering, we apply an arbitrary + // // multiplier to make the font a bit "heavier". With + // // default imgui-glow-renderer this is unnecessary. + // rasterizer_multiply: 1.5, + // // Oversampling font helps improve text rendering at + // // expense of larger font atlas texture. + // oversample_h: 4, + // oversample_v: 4, + // ..FontConfig::default() + // }), + // }]); + + let mut game = Game::new(imgui)?; game.skin.load_all(&mut ctx)?; + // platform.attach_window(); if let Some(path) = opt.path { game.load_beatmap(&mut ctx, path)?;