diff --git a/.gitignore b/.gitignore index 0b42d2d..2bf5b8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/target +/target +/songs diff --git a/src/game/events.rs b/src/game/events.rs index f8f3b9e..f055f19 100644 --- a/src/game/events.rs +++ b/src/game/events.rs @@ -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 { diff --git a/src/game/hitobjects.rs b/src/game/hitobjects.rs new file mode 100644 index 0000000..a608c16 --- /dev/null +++ b/src/game/hitobjects.rs @@ -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(()) + } +} diff --git a/src/game/mod.rs b/src/game/mod.rs index 7e11178..026d13c 100644 --- a/src/game/mod.rs +++ b/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, + ui_state: Option, frame: usize, slider_cache: SliderCache, + seeker_drag: bool, seeker_cache: Option>, combo_colors: Vec, selected_objects: Vec, @@ -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 ").build(ui); - MenuItem::new("Create Difficulty").build(ui); - ui.separator(); - MenuItem::new("Revert to Saved ").build(ui); - MenuItem::new("Export...").build(ui); - ui.separator(); - MenuItem::new("Open Song Folder").build(ui); - MenuItem::new("Exit ").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; diff --git a/src/game/seeker.rs b/src/game/seeker.rs index e23c510..6e6e8f7 100644 --- a/src/game/seeker.rs +++ b/src/game/seeker.rs @@ -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(()) } } diff --git a/src/game/ui.rs b/src/game/ui.rs new file mode 100644 index 0000000..d74f3a8 --- /dev/null +++ b/src/game/ui.rs @@ -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 ").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 ").build(ui); + ui.separator(); + MenuItem::new("Open Song Folder").build(ui); + MenuItem::new("Exit ").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(()) + } +} diff --git a/src/imgui_wrapper.rs b/src/imgui_wrapper.rs index 44409c8..7e04bab 100644 --- a/src/imgui_wrapper.rs +++ b/src/imgui_wrapper.rs @@ -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; diff --git a/src/main.rs b/src/main.rs index 812d8ab..4a9ff9b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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;