on-deck/src/text_drawer.rs

201 lines
5.3 KiB
Rust

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<String> = 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<TextConfig<'a>> for TextConfigBuilder<'a> {
fn from(val: TextConfig<'a>) -> Self {
TextConfigBuilder::new(val)
}
}