use macroquad::color::{BLACK, Color}; use macroquad::math::Vec2; use macroquad::prelude::{Font, TextDimensions}; use macroquad::text::{TextParams, draw_text_ex, measure_text}; #[allow(dead_code)] #[derive(Debug, Clone, Copy, Default)] pub enum Justified { Right, Left, #[default] Center, CenterVertical, CenterHorizontal, } impl Justified { pub fn text_offset(&self, text_size: TextDimensions) -> Vec2 { match self { Justified::Right => Vec2::new(text_size.width, 0.0) * 0.5, Justified::Left => Vec2::new(0.0, 0.0), Justified::Center => -Vec2::new(text_size.width, text_size.height) * 0.5, Justified::CenterVertical => -Vec2::new(0.0, text_size.height) * 0.5, Justified::CenterHorizontal => -Vec2::new(text_size.width, 0.0) * 0.5, } } } #[derive(Debug, Default, Clone)] pub struct TextConfigBuilder<'a> { text_config: TextConfig<'a>, } #[allow(dead_code)] impl<'a> TextConfigBuilder<'a> { pub fn new(text_config: TextConfig<'a>) -> Self { Self { text_config } } pub fn font(mut self, font: &'a Font) -> Self { self.text_config.font = Some(font); self } pub fn font_size(mut self, font_size: u16) -> Self { self.text_config.font_size = font_size; self } pub fn font_scale(mut self, font_scale: f32) -> Self { self.text_config.font_scale = font_scale; self } pub fn font_scale_aspect(mut self, font_scale_aspect: f32) -> Self { self.text_config.font_scale_aspect = font_scale_aspect; self } pub fn rotation(mut self, rotation: f32) -> Self { self.text_config.rotation = rotation; self } pub fn color(mut self, color: Color) -> Self { self.text_config.color = color; self } pub fn justification(mut self, justification: Justified) -> Self { self.text_config.justification = justification; self } pub fn build(self) -> TextConfig<'a> { self.text_config } } #[derive(Debug)] pub struct TextInfo { pub text_dimensions: TextDimensions, pub pos: Vec2, } #[derive(Debug, Clone)] pub struct TextConfig<'a> { font: Option<&'a Font>, font_size: u16, font_scale: f32, font_scale_aspect: f32, rotation: f32, color: Color, justification: Justified, } impl<'a> Default for TextConfig<'a> { fn default() -> Self { Self { font: None, font_size: 12, font_scale: 1.0, font_scale_aspect: 1.0, rotation: 0.0, color: BLACK, justification: Default::default(), } } } impl<'a> TextConfig<'a> { pub fn draw_text(&self, text: &str, pos: Vec2) -> TextInfo { let text_size = self.measure_text(text); let text_params = TextParams { font: self.font, font_size: self.font_size, font_scale: self.font_scale, font_scale_aspect: self.font_scale_aspect, rotation: self.rotation, color: self.color, }; let text_pos = pos + self.justification.text_offset(text_size); draw_text_ex(text, text_pos.x, text_pos.y, text_params); TextInfo { text_dimensions: text_size, pos, } } pub fn draw_text_box(&self, text: &str, pos: Vec2, size: Vec2) -> TextInfo { let mut lines: Vec = text.split("\n").map(|s| s.to_string()).collect(); let mut line_pos = pos; let standard_line_height = self.measure_text("A").height; let mut line_ndx = 0; loop { if line_ndx >= lines.len() { break; } let line = &lines[line_ndx]; let line: Vec<&str> = line.split(" ").collect(); let mut word_pos = line_pos; let mut need_line_break = false; for word_ndx in 0..line.len() { let word = line[word_ndx]; let measure_text = self.measure_text(&format!("{word} ")); if measure_text.width + word_pos.x > size.x { let new_line = format!("\t\t\t{}", line[word_ndx..].join(" ")); lines.insert(line_ndx + 1, new_line); need_line_break = true; break; } let word_info = self.draw_text(&format!("{word} "), word_pos); word_pos.x += word_info.text_dimensions.width; } line_ndx += 1; if need_line_break { line_pos.y += standard_line_height * 1.5; } else { line_pos.y += standard_line_height * 2.0; } if line_pos.y > size.y { // Stop drawing text break; } } TextInfo { text_dimensions: TextDimensions { width: size.x, height: size.y, offset_y: 0.0, }, pos, } } pub fn measure_text(&self, text: &str) -> TextDimensions { measure_text(text, self.font, self.font_size, self.font_scale) } } impl<'a> From> for TextConfigBuilder<'a> { fn from(val: TextConfig<'a>) -> Self { TextConfigBuilder::new(val) } }