diff --git a/skin/hitcircleselect.png b/skin/hitcircleselect.png new file mode 100644 index 0000000..e9d18df Binary files /dev/null and b/skin/hitcircleselect.png differ diff --git a/skin/hitcircleselect@2x.png b/skin/hitcircleselect@2x.png new file mode 100644 index 0000000..87db85e Binary files /dev/null and b/skin/hitcircleselect@2x.png differ diff --git a/src/game/mod.rs b/src/game/mod.rs index 54438fc..a1a31ab 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -14,7 +14,9 @@ use std::str::FromStr; use anyhow::Result; use ggez::{ event::{EventHandler, KeyCode, KeyMods, MouseButton}, - graphics::{self, Color, DrawParam, FilterMode, Image, Rect, Text, WHITE}, + graphics::{ + self, Color, DrawMode, DrawParam, FilterMode, Image, Mesh, Rect, StrokeOptions, Text, WHITE, + }, Context, GameError, GameResult, }; use image::io::Reader as ImageReader; @@ -30,7 +32,7 @@ use crate::audio::{AudioEngine, Sound}; use crate::beatmap::{BeatmapExt, STACK_DISTANCE}; use crate::hitobject::HitObjectExt; use crate::skin::Skin; -use crate::utils; +use crate::utils::{self, rect_contains}; pub const PLAYFIELD_BOUNDS: Rect = Rect::new(112.0, 122.0, 800.0, 600.0); pub const DEFAULT_COLORS: &[(f32, f32, f32)] = &[ @@ -42,6 +44,7 @@ pub const DEFAULT_COLORS: &[(f32, f32, f32)] = &[ pub type SliderCache = HashMap>, Spline>; +#[derive(Clone, Debug)] pub enum Tool { Select, Circle, @@ -338,7 +341,7 @@ impl Game { ]; self.skin.sliderb.draw_frame( ctx, - (cs_real * 2.0, cs_real * 2.0), + (cs_real * 1.8, cs_real * 1.8), DrawParam::default().dest(ball_pos).color(color), (travel_percent / 0.25) as usize, )?; @@ -361,9 +364,27 @@ impl Game { // draw whatever tool user is using let (mx, my) = self.mouse_pos; match self.tool { + Tool::Select => { + let (mx, my) = self.mouse_pos; + if let Some((dx, dy)) = self.left_drag_start { + if rect_contains(&PLAYFIELD_BOUNDS, dx, dy) { + let ax = dx.min(mx); + let ay = dy.min(my); + let bx = dx.max(mx); + let by = dy.max(my); + let drag_rect = Rect::new(ax, ay, bx - ax, by - ay); + let drag_rect = Mesh::new_rectangle( + ctx, + DrawMode::Stroke(StrokeOptions::default()), + drag_rect, + WHITE, + )?; + graphics::draw(ctx, &drag_rect, DrawParam::default())?; + } + } + } Tool::Circle => { - let b = PLAYFIELD_BOUNDS; - if mx > b.x && mx < b.x + b.h && my > b.y && my < b.y + b.h { + if rect_contains(&PLAYFIELD_BOUNDS, mx, my) { let pos = [mx, my]; let color = Color::new(1.0, 1.0, 1.0, 0.4); self.skin.hitcircle.draw( @@ -460,6 +481,56 @@ impl Game { } fn handle_click(&mut self, btn: MouseButton, x: f32, y: f32) -> Result<()> { + println!("handled click {}", self.song.is_some()); + if let Some(song) = &self.song { + println!("song exists! {:?} {:?}", btn, self.tool); + 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; + match self + .beatmap + .hit_objects + .binary_search_by_key(&time, |ho| ho.inner.start_time.0) + { + Ok(v) => { + println!("unfortunately already found at idx {}", v); + } + Err(idx) => { + use libosu::{ + hitobject::HitObject, + hitsounds::{Additions, SampleInfo}, + timing::TimestampMillis, + }; + + let pos_x = (x - PLAYFIELD_BOUNDS.x) / PLAYFIELD_BOUNDS.w * 512.0; + let pos_y = (y - PLAYFIELD_BOUNDS.y) / PLAYFIELD_BOUNDS.h * 384.0; + let inner = HitObject { + start_time: TimestampMillis(time), + pos: Point::new(pos_x as i32, pos_y as i32), + kind: HitObjectKind::Circle, + new_combo: false, + skip_color: 0, + additions: Additions::empty(), + sample_info: SampleInfo::default(), + timing_point: None, + }; + let new_obj = HitObjectExt { + inner, + stacking: 0, + color_idx: 0, + number: 0, + }; + println!("creating new hitobject: {:?}", new_obj); + self.beatmap.hit_objects.insert(idx, new_obj); + self.beatmap.compute_stacking(); + self.beatmap.compute_colors(&self.combo_colors); + } + } + } + } + } Ok(()) } } @@ -477,11 +548,7 @@ impl EventHandler for Game { match btn { MouseButton::Left => { use self::seeker::BOUNDS; - if x > BOUNDS.x - && x < BOUNDS.x + BOUNDS.w - && y > BOUNDS.y - && y < BOUNDS.y + BOUNDS.h - { + if rect_contains(&BOUNDS, x, y) { let jump_percent = (x - BOUNDS.x) / BOUNDS.w; if let Some(song) = &self.song { let pos = jump_percent as f64 * song.length().unwrap(); diff --git a/src/game/seeker.rs b/src/game/seeker.rs index 2a629d1..859e824 100644 --- a/src/game/seeker.rs +++ b/src/game/seeker.rs @@ -1,6 +1,6 @@ use anyhow::Result; use ggez::{ - graphics::{self, Color, DrawParam, Mesh, Rect}, + graphics::{self, Color, DrawMode, DrawParam, FillOptions, Mesh, Rect}, mint::Point2, Context, }; @@ -12,6 +12,14 @@ pub const BOUNDS: Rect = Rect::new(46.0, 732.0, 932.0, 36.0); impl Game { pub(super) fn draw_seeker(&self, ctx: &mut Context) -> Result<()> { + 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 line_y = BOUNDS.y + BOUNDS.h / 2.0; let line = Mesh::new_line( ctx, diff --git a/src/hitobject.rs b/src/hitobject.rs index 068ced8..b52bc9c 100644 --- a/src/hitobject.rs +++ b/src/hitobject.rs @@ -1,5 +1,6 @@ use libosu::hitobject::HitObject; +#[derive(Debug)] pub struct HitObjectExt { pub inner: HitObject, pub stacking: usize, diff --git a/src/utils.rs b/src/utils.rs index c2add81..e0df1f4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,7 @@ use std::path::{Path, PathBuf}; use anyhow::Result; +use ggez::graphics::Rect; pub fn fuck_you_windows( parent: impl AsRef, @@ -20,3 +21,7 @@ pub fn fuck_you_windows( Ok(None) } + +pub fn rect_contains(rect: &Rect, x: f32, y: f32) -> bool { + x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h +}