Minor refactoring
This commit is contained in:
parent
205f78fd84
commit
09f42d2cb4
8 changed files with 371 additions and 311 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
/target
|
||||
/target
|
||||
/songs
|
||||
|
|
|
@ -26,6 +26,15 @@ impl EventHandler for Game {
|
|||
if self.imgui.want_capture_mouse() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.seeker_drag {
|
||||
use super::seeker::BOUNDS;
|
||||
let jump_percent = (x - BOUNDS.x) / BOUNDS.w;
|
||||
if let Some(song) = &self.song {
|
||||
let pos = jump_percent as f64 * song.length().unwrap();
|
||||
song.set_position(pos);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -47,6 +56,7 @@ impl EventHandler for Game {
|
|||
MouseButton::Left => {
|
||||
use super::seeker::BOUNDS;
|
||||
if rect_contains(&BOUNDS, x, y) {
|
||||
self.seeker_drag = true;
|
||||
let jump_percent = (x - BOUNDS.x) / BOUNDS.w;
|
||||
if let Some(song) = &self.song {
|
||||
let pos = jump_percent as f64 * song.length().unwrap();
|
||||
|
@ -68,7 +78,16 @@ impl EventHandler for Game {
|
|||
x: f32,
|
||||
y: f32,
|
||||
) -> GameResult {
|
||||
// let go of the seeker
|
||||
if self.seeker_drag {
|
||||
self.seeker_drag = false;
|
||||
}
|
||||
|
||||
self.imgui.update_mouse_up(btn);
|
||||
if self.imgui.want_capture_mouse() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match btn {
|
||||
MouseButton::Left => {
|
||||
if let Some((px, py)) = self.left_drag_start {
|
||||
|
|
205
src/game/hitobjects.rs
Normal file
205
src/game/hitobjects.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
use anyhow::Result;
|
||||
use ggez::{
|
||||
graphics::{Color, DrawParam},
|
||||
Context,
|
||||
};
|
||||
use libosu::prelude::*;
|
||||
|
||||
use crate::{beatmap::STACK_DISTANCE, hitobject::HitObjectExt};
|
||||
|
||||
use super::{Game, PLAYFIELD_BOUNDS};
|
||||
|
||||
pub struct DrawInfo<'a> {
|
||||
hit_object: &'a HitObjectExt,
|
||||
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,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub(super) fn draw_hitobjects(&mut self, ctx: &mut Context, current_time: f64) -> Result<()> {
|
||||
// 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_seconds();
|
||||
let fade_in = 1.5
|
||||
* self
|
||||
.beatmap
|
||||
.inner
|
||||
.difficulty
|
||||
.approach_fade_time()
|
||||
.as_seconds();
|
||||
let fade_out_time = 0.75; // TODO: figure out what this number actually is
|
||||
let fade_out_offset = 0.0; // 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.as_seconds();
|
||||
let color = self.combo_colors[ho.color_idx];
|
||||
|
||||
// draw in timeline
|
||||
self.draw_hitobject_to_timeline(ctx, current_time, ho)?;
|
||||
|
||||
// draw hitobject in playfield
|
||||
let end_time;
|
||||
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;
|
||||
}
|
||||
HitObjectKind::Spinner(SpinnerInfo {
|
||||
end_time: spinner_end,
|
||||
}) => end_time = spinner_end.as_seconds(),
|
||||
};
|
||||
|
||||
let fade_opacity = if current_time <= ho_time - fade_in {
|
||||
// before the hitobject's time arrives, it fades in
|
||||
// TODO: calculate ease
|
||||
(current_time - (ho_time - preempt)) / fade_in
|
||||
} else if current_time < ho_time + fade_out_time {
|
||||
// while the object is on screen the opacity should be 1
|
||||
1.0
|
||||
} else if current_time < end_time + fade_out_offset {
|
||||
// after the hitobject's time, it fades out
|
||||
// TODO: calculate ease
|
||||
((end_time + fade_out_offset) - current_time) / fade_out_time
|
||||
} else {
|
||||
// completely faded out
|
||||
0.0
|
||||
};
|
||||
let circle_is_hit = current_time > ho_time;
|
||||
|
||||
if ho_time - preempt <= current_time && current_time <= end_time + fade_out_offset {
|
||||
playfield_hitobjects.push(DrawInfo {
|
||||
hit_object: ho,
|
||||
fade_opacity,
|
||||
end_time,
|
||||
color,
|
||||
circle_is_hit,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let cs_scale = PLAYFIELD_BOUNDS.w / 640.0;
|
||||
let osupx_scale_x = PLAYFIELD_BOUNDS.w / 512.0;
|
||||
let osupx_scale_y = PLAYFIELD_BOUNDS.h / 384.0;
|
||||
let cs_osupx = self.beatmap.inner.difficulty.circle_size_osupx();
|
||||
let cs_real = cs_osupx * cs_scale;
|
||||
|
||||
for draw_info in playfield_hitobjects.iter() {
|
||||
let ho = draw_info.hit_object;
|
||||
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 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);
|
||||
|
||||
Game::render_slider_body(
|
||||
&mut self.slider_cache,
|
||||
info,
|
||||
control_points.as_ref(),
|
||||
ctx,
|
||||
PLAYFIELD_BOUNDS,
|
||||
&self.beatmap.inner,
|
||||
color,
|
||||
)?;
|
||||
slider_info = Some((info, control_points));
|
||||
|
||||
let end_pos = ho.inner.end_pos();
|
||||
let end_pos = [
|
||||
PLAYFIELD_BOUNDS.x + osupx_scale_x * end_pos.x as f32,
|
||||
PLAYFIELD_BOUNDS.y + osupx_scale_y * end_pos.y as f32,
|
||||
];
|
||||
self.skin.hitcircle.draw(
|
||||
ctx,
|
||||
(cs_real * 2.0, cs_real * 2.0),
|
||||
DrawParam::default().dest(end_pos).color(color),
|
||||
)?;
|
||||
self.skin.hitcircleoverlay.draw(
|
||||
ctx,
|
||||
(cs_real * 2.0, cs_real * 2.0),
|
||||
DrawParam::default().dest(end_pos),
|
||||
)?;
|
||||
}
|
||||
|
||||
// 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),
|
||||
DrawParam::default().dest(pos).color(color),
|
||||
)?;
|
||||
self.skin.hitcircleoverlay.draw(
|
||||
ctx,
|
||||
(cs_real * 2.0, cs_real * 2.0),
|
||||
DrawParam::default().dest(pos).color(faded_color),
|
||||
)?;
|
||||
|
||||
// draw numbers
|
||||
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, faded_color)?;
|
||||
|
||||
if current_time > ho_time && current_time < draw_info.end_time {
|
||||
let elapsed_time = current_time - ho_time;
|
||||
let total_duration = draw_info.end_time - ho_time;
|
||||
let single_duration = total_duration / info.num_repeats as f64;
|
||||
let finished_repeats = (elapsed_time / single_duration).floor();
|
||||
let this_repeat_time = elapsed_time - finished_repeats * single_duration;
|
||||
let mut travel_percent = this_repeat_time / single_duration;
|
||||
|
||||
// reverse direction on odd trips
|
||||
if finished_repeats as u32 % 2 == 1 {
|
||||
travel_percent = 1.0 - travel_percent;
|
||||
}
|
||||
let travel_length = travel_percent * info.pixel_length;
|
||||
let pos = spline.point_at_length(travel_length);
|
||||
let ball_pos = [
|
||||
PLAYFIELD_BOUNDS.x + osupx_scale_x * pos.x as f32,
|
||||
PLAYFIELD_BOUNDS.y + osupx_scale_y * pos.y as f32,
|
||||
];
|
||||
self.skin.sliderb.draw_frame(
|
||||
ctx,
|
||||
(cs_real * 1.8, cs_real * 1.8),
|
||||
DrawParam::default().dest(ball_pos).color(color),
|
||||
(travel_percent / 0.25) as usize,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if current_time < ho_time {
|
||||
let time_diff = ho_time - current_time;
|
||||
let approach_r = cs_real * (1.0 + 2.0 * time_diff as f32 / 0.75);
|
||||
self.skin.approachcircle.draw(
|
||||
ctx,
|
||||
(approach_r * 2.0, approach_r * 2.0),
|
||||
DrawParam::default().dest(pos).color(color),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
259
src/game/mod.rs
259
src/game/mod.rs
|
@ -1,10 +1,12 @@
|
|||
mod background;
|
||||
mod events;
|
||||
mod grid;
|
||||
mod hitobjects;
|
||||
mod numbers;
|
||||
mod seeker;
|
||||
mod sliders;
|
||||
mod timeline;
|
||||
mod ui;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::File;
|
||||
|
@ -22,23 +24,23 @@ use ggez::{
|
|||
Context,
|
||||
};
|
||||
use image::io::Reader as ImageReader;
|
||||
use imgui::{Window, MenuItem};
|
||||
use imgui_winit_support::WinitPlatform;
|
||||
use libosu::{
|
||||
beatmap::Beatmap,
|
||||
hitobject::{HitObjectKind, SliderSplineKind, SpinnerInfo},
|
||||
hitobject::{HitObjectKind, SliderSplineKind},
|
||||
math::Point,
|
||||
spline::Spline,
|
||||
timing::{Millis, TimingPoint, TimingPointKind},
|
||||
};
|
||||
|
||||
use crate::audio::{AudioEngine, Sound};
|
||||
use crate::beatmap::{BeatmapExt, STACK_DISTANCE};
|
||||
use crate::beatmap::BeatmapExt;
|
||||
use crate::hitobject::HitObjectExt;
|
||||
use crate::imgui_wrapper::ImGuiWrapper;
|
||||
use crate::skin::Skin;
|
||||
use crate::utils::{self, rect_contains};
|
||||
|
||||
use self::ui::UiState;
|
||||
|
||||
pub const PLAYFIELD_BOUNDS: Rect = Rect::new(112.0, 122.0, 800.0, 600.0);
|
||||
pub const DEFAULT_COLORS: &[(f32, f32, f32)] = &[
|
||||
(1.0, 0.75, 0.0),
|
||||
|
@ -71,9 +73,11 @@ pub struct Game {
|
|||
beatmap: BeatmapExt,
|
||||
pub skin: Skin,
|
||||
background_image: Option<Image>,
|
||||
ui_state: Option<UiState>,
|
||||
|
||||
frame: usize,
|
||||
slider_cache: SliderCache,
|
||||
seeker_drag: bool,
|
||||
seeker_cache: Option<CanvasGeneric<GlBackendSpec>>,
|
||||
combo_colors: Vec<Color>,
|
||||
selected_objects: Vec<usize>,
|
||||
|
@ -103,8 +107,10 @@ impl Game {
|
|||
beatmap,
|
||||
song: None,
|
||||
skin,
|
||||
ui_state: Some(UiState::default()),
|
||||
frame: 0,
|
||||
slider_cache: SliderCache::default(),
|
||||
seeker_drag: false,
|
||||
seeker_cache: None,
|
||||
combo_colors: DEFAULT_COLORS
|
||||
.iter()
|
||||
|
@ -224,199 +230,17 @@ impl Game {
|
|||
graphics::queue_text(ctx, &text, [0.0, 0.0], Some(Color::WHITE));
|
||||
graphics::draw_queued_text(ctx, DrawParam::default(), None, FilterMode::Linear)?;
|
||||
|
||||
struct DrawInfo<'a> {
|
||||
hit_object: &'a HitObjectExt,
|
||||
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_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.as_seconds();
|
||||
let color = self.combo_colors[ho.color_idx];
|
||||
|
||||
// draw in timeline
|
||||
self.draw_hitobject_to_timeline(ctx, time, ho)?;
|
||||
|
||||
// draw hitobject in playfield
|
||||
let end_time;
|
||||
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;
|
||||
}
|
||||
HitObjectKind::Spinner(SpinnerInfo {
|
||||
end_time: spinner_end,
|
||||
}) => end_time = spinner_end.as_seconds(),
|
||||
};
|
||||
|
||||
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,
|
||||
fade_opacity,
|
||||
end_time,
|
||||
color,
|
||||
circle_is_hit,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.draw_timeline(ctx, time)?;
|
||||
|
||||
let cs_scale = PLAYFIELD_BOUNDS.w / 640.0;
|
||||
let osupx_scale_x = PLAYFIELD_BOUNDS.w / 512.0;
|
||||
let osupx_scale_y = PLAYFIELD_BOUNDS.h / 384.0;
|
||||
let cs_osupx = self.beatmap.inner.difficulty.circle_size_osupx();
|
||||
let cs_real = cs_osupx * cs_scale;
|
||||
|
||||
for draw_info in playfield_hitobjects.iter() {
|
||||
let ho = draw_info.hit_object;
|
||||
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 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);
|
||||
|
||||
Game::render_slider_body(
|
||||
&mut self.slider_cache,
|
||||
info,
|
||||
control_points.as_ref(),
|
||||
ctx,
|
||||
PLAYFIELD_BOUNDS,
|
||||
&self.beatmap.inner,
|
||||
color,
|
||||
)?;
|
||||
slider_info = Some((info, control_points));
|
||||
|
||||
let end_pos = ho.inner.end_pos();
|
||||
let end_pos = [
|
||||
PLAYFIELD_BOUNDS.x + osupx_scale_x * end_pos.x as f32,
|
||||
PLAYFIELD_BOUNDS.y + osupx_scale_y * end_pos.y as f32,
|
||||
];
|
||||
self.skin.hitcircle.draw(
|
||||
ctx,
|
||||
(cs_real * 2.0, cs_real * 2.0),
|
||||
DrawParam::default().dest(end_pos).color(color),
|
||||
)?;
|
||||
self.skin.hitcircleoverlay.draw(
|
||||
ctx,
|
||||
(cs_real * 2.0, cs_real * 2.0),
|
||||
DrawParam::default().dest(end_pos),
|
||||
)?;
|
||||
}
|
||||
|
||||
// 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),
|
||||
DrawParam::default().dest(pos).color(color),
|
||||
)?;
|
||||
self.skin.hitcircleoverlay.draw(
|
||||
ctx,
|
||||
(cs_real * 2.0, cs_real * 2.0),
|
||||
DrawParam::default().dest(pos).color(faded_color),
|
||||
)?;
|
||||
|
||||
// draw numbers
|
||||
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, faded_color)?;
|
||||
|
||||
if time > ho_time && time < draw_info.end_time {
|
||||
let elapsed_time = time - ho_time;
|
||||
let total_duration = draw_info.end_time - ho_time;
|
||||
let single_duration = total_duration / info.num_repeats as f64;
|
||||
let finished_repeats = (elapsed_time / single_duration).floor();
|
||||
let this_repeat_time = elapsed_time - finished_repeats * single_duration;
|
||||
let mut travel_percent = this_repeat_time / single_duration;
|
||||
|
||||
// reverse direction on odd trips
|
||||
if finished_repeats as u32 % 2 == 1 {
|
||||
travel_percent = 1.0 - travel_percent;
|
||||
}
|
||||
let travel_length = travel_percent * info.pixel_length;
|
||||
let pos = spline.point_at_length(travel_length);
|
||||
let ball_pos = [
|
||||
PLAYFIELD_BOUNDS.x + osupx_scale_x * pos.x as f32,
|
||||
PLAYFIELD_BOUNDS.y + osupx_scale_y * pos.y as f32,
|
||||
];
|
||||
self.skin.sliderb.draw_frame(
|
||||
ctx,
|
||||
(cs_real * 1.8, cs_real * 1.8),
|
||||
DrawParam::default().dest(ball_pos).color(color),
|
||||
(travel_percent / 0.25) as usize,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if time < ho_time {
|
||||
let time_diff = ho_time - time;
|
||||
let approach_r = cs_real * (1.0 + 2.0 * time_diff as f32 / 0.75);
|
||||
self.skin.approachcircle.draw(
|
||||
ctx,
|
||||
(approach_r * 2.0, approach_r * 2.0),
|
||||
DrawParam::default().dest(pos).color(color),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
self.draw_hitobjects(ctx, time)?;
|
||||
|
||||
self.draw_seeker(ctx)?;
|
||||
|
||||
// TODO: don't duplicate these from hitobjects.rs
|
||||
let cs_scale = PLAYFIELD_BOUNDS.w / 640.0;
|
||||
let cs_osupx = self.beatmap.inner.difficulty.circle_size_osupx();
|
||||
let cs_real = cs_osupx * cs_scale;
|
||||
|
||||
// draw whatever tool user is using
|
||||
let (mx, my) = self.mouse_pos;
|
||||
let pos_x = (mx - PLAYFIELD_BOUNDS.x) / PLAYFIELD_BOUNDS.w * 512.0;
|
||||
|
@ -520,53 +344,10 @@ impl Game {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
let mut show = true;
|
||||
self.imgui.render(ctx, 1.0, |ui| {
|
||||
if let Some(menu_bar) = ui.begin_main_menu_bar() {
|
||||
if let Some(menu) = ui.begin_menu("File") {
|
||||
MenuItem::new("Save <C-s>").build(ui);
|
||||
MenuItem::new("Create Difficulty").build(ui);
|
||||
ui.separator();
|
||||
MenuItem::new("Revert to Saved <C-l>").build(ui);
|
||||
MenuItem::new("Export...").build(ui);
|
||||
ui.separator();
|
||||
MenuItem::new("Open Song Folder").build(ui);
|
||||
MenuItem::new("Exit <Esc>").build(ui);
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Edit") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("View") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Compose") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Design") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Timing") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Web") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Help") {
|
||||
menu.end();
|
||||
}
|
||||
menu_bar.end();
|
||||
}
|
||||
// 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);
|
||||
});
|
||||
if let Some(mut state) = self.ui_state.take() {
|
||||
self.draw_ui(ctx, &mut state)?;
|
||||
self.ui_state = Some(state);
|
||||
}
|
||||
|
||||
graphics::present(ctx)?;
|
||||
self.frame += 1;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use ggez::{
|
||||
conf::NumSamples,
|
||||
graphics::{self, Canvas, Color, DrawMode, DrawParam, FillOptions, Mesh, Rect},
|
||||
graphics::{self, Color, DrawMode, DrawParam, FillOptions, Mesh, Rect},
|
||||
mint::Point2,
|
||||
Context,
|
||||
};
|
||||
|
@ -13,86 +12,67 @@ pub const BOUNDS: Rect = Rect::new(46.0, 732.0, 932.0, 36.0);
|
|||
|
||||
impl Game {
|
||||
pub(super) fn draw_seeker(&mut self, ctx: &mut Context) -> Result<()> {
|
||||
if self.seeker_cache.is_none() {
|
||||
println!("drawing seeker");
|
||||
let format = graphics::get_window_color_format(ctx);
|
||||
let canvas = Canvas::new(
|
||||
ctx,
|
||||
BOUNDS.w as u16,
|
||||
BOUNDS.h as u16,
|
||||
NumSamples::Sixteen,
|
||||
format,
|
||||
)?;
|
||||
graphics::set_canvas(ctx, Some(&canvas));
|
||||
let rect = Mesh::new_rectangle(
|
||||
ctx,
|
||||
DrawMode::Fill(FillOptions::default()),
|
||||
Rect::new(0.0, 732.0, 1024.0, 36.0),
|
||||
Color::new(0.0, 0.0, 0.0, 0.7),
|
||||
)?;
|
||||
graphics::draw(ctx, &rect, DrawParam::default())?;
|
||||
|
||||
let rect = Mesh::new_rectangle(
|
||||
ctx,
|
||||
DrawMode::Fill(FillOptions::default()),
|
||||
Rect::new(0.0, 732.0, 1024.0, 36.0),
|
||||
Color::new(0.0, 0.0, 0.0, 0.7),
|
||||
)?;
|
||||
graphics::draw(ctx, &rect, DrawParam::default())?;
|
||||
// draw the main timeline of the seeker
|
||||
let line_y = BOUNDS.y + BOUNDS.h / 2.0;
|
||||
let line = Mesh::new_line(
|
||||
ctx,
|
||||
&[
|
||||
Point2::from([BOUNDS.x, line_y]),
|
||||
Point2::from([BOUNDS.w, line_y]),
|
||||
],
|
||||
1.0,
|
||||
Color::WHITE,
|
||||
)?;
|
||||
graphics::draw(ctx, &line, DrawParam::default())?;
|
||||
|
||||
let line_y = BOUNDS.h / 2.0;
|
||||
let line = Mesh::new_line(
|
||||
ctx,
|
||||
&[
|
||||
Point2::from([0.0, line_y]),
|
||||
Point2::from([BOUNDS.w, line_y]),
|
||||
],
|
||||
1.0,
|
||||
Color::WHITE,
|
||||
)?;
|
||||
graphics::draw(ctx, &line, DrawParam::default())?;
|
||||
if let Some(song) = &self.song {
|
||||
let len = song.length()?;
|
||||
|
||||
if let Some(song) = &self.song {
|
||||
let len = song.length()?;
|
||||
// draw timing points
|
||||
for timing_point in self.beatmap.inner.timing_points.iter() {
|
||||
let color = match timing_point.kind {
|
||||
TimingPointKind::Inherited(_) => Color::new(0.0, 0.8, 0.0, 0.4),
|
||||
TimingPointKind::Uninherited(_) => Color::new(0.8, 0.0, 0.0, 0.6),
|
||||
};
|
||||
|
||||
for timing_point in self.beatmap.inner.timing_points.iter() {
|
||||
let color = match timing_point.kind {
|
||||
TimingPointKind::Inherited(_) => Color::new(0.0, 0.8, 0.0, 0.4),
|
||||
TimingPointKind::Uninherited(_) => Color::new(0.8, 0.0, 0.0, 0.6),
|
||||
};
|
||||
let percent = timing_point.time.as_seconds() / len;
|
||||
let x = BOUNDS.x + percent as f32 * BOUNDS.w;
|
||||
|
||||
let percent = timing_point.time.as_seconds() / len;
|
||||
let x = percent as f32 * BOUNDS.w;
|
||||
|
||||
let line = Mesh::new_line(
|
||||
ctx,
|
||||
&[Point2::from([x, 0.0]), Point2::from([x, BOUNDS.h / 2.0])],
|
||||
1.0,
|
||||
color,
|
||||
)?;
|
||||
graphics::draw(ctx, &line, DrawParam::default())?;
|
||||
}
|
||||
|
||||
let percent = song.position()? / len;
|
||||
let x = percent as f32 * BOUNDS.w;
|
||||
let line = Mesh::new_line(
|
||||
ctx,
|
||||
&[
|
||||
Point2::from([x, 0.2 * BOUNDS.h]),
|
||||
Point2::from([x, 0.8 * BOUNDS.h]),
|
||||
Point2::from([x, BOUNDS.y]),
|
||||
Point2::from([x, BOUNDS.y + BOUNDS.h / 2.0]),
|
||||
],
|
||||
4.0,
|
||||
Color::WHITE,
|
||||
1.0,
|
||||
color,
|
||||
)?;
|
||||
graphics::draw(ctx, &line, DrawParam::default())?;
|
||||
}
|
||||
|
||||
graphics::set_canvas(ctx, None);
|
||||
self.seeker_cache = Some(canvas);
|
||||
};
|
||||
|
||||
if let Some(canvas) = &self.seeker_cache {
|
||||
graphics::draw(
|
||||
// draw the knob for current position
|
||||
let percent = song.position()? / len;
|
||||
let x = BOUNDS.x + percent as f32 * BOUNDS.w;
|
||||
let line = Mesh::new_line(
|
||||
ctx,
|
||||
canvas,
|
||||
DrawParam::default()
|
||||
.dest([BOUNDS.x, BOUNDS.y])
|
||||
.scale([1.0, 10.0]),
|
||||
&[
|
||||
Point2::from([x, BOUNDS.y + 0.2 * BOUNDS.h]),
|
||||
Point2::from([x, BOUNDS.y + 0.8 * BOUNDS.h]),
|
||||
],
|
||||
4.0,
|
||||
Color::WHITE,
|
||||
)?;
|
||||
graphics::draw(ctx, &line, DrawParam::default())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
77
src/game/ui.rs
Normal file
77
src/game/ui.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use anyhow::Result;
|
||||
use ggez::Context;
|
||||
use imgui::{Condition, MenuItem, TabBar, TabItem, Window};
|
||||
|
||||
use super::Game;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct UiState {
|
||||
song_setup_selected: bool,
|
||||
song_setup_artist: String,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub(super) fn draw_ui(&mut self, ctx: &mut Context, state: &mut UiState) -> Result<()> {
|
||||
self.imgui.render(ctx, 1.0, |ui| {
|
||||
// menu bar
|
||||
if let Some(menu_bar) = ui.begin_main_menu_bar() {
|
||||
if let Some(menu) = ui.begin_menu("File") {
|
||||
MenuItem::new("Save <C-s>").build(ui);
|
||||
MenuItem::new("Create Difficulty").build(ui);
|
||||
ui.separator();
|
||||
MenuItem::new("Song Setup").build_with_ref(ui, &mut state.song_setup_selected);
|
||||
MenuItem::new("Revert to Saved <C-l>").build(ui);
|
||||
ui.separator();
|
||||
MenuItem::new("Open Song Folder").build(ui);
|
||||
MenuItem::new("Exit <Esc>").build(ui);
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Edit") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("View") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Compose") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Design") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Timing") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Web") {
|
||||
menu.end();
|
||||
}
|
||||
if let Some(menu) = ui.begin_menu("Help") {
|
||||
menu.end();
|
||||
}
|
||||
menu_bar.end();
|
||||
}
|
||||
|
||||
if state.song_setup_selected {
|
||||
Window::new("Song Setup")
|
||||
.size([80.0, 120.0], Condition::Appearing)
|
||||
.build(&ui, || {
|
||||
TabBar::new("song_setup").build(&ui, || {
|
||||
TabItem::new("General").build(&ui, || {
|
||||
ui.group(|| {
|
||||
ui.text("Artist");
|
||||
ui.same_line();
|
||||
ui.input_text("", &mut state.song_setup_artist).build();
|
||||
});
|
||||
});
|
||||
TabItem::new("Difficulty").build(&ui, || {});
|
||||
TabItem::new("Audio").build(&ui, || {});
|
||||
TabItem::new("Colors").build(&ui, || {});
|
||||
TabItem::new("Design").build(&ui, || {});
|
||||
TabItem::new("Advanced").build(&ui, || {});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ use ggez::Context;
|
|||
use gfx_core::{handle::RenderTargetView, memory::Typed};
|
||||
use gfx_device_gl;
|
||||
|
||||
use imgui::{Context as ImContext, FontId, Key, Ui, Window, FontSource};
|
||||
use imgui::{Context as ImContext, FontId, FontSource, Key, Ui};
|
||||
use imgui_gfx_renderer::*;
|
||||
|
||||
use std::time::Instant;
|
||||
|
|
|
@ -20,11 +20,8 @@ use std::path::PathBuf;
|
|||
use anyhow::Result;
|
||||
use ggez::{
|
||||
conf::{WindowMode, WindowSetup},
|
||||
event, graphics, ContextBuilder,
|
||||
event, 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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue