add text capabilities
This commit is contained in:
parent
14b93f657b
commit
070b404bf4
64
turtle-lib/examples/text_demo.rs
Normal file
64
turtle-lib/examples/text_demo.rs
Normal file
@ -0,0 +1,64 @@
|
||||
//! Demonstration of text rendering with turtle heading orientation
|
||||
|
||||
use macroquad::prelude::*;
|
||||
use turtle_lib::*;
|
||||
|
||||
#[turtle_main("Text Demo")]
|
||||
fn draw(turtle: &mut TurtlePlan) {
|
||||
// Write text at heading 0 (right)
|
||||
turtle
|
||||
.set_pen_color(BLACK)
|
||||
.write_text("Heading 0°", 20u16)
|
||||
.forward(0.0); // Just to complete the chain
|
||||
|
||||
// Move forward and turn, then write text at different angles
|
||||
turtle
|
||||
.forward(100.0)
|
||||
.right(45.0)
|
||||
.write_text("45° right", 18u16)
|
||||
.forward(0.0);
|
||||
|
||||
turtle
|
||||
.forward(80.0)
|
||||
.right(45.0)
|
||||
.write_text("90° down", 18u16)
|
||||
.forward(0.0);
|
||||
|
||||
turtle
|
||||
.forward(80.0)
|
||||
.right(45.0)
|
||||
.write_text("135°", 18u16)
|
||||
.forward(0.0);
|
||||
|
||||
turtle
|
||||
.forward(80.0)
|
||||
.right(45.0)
|
||||
.write_text("180° left", 18u16)
|
||||
.forward(0.0);
|
||||
|
||||
// Use different font sizes
|
||||
turtle
|
||||
.pen_up()
|
||||
.go_to(vec2(-200.0, 100.0))
|
||||
.pen_down()
|
||||
.set_pen_color(BLUE)
|
||||
.write_text("Small", 12f32)
|
||||
.forward(50.0)
|
||||
.write_text("Medium", 20)
|
||||
.forward(50.0)
|
||||
.write_text("Large", 28u16)
|
||||
.forward(0.0);
|
||||
|
||||
// Example with drawing
|
||||
turtle
|
||||
.pen_up()
|
||||
.go_to(vec2(0.0, -150.0))
|
||||
.pen_down()
|
||||
.set_pen_color(RED)
|
||||
.circle_right(50.0, 360.0, 32)
|
||||
.pen_up()
|
||||
.go_to(vec2(0.0, -150.0))
|
||||
.pen_down()
|
||||
.write_text("Circle", 16f32)
|
||||
.forward(0.0);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
//! Builder pattern traits for creating turtle command sequences
|
||||
|
||||
use crate::commands::{CommandQueue, TurtleCommand};
|
||||
use crate::general::{AnimationSpeed, Color, Coordinate, Precision};
|
||||
use crate::general::{AnimationSpeed, Color, Coordinate, FontSize, Precision};
|
||||
use crate::shapes::{ShapeType, TurtleShape};
|
||||
|
||||
/// Trait for adding commands to a queue
|
||||
@ -635,6 +635,48 @@ impl TurtlePlan {
|
||||
self
|
||||
}
|
||||
|
||||
/// Writes text at the turtle's current position, oriented along its heading direction.
|
||||
///
|
||||
/// The text is rendered with its baseline positioned slightly above the turtle's current position,
|
||||
/// and rotated to align with the turtle's current heading.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text` - The text to render (can be `&str` or `String`)
|
||||
/// * `font_size` - The font size, can be any type that converts to `FontSize` (e.g., `f32`, `u16`, `i32`)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use turtle_lib::*;
|
||||
/// #
|
||||
/// #[turtle_main("Text Example")]
|
||||
/// fn draw(turtle: &mut TurtlePlan) {
|
||||
/// // Write text at current position (heading 0° = horizontal)
|
||||
/// turtle.write_text("Hello", 20.0);
|
||||
///
|
||||
/// // Move forward and write at an angle
|
||||
/// turtle.forward(100.0)
|
||||
/// .right(45.0)
|
||||
/// .write_text("World", 24);
|
||||
///
|
||||
/// // Chain with other commands
|
||||
/// turtle.forward(50.0)
|
||||
/// .write_text("End", 16u16);
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn write_text<T>(&mut self, text: impl Into<String>, font_size: T) -> &mut Self
|
||||
where
|
||||
T: Into<FontSize>,
|
||||
{
|
||||
self.queue.push(TurtleCommand::WriteText {
|
||||
text: text.into(),
|
||||
font_size: font_size.into(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Resets the turtle to its default state.
|
||||
///
|
||||
/// This clears all drawings, clears the animation queue, and resets all turtle parameters:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
//! Turtle commands and command queue
|
||||
|
||||
use crate::general::{AnimationSpeed, Color, Coordinate, Precision};
|
||||
use crate::general::{AnimationSpeed, Color, Coordinate, FontSize, Precision};
|
||||
use crate::shapes::TurtleShape;
|
||||
|
||||
/// Individual turtle commands
|
||||
@ -43,6 +43,12 @@ pub enum TurtleCommand {
|
||||
BeginFill,
|
||||
EndFill,
|
||||
|
||||
// Text rendering
|
||||
WriteText {
|
||||
text: String,
|
||||
font_size: FontSize,
|
||||
},
|
||||
|
||||
// Reset
|
||||
Reset,
|
||||
}
|
||||
|
||||
@ -31,6 +31,15 @@ pub fn render_world(world: &TurtleWorld) {
|
||||
DrawCommand::Mesh { data } => {
|
||||
draw_mesh(&data.to_mesh());
|
||||
}
|
||||
DrawCommand::Text {
|
||||
text,
|
||||
position,
|
||||
heading,
|
||||
font_size,
|
||||
color,
|
||||
} => {
|
||||
draw_text_command(text, *position, *heading, *font_size, *color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,6 +79,15 @@ pub fn render_world_with_tweens(world: &TurtleWorld, zoom_level: f32) {
|
||||
DrawCommand::Mesh { data } => {
|
||||
draw_mesh(&data.to_mesh());
|
||||
}
|
||||
DrawCommand::Text {
|
||||
text,
|
||||
position,
|
||||
heading,
|
||||
font_size,
|
||||
color,
|
||||
} => {
|
||||
draw_text_command(text, *position, *heading, *font_size, *color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -284,6 +302,42 @@ fn should_draw_tween_line(command: &crate::commands::TurtleCommand) -> bool {
|
||||
matches!(command, TurtleCommand::Move(..) | TurtleCommand::Goto(..))
|
||||
}
|
||||
|
||||
/// Draw a text command with rotation based on turtle heading
|
||||
fn draw_text_command(
|
||||
text: &str,
|
||||
position: Vec2,
|
||||
heading_radians: f32,
|
||||
font_size: crate::general::FontSize,
|
||||
color: Color,
|
||||
) {
|
||||
// Heading in turtle coordinates: 0 rad = right, positive = counter-clockwise
|
||||
// Macroquad rotation: same convention (0 = right, positive = counter-clockwise)
|
||||
// So we use the heading directly
|
||||
let rotation_rad = heading_radians;
|
||||
|
||||
// Calculate perpendicular offset (90° clockwise from heading)
|
||||
// This places text slightly to the right of the movement direction
|
||||
let font_size_val = font_size.value();
|
||||
let offset_distance = f32::from(font_size_val) / 3.0;
|
||||
|
||||
// Perpendicular direction: heading - π/2 (rotated 90° clockwise)
|
||||
let perpendicular_angle = heading_radians - std::f32::consts::PI / 2.0;
|
||||
let offset_x = offset_distance * perpendicular_angle.cos();
|
||||
let offset_y = offset_distance * perpendicular_angle.sin();
|
||||
|
||||
draw_text_ex(
|
||||
text,
|
||||
position.x + offset_x,
|
||||
position.y + offset_y,
|
||||
TextParams {
|
||||
font_size: font_size_val,
|
||||
rotation: rotation_rad,
|
||||
color,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Draw arc segments for circle tween animation
|
||||
fn draw_tween_arc(
|
||||
tween: &crate::tweening::CommandTween,
|
||||
|
||||
@ -108,6 +108,17 @@ pub fn execute_command_side_effects(command: &TurtleCommand, state: &mut Turtle)
|
||||
true
|
||||
}
|
||||
|
||||
TurtleCommand::WriteText { text, font_size } => {
|
||||
state.commands.push(DrawCommand::Text {
|
||||
text: text.clone(),
|
||||
position: state.params.position,
|
||||
heading: state.params.heading,
|
||||
font_size: *font_size,
|
||||
color: state.params.color,
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
TurtleCommand::Move(_)
|
||||
| TurtleCommand::Turn(_)
|
||||
| TurtleCommand::Circle { .. }
|
||||
|
||||
@ -3,9 +3,11 @@
|
||||
use macroquad::prelude::*;
|
||||
|
||||
pub mod angle;
|
||||
pub mod fontsize;
|
||||
pub mod length;
|
||||
|
||||
pub use angle::Angle;
|
||||
pub use fontsize::FontSize;
|
||||
pub use length::Length;
|
||||
|
||||
/// Precision type for calculations
|
||||
|
||||
48
turtle-lib/src/general/fontsize.rs
Normal file
48
turtle-lib/src/general/fontsize.rs
Normal file
@ -0,0 +1,48 @@
|
||||
//! `FontSize` type for text rendering
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct FontSize(pub u16);
|
||||
|
||||
impl FontSize {
|
||||
/// Create a new `FontSize` from a u16 value
|
||||
#[must_use]
|
||||
pub const fn new(size: u16) -> Self {
|
||||
Self(size)
|
||||
}
|
||||
|
||||
/// Get the inner u16 value
|
||||
#[must_use]
|
||||
pub const fn value(&self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for FontSize {
|
||||
fn from(size: u16) -> Self {
|
||||
Self(size)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for FontSize {
|
||||
fn from(f: f32) -> Self {
|
||||
Self(f.max(1.0) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for FontSize {
|
||||
fn from(i: i32) -> Self {
|
||||
Self(i.max(1) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for FontSize {
|
||||
fn from(i: i16) -> Self {
|
||||
Self(i.max(1) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for FontSize {
|
||||
fn from(size: usize) -> Self {
|
||||
Self((size as u16).max(1))
|
||||
}
|
||||
}
|
||||
@ -281,6 +281,14 @@ impl MeshData {
|
||||
pub enum DrawCommand {
|
||||
/// Pre-tessellated mesh data (lines, arcs, circles, polygons - all use this)
|
||||
Mesh { data: MeshData },
|
||||
/// Text rendering command
|
||||
Text {
|
||||
text: String,
|
||||
position: Vec2,
|
||||
heading: f32,
|
||||
font_size: crate::general::FontSize,
|
||||
color: Color,
|
||||
},
|
||||
}
|
||||
|
||||
/// The complete turtle world containing all drawing state
|
||||
|
||||
@ -432,8 +432,8 @@ impl TweenController {
|
||||
TurtleCommand::SetFillColor(color) => {
|
||||
target.fill_color = *color;
|
||||
}
|
||||
TurtleCommand::BeginFill | TurtleCommand::EndFill => {
|
||||
// Fill commands don't change turtle state for tweening purposes
|
||||
TurtleCommand::BeginFill | TurtleCommand::EndFill | TurtleCommand::WriteText { .. } => {
|
||||
// Fill and text commands don't change turtle state for tweening purposes
|
||||
// They're handled directly in execution
|
||||
}
|
||||
TurtleCommand::Reset => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user