diff --git a/src/main.rs b/src/main.rs index 5603172..80c572f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ use quicksilver::{ geom::{Circle, Rectangle, Vector}, - graphics::{Background::{Col, Img}, Color, Font, FontStyle, Image}, + graphics::{ + Background::{Col, Img}, + Color, Font, FontStyle, Image, + }, input::{ButtonState, Key}, lifecycle::{run, Asset, Event, Settings, State, Window}, sound::Sound, @@ -9,7 +12,9 @@ use quicksilver::{ use rand::prelude::*; const WIDTH_F: f32 = 800.0; -const WIDTH_H: f32 = 600.0; +const HEIGHT_F: f32 = 600.0; +const MUSIC2_LENGTH: f64 = 2.0 * 60.0 * 1000.0; +const TEXT_RATE: f64 = 100.0; enum MenuItemType { Button { @@ -19,6 +24,18 @@ enum MenuItemType { h_c: Color, c: Color, }, + AppearingText { + text: &'static str, + text_image: Option, + current_text: String, + text_size: f32, + text_c: Color, + timer: f64, + }, + Pause { + timer: f64, + length: f64, + }, } struct MenuItem { @@ -67,6 +84,66 @@ impl Menu { Menu { items: vec![item] } } + + fn text(x: f32, y: f32, text_size: f32, first: bool, s: &'static str) -> MenuItem { + MenuItem { + x, + y, + w: 0.0, + h: 0.0, + item_type: MenuItemType::AppearingText { + text: s, + text_image: None, + text_size, + current_text: String::new(), + text_c: Color::WHITE, + timer: 0.0, + }, + is_hover: false, + is_focus: false, + is_loaded: !first, + } + } + + fn pause(length: f64, first: bool) -> MenuItem { + MenuItem { + x: 0.0, + y: 0.0, + w: 0.0, + h: 0.0, + item_type: MenuItemType::Pause { timer: 0.0, length }, + is_hover: false, + is_focus: false, + is_loaded: !first, + } + } + + fn s_01() -> Menu { + Menu { + items: vec![ + Menu::pause(500.0, true), + Menu::text(50.0, HEIGHT_F - 140.0, 40.0, false, "This is how it is."), + Menu::pause(500.0, false), + Menu::text( + 50.0, + HEIGHT_F - 100.0, + 40.0, + false, + "Nothing is, and everything is nothing.", + ), + Menu::pause(500.0, false), + Menu::text(50.0, HEIGHT_F - 60.0, 40.0, false, "...until you appeared."), + Menu::pause(100.0, false), + Menu::text( + 570.0, + HEIGHT_F - 50.0, + 30.0, + false, + "(Click to continue...)", + ), + ], + } + } } struct GameState { @@ -77,8 +154,15 @@ struct GameState { s_speak_m: Asset, s_speak_f: Asset, font: Asset, - timer: f64, + music2: Asset, + music_on: bool, + music_timer: f64, menu: Menu, + state: u32, + state_dirty: bool, + selection_mode: bool, + current_item: Option, + current_finished: bool, } impl State for GameState { @@ -91,21 +175,93 @@ impl State for GameState { s_speak_m: Asset::new(Sound::load("speak_m.mp3")), s_speak_f: Asset::new(Sound::load("speak_f.mp3")), font: Asset::new(Font::load("ClearSans-Regular.ttf")), - timer: 0.0, + music2: Asset::new(Sound::load("music2.mp3")), + music_on: false, + music_timer: 0.0, menu: Menu::start(), + state: 0, + state_dirty: false, + selection_mode: true, + current_item: None, + current_finished: true, }) } fn event(&mut self, event: &Event, window: &mut Window) -> Result<()> { match event { - Event::MouseMoved(v) => - for mi in &mut self.menu.items { - if mi.is_inside(v.x, v.y) { - mi.is_hover = true; + Event::MouseMoved(v) => { + let mut hovered = false; + for i in 0..self.menu.items.len() { + if self.menu.items[i].is_inside(v.x, v.y) { + self.menu.items[i].is_hover = true; + self.current_item = Some(i); + hovered = true; } else { - mi.is_hover = false; + self.menu.items[i].is_hover = false; } - }, + } + if !hovered { + self.current_item = None; + } + } + Event::MouseButton(button, state) => { + if let ButtonState::Pressed = state { + if self.current_finished { + if self.selection_mode { + if let Some(idx) = self.current_item { + match self.state { + 0 => { + self.state += 1; + self.state_dirty = true; + } + _ => (), + } + } + } else { + self.state += 1; + self.state_dirty = true; + } + } else { + for mi in &mut self.menu.items { + match &mut mi.item_type { + MenuItemType::AppearingText { + text, + text_image, + current_text, + text_size, + text_c, + timer, + } => { + self.font.execute(|f| { + *current_text = text.to_string(); + let style = FontStyle::new(*text_size, *text_c); + *text_image = Some(f.render(text, &style)?); + Ok(()) + })?; + } + MenuItemType::Button { + text, + text_image, + text_c, + h_c, + c, + } => { + if text_image.is_none() { + self.font.execute(|font| { + let style = FontStyle::new(42.0, *text_c); + *text_image = Some(font.render(text, &style)?); + Ok(()) + })?; + } + } + MenuItemType::Pause { timer, length } => (), + } + mi.is_loaded = true; + } + self.current_finished = true; + } + } + } _ => (), } Ok(()) @@ -113,19 +269,100 @@ impl State for GameState { fn update(&mut self, window: &mut Window) -> Result<()> { let dt = window.update_rate(); - self.timer += dt; - for mi in &mut self.menu.items { + + if self.state_dirty { + self.state_dirty = false; + match self.state { + 1 => { + self.menu = Menu::s_01(); + self.current_finished = false; + self.selection_mode = false; + } + _ => { + self.menu = Menu::start(); + self.current_item = None; + self.selection_mode = true; + self.state = 0; + } + } + } + + if self.music_on { + self.music_timer += dt; + if self.music_timer > MUSIC2_LENGTH { + self.music_timer = 0.0; + self.music2.execute(|m2| m2.play())?; + } + } + for i in 0..self.menu.items.len() { + let mi: &mut MenuItem = &mut self.menu.items[i]; if !mi.is_loaded { match &mut mi.item_type { - MenuItemType::Button { text, text_image, text_c, h_c, c } => { + MenuItemType::Button { + text, + text_image, + text_c, + h_c, + c, + } => { self.font.execute(|font| { let style = FontStyle::new(42.0, *text_c); *text_image = Some(font.render(text, &style)?); Ok(()) })?; + mi.is_loaded = true; + if i + 1 < self.menu.items.len() { + self.menu.items[i + 1].is_loaded = false; + } else { + self.current_finished = true; + } + } + MenuItemType::AppearingText { + text, + text_image, + current_text, + text_size, + text_c, + timer, + } => { + *timer += dt; + if *timer > TEXT_RATE { + *timer -= TEXT_RATE; + let next = text.chars().nth(current_text.len()); + if let Some(next_t) = next { + current_text.push(next_t); + self.s_speak_f.execute(|s| { + s.set_volume(0.3); + s.play() + })?; + } else { + mi.is_loaded = true; + if i + 1 < self.menu.items.len() { + self.menu.items[i + 1].is_loaded = false; + } else { + self.current_finished = true; + } + continue; + } + self.font.execute(|font| { + let style = FontStyle::new(*text_size, *text_c); + *text_image = Some(font.render(current_text, &style)?); + Ok(()) + })?; + } + } + MenuItemType::Pause { timer, length } => { + *timer += dt; + if timer > length { + mi.is_loaded = true; + if i + 1 < self.menu.items.len() { + self.menu.items[i + 1].is_loaded = false; + } else { + self.current_finished = true; + } + } } } - mi.is_loaded = true; } } Ok(()) @@ -140,7 +377,13 @@ impl State for GameState { rect.size.x = mi.w; rect.size.y = mi.h; match &mut mi.item_type { - MenuItemType::Button{ text, text_image, text_c, h_c, c } => { + MenuItemType::Button { + text, + text_image, + text_c, + h_c, + c, + } => { if mi.is_hover { window.draw(&rect, Col(*h_c)); } else { @@ -150,11 +393,25 @@ impl State for GameState { let mut image_rect = i.area(); image_rect.pos.x = mi.x + (mi.w - image_rect.size.x) / 2.0; image_rect.pos.y = mi.y + (mi.h - image_rect.size.y) / 2.0; - window.draw( - &image_rect, - Img(i)); + window.draw(&image_rect, Img(i)); } - }, + } + MenuItemType::AppearingText { + text, + text_image, + current_text, + text_size, + text_c, + timer, + } => { + if let Some(i) = text_image { + let mut image_rect = i.area(); + image_rect.pos.x = mi.x; + image_rect.pos.y = mi.y; + window.draw(&image_rect, Img(i)); + } + } + MenuItemType::Pause { timer, length } => (), } } Ok(()) diff --git a/static/music2.mp3 b/static/music2.mp3 new file mode 100644 index 0000000..b1b22ce Binary files /dev/null and b/static/music2.mp3 differ