/* Hurry Curry! - a game about cooking Copyright (C) 2025 Hurry Curry! Contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ use crate::render::{sprite::SpriteDraw, Renderer}; use hurrycurry_protocol::glam::{IVec2, Vec2}; use sdl2::{ keyboard::{KeyboardState, Keycode, Scancode}, mouse::MouseState, }; #[derive(Default)] pub struct FocusDevice { focus: usize, pressing: Option, interact_just_pressed: bool, interact_just_released: bool, interact_down: bool, } #[derive(Default)] pub struct UiState { mouse_position: Vec2, ui_scale: Vec2, backspace: bool, text_input: String, keyboard_focus: FocusDevice, mouse_focus: FocusDevice, } pub struct Ui<'a, 'b> { cursor: Vec2, size: Vec2, cross_height: f32, index: usize, direction_horizontal: bool, pub renderer: &'a mut Renderer<'b>, state: &'a mut UiState, } impl UiState { pub fn update(&mut self, keyboard: &KeyboardState, mouse: &MouseState, _dt: f32) { self.mouse_position = IVec2::new(mouse.x(), mouse.y()).as_vec2() / self.ui_scale; self.mouse_focus.update(mouse.left()); self.keyboard_focus .update(keyboard.is_scancode_pressed(Scancode::Space)); } pub fn text_input(&mut self, text: String) { self.text_input = text; } pub fn keyboard_event(&mut self, keycode: Keycode, down: bool) { if down { match keycode { Keycode::DOWN => self.keyboard_focus.focus += 1, Keycode::UP if self.keyboard_focus.focus > 0 => self.keyboard_focus.focus -= 1, Keycode::BACKSPACE => self.backspace = true, _ => (), } } } pub fn draw(&mut self, renderer: &mut Renderer, ui: impl FnOnce(&mut Ui)) { self.ui_scale = renderer.ui_scale; self.mouse_focus.focus = usize::MAX; let mut u = Ui { cursor: Vec2::ZERO, direction_horizontal: false, size: renderer.ui_size, renderer, state: self, cross_height: 0., index: 0, }; ui(&mut u); self.end() } fn end(&mut self) { if self.mouse_focus.interact_just_released { self.mouse_focus.pressing = None; } if self.keyboard_focus.interact_just_released { self.keyboard_focus.pressing = None; } self.text_input.clear(); self.backspace = false; } } impl FocusDevice { pub fn update(&mut self, interact: bool) { self.interact_just_pressed = interact && !self.interact_down; self.interact_just_released = !interact && self.interact_down; self.interact_down = interact; } pub fn element(&mut self, index: usize) -> (bool, bool, bool) { let focus = self.focus == index; if focus && self.interact_just_pressed { self.pressing = Some(index) }; let pressing = self.pressing == Some(index); let released = self.interact_just_released && pressing && focus; (focus, pressing, released) } } impl Ui<'_, '_> { pub fn vertical(&mut self, content: impl FnOnce(&mut Ui)) { self.flow(false, content) } pub fn horizontal(&mut self, content: impl FnOnce(&mut Ui)) { self.flow(true, content) } pub fn flow(&mut self, dir: bool, content: impl FnOnce(&mut Ui)) { let d = self.direction_horizontal; let ch = self.cross_height; let c = self.cursor; self.direction_horizontal = dir; self.cross_height = 0.; content(self); let size = (self.cursor - c).max(if dir { Vec2::Y } else { Vec2::X } * self.cross_height); self.direction_horizontal = d; self.cross_height = ch; self.cursor = c; self.advance(size); } pub fn text(&mut self, text: &str) { self.scaled_text(text, 1.) } pub fn small_text(&mut self, text: &str) { self.scaled_text(text, 0.5) } pub fn scaled_text(&mut self, text: &str, scale: f32) { let margin = Vec2::splat(2.); let size = margin + self .renderer .draw_text(self.cursor + margin, text, scale, None) + margin; self.advance(size); } pub fn button(&mut self, w: f32, label: &str) -> bool { let c = self.cursor; let margin = Vec2::splat(4.); let text_size = self .renderer .draw_text(self.cursor + margin, label, 1., None); let size = margin + Vec2::new(w, text_size.y) + margin; self.index += 1; let mouse_rel = self.state.mouse_position - c; if mouse_rel.x >= 0. && mouse_rel.y >= 0. && mouse_rel.x < size.x && mouse_rel.y < size.y { self.state.mouse_focus.focus = self.index; } let (focus, pressing, released) = { let (mfocus, mpressing, mreleased) = self.state.mouse_focus.element(self.index); let (kfocus, kpressing, kreleased) = self.state.keyboard_focus.element(self.index); ( mfocus || kfocus, mpressing || kpressing, mreleased || kreleased, ) }; let l = if pressing { 100 } else if focus { 50 } else { 30 }; self.renderer.draw_ui(SpriteDraw::screen( self.renderer.misc_textures.solid, i32::MAX - 1, c, size, Some([l, l, l, 200]), )); self.advance(size); released } pub fn textedit(&mut self, w: f32, content: &mut String) { let c = self.cursor; let margin = Vec2::splat(4.); let text_size = self .renderer .draw_text(self.cursor + margin, content, 1., None); let size = margin + Vec2::new(w, text_size.y) + margin; self.index += 1; let mouse_rel = self.state.mouse_position - c; if mouse_rel.x >= 0. && mouse_rel.y >= 0. && mouse_rel.x < size.x && mouse_rel.y < size.y { self.state.mouse_focus.focus = self.index; } if self.state.mouse_focus.interact_just_pressed && self.state.mouse_focus.focus == self.index { self.state.keyboard_focus.focus = self.index; } let keyboard_focus = self.state.keyboard_focus.focus == self.index; if keyboard_focus { *content += &self.state.text_input; self.state.text_input.clear(); if self.state.backspace { content.pop(); } } let focus = self.state.mouse_focus.focus == self.index || keyboard_focus; let l = if focus { 50 } else { 30 }; self.renderer.draw_ui(SpriteDraw::screen( self.renderer.misc_textures.solid, i32::MAX - 1, c, size, Some([l, l, l, 200]), )); self.advance(size); } pub fn fill(&mut self) { self.renderer.draw_ui(SpriteDraw::screen( self.renderer.misc_textures.solid, i32::MAX - 1, self.cursor, self.get_remaining(), Some([30, 30, 30, 200]), )); } pub fn get_remaining(&self) -> Vec2 { if self.direction_horizontal { Vec2::new(self.size.x - self.cursor.x, self.cross_height) } else { Vec2::new(self.cross_height, self.size.y - self.cursor.y) } } pub fn advance(&mut self, size: Vec2) { if self.direction_horizontal { self.cursor.x += size.x; self.cross_height = self.cross_height.max(size.y); } else { self.cursor.y += size.y; self.cross_height = self.cross_height.max(size.x); } } }