use std::{collections::HashMap, fs::read_dir, path::PathBuf}; use raylib::prelude::*; use crate::theme::*; #[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 hover = bounds.check_collision_point_rec(mouse_pos); let pressed = hover && d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT); d.draw_rectangle(x, y, width, height, widget_bg(hover)); pressed } #[derive(Debug, Default)] pub struct Tooltip { text: Option<&'static str>, mouse_x: i32, mouse_y: i32, screen_width: i32, screen_height: i32, } impl Tooltip { pub fn init_frame(&mut self, d: &mut RaylibDrawHandle) { let p = d.get_mouse_position(); *self = Self { text: None, mouse_x: p.x as i32, mouse_y: p.y as i32, screen_width: d.get_screen_width(), screen_height: d.get_screen_height(), }; } pub fn add(&mut self, x: i32, y: i32, width: i32, height: i32, text: &'static str) { if self.mouse_x >= x && self.mouse_y >= y && self.mouse_x <= (x + width) && self.mouse_y <= (y + height) { self.text = Some(text); } } pub fn add_rec(&mut self, bounds: Rectangle, text: &'static str) { self.add( bounds.x as i32, bounds.y as i32, bounds.width as i32, bounds.height as i32, text, ); } pub fn draw(&self, d: &mut RaylibDrawHandle) { if let Some(text) = self.text { let font_size = 20; let margin = 4; let text_width = d.measure_text(text, font_size); let x = self .mouse_x .min(self.screen_width - text_width - margin * 2); let y = self .mouse_y .min(self.screen_height - font_size - margin * 2); d.draw_rectangle( x, y, text_width + margin * 2, font_size + margin * 2, BG_LIGHT, ); d.draw_text(text, x + margin, y + margin, font_size, Color::WHITE); } } } pub fn simple_toggle_button( d: &mut RaylibDrawHandle, state: &mut bool, x: i32, y: i32, width: i32, height: i32, margin: i32, ) { 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 hover = bounds.check_collision_point_rec(mouse_pos); d.draw_rectangle(x, y, width, height, widget_bg(hover)); if *state { d.draw_rectangle( x + margin, y + margin, width - margin * 2, height - margin * 2, FG_TOGGLE_ENABLED, ); } if hover && d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) { *state = !*state; } } 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 bounds = Rectangle::new(x as f32, y as f32, width as f32, height as f32); d.draw_rectangle_rec(bounds, widget_bg(&option == current)); 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; } 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; d.draw_rectangle_rec(bounds, widget_bg(*is_selected)); d.draw_rectangle_rec( Rectangle::new( bounds.x + 2., bounds.y + bounds.height - 5., bounds.width - 4., 3., ), BG_DARK, ); 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 bounds = Rectangle { x: pos.x, y: pos.y, width: tex_size + border * 2., height: tex_size + border * 2., }; d.draw_rectangle_rec( bounds, if &option == current { BG_WIDGET_ACTIVE } else { gray(16) }, ); 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, BG_LIGHT); } 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., FG_MARBLE_VALUE); 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, BG_WIDGET); 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); } #[derive(Debug, Clone, Copy, PartialEq)] pub enum Scroll { Up, Down, } pub fn get_scroll(rl: &RaylibHandle) -> Option { const SCROLL_THRESHOLD: f32 = 0.5; let value = rl.get_mouse_wheel_move(); if value > SCROLL_THRESHOLD { Some(Scroll::Up) } else if value < -SCROLL_THRESHOLD { Some(Scroll::Down) } else { None } } pub fn get_free_id(items: &[T], id_fn: fn(&T) -> usize) -> usize { let mut id = 0; while items.iter().any(|i| id_fn(i) == id) { id += 1; } id }