Add imgui into the editor

This commit is contained in:
Michael Zhang 2022-01-10 16:49:03 -06:00
parent 70072b5e1a
commit c5c219337e
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
13 changed files with 396 additions and 224 deletions

184
Cargo.lock generated
View file

@ -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"

View file

@ -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 = []

2
run.sh
View file

@ -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 "$@"

View file

@ -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;

View file

@ -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

View file

@ -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,

View file

@ -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<Vec<Point<i32>>, Spline>;
pub struct PartialSliderState {
start_time: TimestampMillis,
start_time: Millis,
kind: SliderSplineKind,
control_points: Vec<Point<i32>>,
pixel_length: f64,
@ -62,6 +65,7 @@ pub enum Tool {
pub struct Game {
is_playing: bool,
imgui: ImGuiWrapper,
audio_engine: AudioEngine,
song: Option<Sound>,
beatmap: BeatmapExt,
@ -85,7 +89,7 @@ pub struct Game {
}
impl Game {
pub fn new() -> Result<Game> {
pub fn new(imgui: ImGuiWrapper) -> Result<Game> {
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,

View file

@ -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),
)?;
}

View file

@ -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(

View file

@ -130,6 +130,7 @@ impl Game {
ctx: &mut Context,
control_points: &[Point<i32>],
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) {

View file

@ -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,
)?;
}

217
src/imgui_wrapper.rs Normal file
View file

@ -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<gfx_core::format::Rgba8, gfx_device_gl::Resources>,
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<F>(&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;
}
}

View file

@ -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)?;