/*
Hurry Curry! - a game about cooking
Copyright 2024 metamuffin
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);
}
}
}