use std::{ cell::RefCell, collections::{HashMap, HashSet}, rc::Rc, }; mod gui; use clay_layout::render_commands::RenderCommandConfig; use gui::GUI; use raylib::{ RaylibHandle, RaylibThread, color::Color, ffi::{MouseButton, TextLength, Vector2}, prelude::{Font, RaylibDraw, RaylibDrawHandle}, text::RaylibFont, texture::Texture2D, }; struct FontStore { font: String, fonts: HashMap>, fonts_to_be_loaded: HashSet, } impl FontStore { // const SIZE_PARAGRAPH: f32 = 20.; // const SIZE_HEADER: f32 = 30.; // const MOD_SUBTEXT: f32 = 0.5; pub fn new(name: impl Into) -> Self { Self { fonts: HashMap::new(), font: name.into(), fonts_to_be_loaded: HashSet::new(), } } pub fn get(&mut self, size: i32) -> Option> { let size = size.max(1); if let Some(font) = self.fonts.get(&size) { Some(font.clone()) } else { self.fonts_to_be_loaded.insert(size); None } } // pub fn get_no_load(&self, size: i32) -> Option> { // if let Some(font) = self.fonts.get(&size) { // Some(font.clone()) // } else { // None // } // } pub fn load_fonts( &mut self, handle: &mut RaylibHandle, thread: &RaylibThread, ) -> Result { let invalidate = self.fonts_to_be_loaded.len() > 0; if invalidate { for size in self.fonts_to_be_loaded.clone() { assert!(size != 0, "HOW IS THIS ZERO?"); println!("Loading {} in {size}px", self.font); let font = Rc::new(handle.load_font_ex(&thread, &self.font, size, None)?); self.fonts.insert(size, font.clone()); } self.fonts_to_be_loaded.clear(); } Ok(invalidate) } pub fn text( &mut self, d: &mut RaylibDrawHandle, text: &str, x: f32, y: f32, size: f32, letter_spacing: u16, color: Option, ) { // Program crashes at size=0. let size = if size == 0. { 0.1 } else { size }; if let Some(font) = self.get(size as i32) { d.draw_text_ex( font.as_ref(), text, Vector2 { x, y }, size, letter_spacing.into(), color.unwrap_or(Color::BLUE), ); } } } #[inline] fn clay2ray(c: clay_layout::color::Color) -> Color { Color { r: c.r as u8, g: c.g as u8, b: c.b as u8, a: c.a as u8, } } fn main() -> Result<(), String> { println!("Openbirch GUI"); let (mut rl, thread) = raylib::init() .size(640, 480) .title("Openbirch GUI") .msaa_4x() .resizable() .build(); let mut clay = clay_layout::Clay::new((640., 480.).into()); let font_store = Rc::new(RefCell::new(FontStore::new("adamina.ttf"))); let font_store_math = Rc::new(RefCell::new(FontStore::new("xits-italic.ttf"))); let mut gui = GUI::new(); let mut previous_left_button = false; let mut previous_right_button = false; // stupid fucking 'static closures let font_store_clone = font_store.clone(); let font_store_math_clone = font_store_math.clone(); clay.set_measure_text_function(move |text, config| { let mut text_size = clay_layout::math::Dimensions::new(0., 0.); let mut max_text_width: f32 = 0.; let mut line_text_width: f32 = 0.; let font_store = match config.font_id { 0 => font_store_clone.clone(), 1 => font_store_math_clone.clone(), _ => panic!("Unknown font {}", config.font_id), }; let font = if let Some(font) = font_store.borrow_mut().get(config.font_size.into()) { font } else { // println!("Font not cached. Returning: {:?}", text_size); return text_size; }; let scale_factor = config.font_size as f32 / font.baseSize as f32; let text_height = config.font_size as f32; for char in text.chars() { if char == '\n' { max_text_width = max_text_width.max(line_text_width); line_text_width = 0.; continue; } let idx = char as u32 - 32; line_text_width += if let Some(glyph) = font.chars().get(idx as usize) { if glyph.advanceX > 0 { glyph.advanceX as f32 } else { font.get_glyph_atlas_rec(char).width + glyph.offsetX as f32 } } else { 0 as f32 } + config.letter_spacing as f32; } max_text_width = max_text_width.max(line_text_width); text_size.width = max_text_width * scale_factor; text_size.height = text_height; text_size // unsafe { // std::mem::transmute(font.measure_text(text, config.font_size as f32, config.letter_spacing as f32 + 1.)) // } }); rl.set_target_fps(60); rl.set_exit_key(None); let mut debug = false; while !rl.window_should_close() { if rl.is_window_resized() { let size = (rl.get_screen_width() as f32, rl.get_screen_height() as f32); clay.layout_dimensions(size.into()); println!("Resized window to {:?}", size); } let left_button = rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT); let right_button = rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT); { if font_store.borrow_mut().load_fonts(&mut rl, &thread)? || font_store_math.borrow_mut().load_fonts(&mut rl, &thread)? { clay.reset_measure_text_cache(); } } if rl.is_key_down(raylib::ffi::KeyboardKey::KEY_RIGHT_ALT) && rl.is_key_pressed(raylib::ffi::KeyboardKey::KEY_D) { debug = !clay.debug_mode_enabled(); clay.reset_measure_text_cache(); clay.enable_debug_mode(debug); println!("Toggled debug mode to {debug}"); } clay.set_debug_highlight((180, 142, 173, 150).into()); clay.update_scroll_containers( false, unsafe { std::mem::transmute(rl.get_mouse_wheel_move_v()) }, rl.get_frame_time(), ); clay.pointer_state( unsafe { std::mem::transmute(rl.get_mouse_position()) }, left_button, ); let render_commands = gui.render( &clay, (left_button, left_button && !previous_left_button), (right_button, right_button && !previous_right_button), (rl.get_char_pressed(), rl.get_key_pressed_number()), rl.get_time() ); let mut d = rl.begin_drawing(&thread); d.clear_background(Color::WHITE); for command in render_commands { match command.config { RenderCommandConfig::Rectangle(rectangle) => { // println!( // "RECT!!! {}: {:?}\n{:?}\n\n", // command.id, command.bounding_box, rectangle // ); // d.draw_rectangle_rounded( raylib::ffi::Rectangle { x: command.bounding_box.x, y: command.bounding_box.y, width: command.bounding_box.width, height: command.bounding_box.height, }, rectangle.corner_radii.top_left, 8, clay2ray(rectangle.color), ); } RenderCommandConfig::Text(text) => { let font = match text.font_id { 0 => font_store.clone(), 1 => font_store_math.clone(), _ => panic!("Unknown font {}", text.font_id), }; font.borrow_mut().text( &mut d, text.text, command.bounding_box.x, command.bounding_box.y, text.font_size as f32, text.letter_spacing, Some(clay2ray(text.color)), ); } RenderCommandConfig::ScissorStart() => unsafe { raylib::ffi::BeginScissorMode( command.bounding_box.x as i32, command.bounding_box.y as i32, command.bounding_box.width as i32, command.bounding_box.height as i32, ); }, RenderCommandConfig::ScissorEnd() => unsafe { raylib::ffi::EndScissorMode(); }, RenderCommandConfig::Border(border) => { // Left border if border.width.left > 0 { d.draw_rectangle( command.bounding_box.x as i32, (command.bounding_box.y + border.corner_radii.top_left) as i32, border.width.left as i32, (command.bounding_box.height - border.corner_radii.top_left - border.corner_radii.bottom_left) as i32, clay2ray(border.color), ); } // Right border if border.width.right > 0 { d.draw_rectangle( (command.bounding_box.x + command.bounding_box.width - border.width.right as f32) as i32, (command.bounding_box.y + border.corner_radii.top_right) as i32, border.width.right as i32, (command.bounding_box.height - border.corner_radii.top_right - border.corner_radii.bottom_right) as i32, clay2ray(border.color), ); } // Top border if border.width.top > 0 { d.draw_rectangle( (command.bounding_box.x + border.corner_radii.top_left) as i32, (command.bounding_box.y) as i32, (command.bounding_box.width - border.corner_radii.top_left - border.corner_radii.top_right) as i32, (border.width.top) as i32, clay2ray(border.color), ); } // Top border if border.width.bottom > 0 { d.draw_rectangle( (command.bounding_box.x + border.corner_radii.bottom_left) as i32, (command.bounding_box.y + command.bounding_box.height - border.width.bottom as f32) as i32, (command.bounding_box.width - border.corner_radii.bottom_left - border.corner_radii.bottom_right) as i32, border.width.bottom as i32, clay2ray(border.color), ); } // Top left corner if border.corner_radii.top_left > 0. { d.draw_ring( Vector2 { x: command.bounding_box.x + border.corner_radii.top_left, y: command.bounding_box.y + border.corner_radii.top_left, }, border.corner_radii.top_left - border.width.top as f32, border.corner_radii.top_left, 180., 270., 10, clay2ray(border.color), ); } // Top right corner if border.corner_radii.top_right > 0. { d.draw_ring( Vector2 { x: command.bounding_box.x + command.bounding_box.width - border.corner_radii.top_right, y: command.bounding_box.y + border.corner_radii.top_right, }, border.corner_radii.top_right - border.width.top as f32, border.corner_radii.top_right, 270., 360., 10, clay2ray(border.color), ); } // Bottom left if border.corner_radii.bottom_left > 0. { d.draw_ring( Vector2 { x: command.bounding_box.x + border.corner_radii.bottom_left, y: command.bounding_box.y + command.bounding_box.height - border.corner_radii.bottom_left, }, border.corner_radii.bottom_left - border.width.top as f32, border.corner_radii.bottom_left, 90., 180., 10, clay2ray(border.color), ); } // Bottom right if border.corner_radii.bottom_right > 0. { d.draw_ring( Vector2 { x: command.bounding_box.x + command.bounding_box.width - border.corner_radii.bottom_right, y: command.bounding_box.y + command.bounding_box.height - border.corner_radii.bottom_right, }, border.corner_radii.bottom_right - border.width.bottom as f32, border.corner_radii.bottom_right, 0., 90., 10, clay2ray(border.color), ); } } RenderCommandConfig::None() => {} RenderCommandConfig::Image(image) => { let texture: &Texture2D = unsafe { std::mem::transmute::<*const std::ffi::c_void, *const Texture2D>(image.data) .as_ref() .unwrap() }; d.draw_texture_ex( texture, Vector2 { x: command.bounding_box.x, y: command.bounding_box.y, }, 0., command.bounding_box.width / image.dimensions.width, clay2ray(image.background_color), ); } _ => panic!("Unimplemented {:#?}", command), } } previous_left_button = left_button; previous_right_button = right_button; } Ok(()) }