use std::{collections::HashMap, fs::read_dir, path::PathBuf}; use raylib::prelude::*; #[derive(Default)] pub struct Textures { map: HashMap, } impl Textures { pub fn load_dir(&mut self, folder: &str, rl: &mut RaylibHandle, thread: &RaylibThread) { for d in read_dir(folder).unwrap().flatten() { let path = d.path(); if path.is_file() { let name = path.file_stem().unwrap().to_string_lossy(); let texture = rl .load_texture(thread, &format!("{folder}/{name}.png")) .unwrap(); self.map.insert(name.to_string(), texture); } } } pub fn get(&self, name: &str) -> &Texture2D { self.map .get(name) .unwrap_or_else(|| self.map.get("missing").unwrap()) } } pub fn simple_button(d: &mut RaylibDrawHandle, x: i32, y: i32, width: i32, height: i32) -> bool { let mouse_pos = d.get_mouse_position(); let bounds = Rectangle { x: x as f32, y: y as f32, width: width as f32, height: height as f32, }; let mut pressed = false; let color = if bounds.check_collision_point_rec(mouse_pos) { if d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) { pressed = true; } Color::DARKCYAN } else { Color::GRAY }; d.draw_rectangle(x, y, width, height, color); pressed } pub fn simple_option_button( d: &mut RaylibDrawHandle, x: i32, y: i32, width: i32, height: i32, option: T, current: &mut T, ) -> bool where T: PartialEq, { let color = if &option == current { Color::DARKCYAN } else { Color::GRAY }; let bounds = Rectangle { x: x as f32, y: y as f32, width: width as f32, height: height as f32, }; let mouse_pos = d.get_mouse_position(); let mut changed = false; if d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) && bounds.check_collision_point_rec(mouse_pos) && current != &option { *current = option; changed = true; } d.draw_rectangle_rec(bounds, color); changed } pub fn text_input( d: &mut RaylibDrawHandle, bounds: Rectangle, text: &mut String, is_selected: &mut bool, max_len: usize, editable: bool, ) -> bool { let mut changed = false; let (bg, underline) = if *is_selected { (Color::DARKCYAN, Color::CYAN) } else { (Color::GRAY, Color::DIMGRAY) }; d.draw_rectangle_rec(bounds, bg); d.draw_rectangle_rec( Rectangle::new( bounds.x + 2., bounds.y + bounds.height - 5., bounds.width - 4., 3., ), underline, ); let drawn_text = if *is_selected { &format!("{text}_") } else { text.as_str() }; d.draw_text( drawn_text, bounds.x as i32 + 4, bounds.y as i32 + 4, 20, Color::WHITE, ); let mouse_pos = d.get_mouse_position(); if editable && d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) && (bounds.check_collision_point_rec(mouse_pos) || *is_selected) { *is_selected = !*is_selected; } if *is_selected { if d.is_key_pressed(KeyboardKey::KEY_ESCAPE) { *is_selected = false; } if d.is_key_pressed(KeyboardKey::KEY_BACKSPACE) && !text.is_empty() { changed = true; text.pop(); } if text.len() < max_len { let char_code = unsafe { ffi::GetCharPressed() }; let c = if char_code > 0 { char::from_u32(char_code as u32) } else { None }; if let Some(c) = c { changed = true; text.push(c); } } } changed } pub fn texture_option_button( d: &mut RaylibDrawHandle, pos: Vector2, texture: &Texture2D, option: T, current: &mut T, tex_size: f32, border: f32, // tooltip ) where T: PartialEq, { let color = if &option == current { Color::DARKCYAN } else { Color::GRAY }; let bounds = Rectangle { x: pos.x, y: pos.y, width: tex_size + border * 2., height: tex_size + border * 2., }; d.draw_rectangle_rec(bounds, color); d.draw_texture_ex( texture, pos + Vector2::new(border, border), 0., tex_size / texture.width as f32, Color::WHITE, ); let mouse_pos = d.get_mouse_position(); if d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) && bounds.check_collision_point_rec(mouse_pos) { *current = option; } } pub fn draw_usize( d: &mut RaylibDrawHandle, textures: &Textures, number: usize, x: i32, y: i32, digits: u8, scale: u8, ) { let digits = digits as i32; let scale = scale as i32; for i in 0..digits { d.draw_rectangle(x + 10 * i * scale, y, 8 * scale, 16 * scale, Color::DIMGRAY); } let mut num = number; let mut i = 0; while (num != 0 || i == 0) && i < digits { let texture = textures.get(&format!("digit_{}", num % 10)); let x = x + (digits - i - 1) * 10 * scale; draw_scaled_texture(d, texture, x, y, scale as f32); num /= 10; i += 1; } } pub fn draw_usize_small( d: &mut RaylibDrawHandle, textures: &Textures, mut num: usize, mut x: i32, y: i32, scale: f32, ) { const MAX_DIGITS: usize = 8; let mut digits = [0; MAX_DIGITS]; let mut i = 0; while (num != 0 || i == 0) && i < MAX_DIGITS { digits[MAX_DIGITS - i - 1] = num % 10; num /= 10; i += 1; } let texture = textures.get("digits_small"); for &digit in &digits[(MAX_DIGITS - i)..] { let source = Rectangle::new(4. * digit as f32, 0., 4., 6.); let dest = Rectangle::new(x as f32, y as f32, 4. * scale, 6. * scale); d.draw_texture_pro(texture, source, dest, Vector2::zero(), 0., Color::RED); x += 4 * scale as i32; } } pub fn slider( d: &mut RaylibDrawHandle, value: &mut u8, min: u8, max: u8, x: i32, y: i32, width: i32, height: i32, ) -> bool { // the +1 makes the lowest state look slightly filled and the max state fully filled let percent = (*value - min + 1) as f32 / (max - min + 1) as f32; d.draw_rectangle(x, y, width, height, Color::DIMGRAY); d.draw_rectangle(x, y, (width as f32 * percent) as i32, height, Color::CYAN); let mouse_pos = d.get_mouse_position(); let bounds = Rectangle::new(x as f32, y as f32, width as f32, height as f32); if bounds.check_collision_point_rec(mouse_pos) { if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) { let percent = (mouse_pos.x - bounds.x) / bounds.width; let new_value = min + (percent * (max - min + 1) as f32) as u8; if *value != new_value { *value = new_value } } else if d.get_mouse_wheel_move() > 0.5 && *value < max { *value += 1; } else if d.get_mouse_wheel_move() < -0.5 && *value > min { *value -= 1; } } false } pub fn userdata_dir() -> PathBuf { PathBuf::from("user") } pub fn draw_scaled_texture( d: &mut RaylibDrawHandle, texture: &Texture2D, x: i32, y: i32, scale: f32, ) { let pos = Vector2::new(x as f32, y as f32); d.draw_texture_ex(texture, pos, 0., scale, Color::WHITE); }