Minor refactoring

This commit is contained in:
Michael Zhang 2022-01-12 22:24:54 -06:00
parent 205f78fd84
commit 09f42d2cb4
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
8 changed files with 371 additions and 311 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
/songs

View file

@ -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
View 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(())
}
}

View file

@ -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(mut state) = self.ui_state.take() {
self.draw_ui(ctx, &mut state)?;
self.ui_state = Some(state);
}
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);
});
graphics::present(ctx)?;
self.frame += 1;

View file

@ -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,18 +12,6 @@ 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()),
@ -33,11 +20,12 @@ impl Game {
)?;
graphics::draw(ctx, &rect, DrawParam::default())?;
let line_y = BOUNDS.h / 2.0;
// draw the main timeline of the seeker
let line_y = BOUNDS.y + BOUNDS.h / 2.0;
let line = Mesh::new_line(
ctx,
&[
Point2::from([0.0, line_y]),
Point2::from([BOUNDS.x, line_y]),
Point2::from([BOUNDS.w, line_y]),
],
1.0,
@ -48,6 +36,7 @@ impl Game {
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),
@ -55,24 +44,28 @@ impl Game {
};
let percent = timing_point.time.as_seconds() / len;
let x = percent as f32 * BOUNDS.w;
let x = BOUNDS.x + percent as f32 * BOUNDS.w;
let line = Mesh::new_line(
ctx,
&[Point2::from([x, 0.0]), Point2::from([x, BOUNDS.h / 2.0])],
&[
Point2::from([x, BOUNDS.y]),
Point2::from([x, BOUNDS.y + BOUNDS.h / 2.0]),
],
1.0,
color,
)?;
graphics::draw(ctx, &line, DrawParam::default())?;
}
// draw the knob for current position
let percent = song.position()? / len;
let x = percent as f32 * BOUNDS.w;
let x = BOUNDS.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 + 0.2 * BOUNDS.h]),
Point2::from([x, BOUNDS.y + 0.8 * BOUNDS.h]),
],
4.0,
Color::WHITE,
@ -80,19 +73,6 @@ impl Game {
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(
ctx,
canvas,
DrawParam::default()
.dest([BOUNDS.x, BOUNDS.y])
.scale([1.0, 10.0]),
)?;
}
Ok(())
}
}

77
src/game/ui.rs Normal file
View 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(())
}
}

View file

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

View file

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