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
|
//! Builder pattern traits for creating turtle command sequences
|
||||||
|
|
||||||
use crate::commands::{CommandQueue, TurtleCommand};
|
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};
|
use crate::shapes::{ShapeType, TurtleShape};
|
||||||
|
|
||||||
/// Trait for adding commands to a queue
|
/// Trait for adding commands to a queue
|
||||||
@ -635,6 +635,48 @@ impl TurtlePlan {
|
|||||||
self
|
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.
|
/// Resets the turtle to its default state.
|
||||||
///
|
///
|
||||||
/// This clears all drawings, clears the animation queue, and resets all turtle parameters:
|
/// This clears all drawings, clears the animation queue, and resets all turtle parameters:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//! Turtle commands and command queue
|
//! Turtle commands and command queue
|
||||||
|
|
||||||
use crate::general::{AnimationSpeed, Color, Coordinate, Precision};
|
use crate::general::{AnimationSpeed, Color, Coordinate, FontSize, Precision};
|
||||||
use crate::shapes::TurtleShape;
|
use crate::shapes::TurtleShape;
|
||||||
|
|
||||||
/// Individual turtle commands
|
/// Individual turtle commands
|
||||||
@ -43,6 +43,12 @@ pub enum TurtleCommand {
|
|||||||
BeginFill,
|
BeginFill,
|
||||||
EndFill,
|
EndFill,
|
||||||
|
|
||||||
|
// Text rendering
|
||||||
|
WriteText {
|
||||||
|
text: String,
|
||||||
|
font_size: FontSize,
|
||||||
|
},
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
Reset,
|
Reset,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,15 @@ pub fn render_world(world: &TurtleWorld) {
|
|||||||
DrawCommand::Mesh { data } => {
|
DrawCommand::Mesh { data } => {
|
||||||
draw_mesh(&data.to_mesh());
|
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 } => {
|
DrawCommand::Mesh { data } => {
|
||||||
draw_mesh(&data.to_mesh());
|
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(..))
|
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
|
/// Draw arc segments for circle tween animation
|
||||||
fn draw_tween_arc(
|
fn draw_tween_arc(
|
||||||
tween: &crate::tweening::CommandTween,
|
tween: &crate::tweening::CommandTween,
|
||||||
|
|||||||
@ -108,6 +108,17 @@ pub fn execute_command_side_effects(command: &TurtleCommand, state: &mut Turtle)
|
|||||||
true
|
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::Move(_)
|
||||||
| TurtleCommand::Turn(_)
|
| TurtleCommand::Turn(_)
|
||||||
| TurtleCommand::Circle { .. }
|
| TurtleCommand::Circle { .. }
|
||||||
|
|||||||
@ -3,9 +3,11 @@
|
|||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
|
|
||||||
pub mod angle;
|
pub mod angle;
|
||||||
|
pub mod fontsize;
|
||||||
pub mod length;
|
pub mod length;
|
||||||
|
|
||||||
pub use angle::Angle;
|
pub use angle::Angle;
|
||||||
|
pub use fontsize::FontSize;
|
||||||
pub use length::Length;
|
pub use length::Length;
|
||||||
|
|
||||||
/// Precision type for calculations
|
/// 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 {
|
pub enum DrawCommand {
|
||||||
/// Pre-tessellated mesh data (lines, arcs, circles, polygons - all use this)
|
/// Pre-tessellated mesh data (lines, arcs, circles, polygons - all use this)
|
||||||
Mesh { data: MeshData },
|
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
|
/// The complete turtle world containing all drawing state
|
||||||
|
|||||||
@ -432,8 +432,8 @@ impl TweenController {
|
|||||||
TurtleCommand::SetFillColor(color) => {
|
TurtleCommand::SetFillColor(color) => {
|
||||||
target.fill_color = *color;
|
target.fill_color = *color;
|
||||||
}
|
}
|
||||||
TurtleCommand::BeginFill | TurtleCommand::EndFill => {
|
TurtleCommand::BeginFill | TurtleCommand::EndFill | TurtleCommand::WriteText { .. } => {
|
||||||
// Fill commands don't change turtle state for tweening purposes
|
// Fill and text commands don't change turtle state for tweening purposes
|
||||||
// They're handled directly in execution
|
// They're handled directly in execution
|
||||||
}
|
}
|
||||||
TurtleCommand::Reset => {
|
TurtleCommand::Reset => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user