add a macro for simpler first examples.

This commit is contained in:
Franz Dietrich 2025-10-12 16:13:25 +02:00
parent 453e8e39bd
commit cef63ca32a
26 changed files with 902 additions and 453 deletions

View File

@ -1,105 +0,0 @@
## Project Context
- **turtle-lib**: Heavy Bevy-based turtle graphics (0.17.1) with ECS architecture
- **turtle-lib-macroquad**: Lightweight macroquad implementation with Lyon tessellation (current focus)
- **turtle-lyon-poc**: Proof of concept for Lyon (COMPLETED - integrated into main crate)
- **turtle-skia-poc**: Alternative tiny-skia rendering approach
- **Status**: Lyon migration complete, fill quality issues resolved
## Architecture
```
turtle-lib-macroquad/src/
├── lib.rs - Public API & TurtleApp
├── state.rs - TurtleState & TurtleWorld
├── commands.rs - TurtleCommand & CommandQueue
├── builders.rs - Builder traits (DirectionalMovement, Turnable, CurvedMovement)
├── execution.rs - Command execution
├── tweening.rs - Animation controller
├── drawing.rs - Macroquad rendering
├── tessellation.rs - Lyon tessellation (355 lines - polygons, circles, arcs, strokes)
├── circle_geometry.rs - Circle/arc geometry calculations
├── shapes.rs - Turtle shapes
└── general/ - Type definitions (Angle, Length, Color, etc.)
```
## Current Status
1. ✅ **Lyon integration complete**: Using Lyon 1.0 for all tessellation
2. ✅ **Fill quality fixed**: EvenOdd fill rule handles complex fills and holes automatically
3. ✅ **Simplified codebase**: Replaced manual triangulation with Lyon's GPU-optimized tessellation
4. ✅ **Full feature set**: Polygons, circles, arcs, strokes all using Lyon
## Key Features
- **Builder API**: Fluent interface for turtle commands
- **Animation system**: Tweening controller with configurable speeds (Instant/Animated)
- **Lyon tessellation**: Automatic hole detection, proper winding order, GPU-optimized
- **Fill support**: Multi-contour fills with automatic hole handling
- **Shapes**: Arrow, circle, square, triangle, classic turtle shapes
## Response Style Rules
- NO emoji/smileys
- NO extensive summaries
- Use bullet points for lists
- Be concise and direct
- Focus on code solutions
# Tools to use
- when in doubt you can always use #fetch to get additional docs and online information.
- when the userinput is incomplete generate a brief text and let the user confirm your understanding.
## Code Patterns
### Lyon Tessellation (Current)
```rust
// tessellation.rs - Lyon integration
pub fn tessellate_polygon(vertices: &[Vec2], color: Color) -> Result<MeshData, Box<dyn std::error::Error>>
pub fn tessellate_multi_contour(contours: &[Vec<Vec2>], color: Color) -> Result<MeshData, Box<dyn std::error::Error>>
pub fn tessellate_stroke(vertices: &[Vec2], color: Color, width: f32, closed: bool) -> Result<MeshData, Box<dyn std::error::Error>>
pub fn tessellate_circle(center: Vec2, radius: f32, color: Color, filled: bool, stroke_width: f32) -> Result<MeshData, Box<dyn std::error::Error>>
pub fn tessellate_arc(center: Vec2, radius: f32, start_angle: f32, arc_angle: f32, color: Color, stroke_width: f32, segments: usize) -> Result<MeshData, Box<dyn std::error::Error>>
```
### Fill with Holes
```rust
// Multi-contour fills automatically detect holes using EvenOdd fill rule
let contours = vec![outer_boundary, hole1, hole2];
let mesh = tessellate_multi_contour(&contours, color)?;
```
## Builder API
```rust
let mut t = create_turtle();
t.forward(100).right(90)
.circle_left(50.0, 180.0, 36)
.begin_fill()
.set_fill_color(BLACK)
.circle_left(90.0, 180.0, 36)
.end_fill();
let app = TurtleApp::new().with_commands(t.build());
```
## File Links
- Main crate: [turtle-lib-macroquad/src/lib.rs](turtle-lib-macroquad/src/lib.rs)
- Tessellation: [turtle-lib-macroquad/src/tessellation.rs](turtle-lib-macroquad/src/tessellation.rs)
- Rendering: [turtle-lib-macroquad/src/drawing.rs](turtle-lib-macroquad/src/drawing.rs)
- Animation: [turtle-lib-macroquad/src/tweening.rs](turtle-lib-macroquad/src/tweening.rs)
- Examples: [turtle-lib-macroquad/examples/](turtle-lib-macroquad/examples/)
## Testing
Run examples to verify Lyon integration:
```bash
cargo run --example yinyang
cargo run --example stern
cargo run --example nikolaus
```
## Code Quality
Run clippy with strict checks on turtle-lib-macroquad:
```bash
cargo clippy --package turtle-lib-macroquad -- -Wclippy::pedantic -Wclippy::cast_precision_loss -Wclippy::cast_sign_loss -Wclippy::cast_possible_truncation
```
Note: Cast warnings are intentionally allowed for graphics code where precision loss is acceptable.
## Dependencies
- macroquad 0.4 - Game framework and rendering
- lyon 1.0 - Tessellation (fills, strokes, circles, arcs)
- tween 2.1.0 - Animation easing
- tracing 0.1 - Logging (with log features)

200
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,200 @@
# Turtle Graphics Library - AI Agent Instructions
## Project Overview
Rust workspace with turtle graphics implementations. **Primary focus: `turtle-lib-macroquad`** - lightweight library using Macroquad + Lyon for GPU-accelerated rendering.
### Workspace Structure
```
turtle/
├── turtle-lib-macroquad/ # MAIN LIBRARY - Macroquad + Lyon (focus here)
├── turtle-lib-macroquad-macros/ # Proc macro for turtle_main
├── turtle-lib/ # Legacy Bevy 0.17.1 implementation (maintenance only)
├── turtle-example/ # Legacy examples
└── turtle-ui/ # UI components
```
## Architecture (`turtle-lib-macroquad`)
### Core Design Pattern: Command Queue + Tweening
- **Builder API** (`TurtlePlan`) accumulates commands
- **Command Queue** stores execution plan
- **Tween Controller** interpolates between states for animation
- **Lyon Tessellation** converts all primitives to GPU meshes
### Key Files
```
src/
├── lib.rs - Public API, TurtleApp (main loop), re-exports
├── builders.rs - Fluent API traits (forward/right/etc chain)
├── commands.rs - TurtleCommand enum (Move/Turn/Circle/etc)
├── execution.rs - Execute commands, update state
├── tweening.rs - Animation interpolation, speed control
├── drawing.rs - Render Lyon meshes with Macroquad
├── tessellation.rs - Lyon integration (polygons/strokes/fills/arcs)
├── state.rs - TurtleState, TurtleWorld, FillState
└── circle_geometry.rs - Arc/circle math
```
### Critical Concepts
**1. Consolidated Commands** (reduces duplication):
- `Move(distance)` - negative = backward
- `Turn(angle)` - positive = right, negative = left (degrees)
- `Circle{radius, angle, steps, direction}` - unified left/right
**2. Fill System** (multi-contour with holes):
- `FillState` tracks `Vec<Vec<Vec2>>` (multiple contours)
- `pen_up()` closes current contour, `pen_down()` opens new
- Lyon's EvenOdd fill rule auto-detects holes
- Example: Donut = outer circle + inner circle (2 contours)
**3. Speed Modes**:
- `< 999`: Animated with tweening
- `>= 999`: Instant execution
- Controlled via `SetSpeed` commands (dynamic switching)
**4. Lyon Tessellation Pipeline**:
All drawing → Lyon → GPU mesh → Macroquad rendering
- ~410 lines eliminated vs manual triangulation
- Functions: `tessellate_polygon/stroke/circle/arc/multi_contour`
## Developer Workflows
### Building & Testing
```bash
# Main library
cargo build --package turtle-lib-macroquad
cargo test --package turtle-lib-macroquad
cargo clippy --package turtle-lib-macroquad -- -Wclippy::pedantic \
-Aclippy::cast_precision_loss -Aclippy::cast_sign_loss -Aclippy::cast_possible_truncation
# Run examples (15+ examples available)
cargo run --package turtle-lib-macroquad --example hello_turtle
cargo run --package turtle-lib-macroquad --example yinyang
cargo run --package turtle-lib-macroquad --example cheese_macro
```
### Macro Crate
```bash
cargo build --package turtle-lib-macroquad-macros
```
### Code Quality Standards
- Clippy pedantic mode enabled
- Cast warnings allowed for graphics math
- All examples must build warning-free
- Use `#[must_use]` on builder methods
## Project-Specific Patterns
### 1. The `turtle_main` Macro (PREFERRED for examples)
Simplest way to create turtle programs:
```rust
use turtle_lib_macroquad::*;
#[turtle_main("Window Title")]
fn draw(turtle: &mut TurtlePlan) {
turtle.forward(100.0).right(90.0);
}
```
Generates: window setup + render loop + quit handling (ESC/Q)
### 2. Import Convention
Only need: `use turtle_lib_macroquad::*;`
- Re-exports: `vec2`, `RED/BLUE/GREEN/etc`, all turtle types
- No `use macroquad::prelude::*` needed (causes unused warnings)
### 3. Builder Chain Pattern
```rust
let mut t = create_turtle();
t.forward(100).right(90)
.set_pen_color(BLUE)
.circle_left(50.0, 360.0, 36)
.begin_fill()
.end_fill();
let app = TurtleApp::new().with_commands(t.build());
```
### 4. Multi-Contour Fill Example
```rust
turtle.begin_fill();
turtle.circle_left(100.0, 360.0, 72); // Outer circle
turtle.pen_up(); // Closes contour
turtle.goto(vec2(0.0, -30.0));
turtle.pen_down(); // Opens new contour
turtle.circle_left(30.0, 360.0, 36); // Inner (becomes hole)
turtle.end_fill(); // EvenOdd rule creates donut
```
### 5. Manual Setup (advanced control)
```rust
#[macroquad::main("Custom")]
async fn main() {
let mut turtle = create_turtle();
// ... drawing code ...
let mut app = TurtleApp::new().with_commands(turtle.build());
loop {
clear_background(WHITE);
app.update();
app.render();
next_frame().await;
}
}
```
## Common Tasks
### Adding New Turtle Command
1. Add variant to `TurtleCommand` enum in `commands.rs`
2. Implement builder method in `builders.rs` (chain with `self`)
3. Add execution logic in `execution.rs`
4. Update tessellation/rendering if needed
### Adding Example
- Prefer `turtle_main` macro for simplicity
- Use only `use turtle_lib_macroquad::*;`
- Keep examples focused (one concept each)
- See `examples/hello_turtle.rs` for minimal template
### Debugging Lyon Issues
- Enable tracing: `RUST_LOG=turtle_lib_macroquad=debug cargo run`
- Check `tessellation.rs` for Lyon API usage
- EvenOdd fill rule: holes must have opposite winding
## Dependencies & Integration
### Main Dependencies
- `macroquad = "0.4"` - Window/rendering framework
- `lyon = "1.0"` - Tessellation (fills, strokes, circles)
- `tween = "2.1.0"` - Animation easing
- `tracing = "0.1"` - Optional logging (zero overhead when unused)
### Proc Macro Crate
- Separate crate required by Rust (proc-macro = true)
- Uses `syn`, `quote`, `proc-macro2`
- Generates full macroquad app boilerplate
## What NOT to Do
- Don't add `use macroquad::prelude::*` in examples when not required
- Don't manually triangulate - use Lyon functions
- Don't add commands for Forward/Backward separately (use Move)
- Don't modify `turtle-lib` (Bevy) unless specifically needed
- Don't create summary/comparison docs unless requested
## Key Documentation Files
- `README.md` - Main API docs
- `turtle-lib-macroquad/README.md` - Library-specific docs
- `turtle-lib-macroquad-macros/README.md` - Macro docs
## Response Style
- Be concise, no extensive summaries
- No emojis in technical responses
- Focus on code solutions over explanations
- Use bullet points for lists
- Reference specific files when helpful

View File

@ -1,7 +1,12 @@
[workspace]
resolver = "2"
members = ["turtle-lib", "turtle-example", "turtle-lib-macroquad"]
members = [
"turtle-lib",
"turtle-example",
"turtle-lib-macroquad",
"turtle-lib-macroquad-macros",
]
[workspace.dependencies]
# Pin Bevy across the workspace

View File

@ -0,0 +1,13 @@
[package]
name = "turtle-lib-macroquad-macros"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }

View File

@ -0,0 +1,72 @@
# turtle-lib-macroquad-macros
Procedural macros for `turtle-lib-macroquad`.
## `turtle_main` Macro
The `turtle_main` macro simplifies creating turtle graphics programs by automatically setting up:
- The Macroquad window
- Turtle initialization
- The main rendering loop
- Quit handling (ESC or Q keys)
### Usage
#### With a function parameter:
```rust
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[turtle_main("My Drawing")]
fn my_drawing(turtle: &mut TurtlePlan) {
turtle.set_pen_color(RED);
turtle.forward(100.0);
turtle.right(90.0);
turtle.forward(100.0);
}
```
#### With inline code:
```rust
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[turtle_main("My Drawing")]
fn my_drawing() {
turtle.set_pen_color(RED);
turtle.forward(100.0);
turtle.right(90.0);
turtle.forward(100.0);
}
```
### What it does
The macro expands your code into a full Macroquad application with:
- `#[macroquad::main]` attribute for window creation
- Turtle instance creation
- TurtleApp initialization with your commands
- A main loop that:
- Clears the background to WHITE
- Updates the turtle app
- Renders the drawing
- Shows "Press ESC or Q to quit" message
- Handles quit keys
### Benefits
- **Less boilerplate**: No need to write the same loop structure in every example
- **Consistent UI**: All examples have the same quit behavior
- **Beginner-friendly**: Makes turtle graphics examples more approachable
- **Focus on drawing**: Your code focuses on the turtle commands, not the framework
## License
Licensed under either of:
- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.

View File

@ -0,0 +1,175 @@
//! Procedural macros for turtle-lib-macroquad
//!
//! This crate provides the `turtle_main` procedural macro that simplifies
//! creating turtle graphics programs by automatically setting up the
//! macroquad window, turtle initialization, and the main rendering loop.
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};
/// A convenience macro that wraps your turtle drawing code with the necessary
/// boilerplate for running a turtle graphics program.
///
/// This macro:
/// - Wraps your code with `#[macroquad::main]`
/// - Creates a turtle instance (`turtle`)
/// - Sets up the `TurtleApp` with your drawing commands
/// - Provides a main loop with rendering and quit handling (ESC or Q)
///
/// # Example
///
/// ```no_run
/// use turtle_lib_macroquad::*;
///
/// #[turtle_main("My Turtle Drawing")]
/// fn my_drawing(turtle: &mut TurtlePlan) {
/// // Use colors from turtle_lib_macroquad (re-exported from macroquad)
/// turtle.set_pen_color(RED);
/// turtle.forward(100.0);
/// turtle.right(90.0);
/// turtle.forward(100.0);
/// }
/// ```
///
/// If you need macroquad types not re-exported by turtle_lib_macroquad:
///
/// ```no_run
/// use macroquad::prelude::SKYBLUE; // Import specific items
/// use turtle_lib_macroquad::*;
///
/// #[turtle_main("My Drawing")]
/// fn my_drawing(turtle: &mut TurtlePlan) {
/// turtle.set_pen_color(SKYBLUE);
/// turtle.forward(100.0);
/// }
/// ```
///
/// This expands to approximately:
///
/// ```no_run
/// use macroquad::prelude::*;
/// use turtle_lib_macroquad::*;
///
/// #[macroquad::main("My Turtle Drawing")]
/// async fn main() {
/// let mut turtle = create_turtle();
///
/// // Your drawing code here
/// turtle.set_pen_color(RED);
/// turtle.forward(100.0);
/// turtle.right(90.0);
/// turtle.forward(100.0);
///
/// let mut app = TurtleApp::new().with_commands(turtle.build());
///
/// loop {
/// clear_background(WHITE);
/// app.update();
/// app.render();
/// draw_text("Press ESC or Q to quit", 10.0, 40.0, 16.0, DARKGRAY);
///
/// if is_key_pressed(KeyCode::Escape) || is_key_pressed(KeyCode::Q) {
/// break;
/// }
///
/// next_frame().await;
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn turtle_main(args: TokenStream, input: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(input as ItemFn);
// Parse the window title from args (default to "Turtle Graphics")
let window_title = if args.is_empty() {
quote! { "Turtle Graphics" }
} else {
let args_str = args.to_string();
// Remove quotes if present
let title = args_str.trim().trim_matches('"');
quote! { #title }
};
let fn_name = &input_fn.sig.ident;
let fn_block = &input_fn.block;
// Check if the function has the expected signature
let has_turtle_param = input_fn.sig.inputs.len() == 1;
let expanded = if has_turtle_param {
// Function takes a turtle parameter
quote! {
#[macroquad::main(#window_title)]
async fn main() {
let mut turtle = turtle_lib_macroquad::create_turtle();
// Call the user's function with the turtle
#fn_name(&mut turtle);
let mut app = turtle_lib_macroquad::TurtleApp::new()
.with_commands(turtle.build());
loop {
macroquad::prelude::clear_background(macroquad::prelude::WHITE);
app.update();
app.render();
macroquad::prelude::draw_text(
"Press ESC or Q to quit",
10.0,
40.0,
16.0,
macroquad::prelude::DARKGRAY
);
if macroquad::prelude::is_key_pressed(macroquad::prelude::KeyCode::Escape)
|| macroquad::prelude::is_key_pressed(macroquad::prelude::KeyCode::Q)
{
break;
}
macroquad::prelude::next_frame().await;
}
}
fn #fn_name(turtle: &mut turtle_lib_macroquad::TurtlePlan) #fn_block
}
} else {
// Function takes no parameters - inline the code
quote! {
#[macroquad::main(#window_title)]
async fn main() {
let mut turtle = turtle_lib_macroquad::create_turtle();
// Inline the user's code
#fn_block
let mut app = turtle_lib_macroquad::TurtleApp::new()
.with_commands(turtle.build());
loop {
macroquad::prelude::clear_background(macroquad::prelude::WHITE);
app.update();
app.render();
macroquad::prelude::draw_text(
"Press ESC or Q to quit",
10.0,
40.0,
16.0,
macroquad::prelude::DARKGRAY
);
if macroquad::prelude::is_key_pressed(macroquad::prelude::KeyCode::Escape)
|| macroquad::prelude::is_key_pressed(macroquad::prelude::KeyCode::Q)
{
break;
}
macroquad::prelude::next_frame().await;
}
}
}
};
TokenStream::from(expanded)
}

View File

@ -9,6 +9,7 @@ macroquad = "0.4"
tween = "2.1.0"
lyon = "1.0"
tracing = { version = "0.1", features = ["log"], default-features = false }
turtle-lib-macroquad-macros = { path = "../turtle-lib-macroquad-macros" }
[dev-dependencies]
# For examples and testing

View File

@ -22,17 +22,74 @@ The main turtle graphics library built on Macroquad with Lyon tessellation.
- Frame-rate independent animation
- Live fill preview during circle/arc drawing
## Quick Start
### Using the `turtle_main` Macro (Recommended for Beginners)
The easiest way to create turtle programs is with the `turtle_main` macro:
```rust
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[turtle_main("My First Drawing")]
fn my_drawing(turtle: &mut TurtlePlan) {
turtle.set_pen_color(RED);
turtle.forward(100.0);
turtle.right(90.0);
turtle.forward(100.0);
}
```
The macro automatically handles:
- Window creation and setup
- Turtle initialization
- Rendering loop
- Quit handling (ESC or Q keys)
### Manual Setup (For Advanced Use)
For more control over the application loop:
```rust
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Turtle")]
async fn main() {
let mut turtle = create_turtle();
turtle.forward(100.0).right(90.0);
let mut app = TurtleApp::new().with_commands(turtle.build());
loop {
clear_background(WHITE);
app.update();
app.render();
next_frame().await;
}
}
```
## Quick Examples
All examples now use the `turtle_main` macro for simplicity:
```bash
# Run from this directory
cargo run --example hello_turtle # Minimal 10-line example
cargo run --example macro_demo # Simple square with macro
cargo run --example square # Basic square drawing
cargo run --example shapes # Different turtle shapes
cargo run --example yinyang # Multi-contour fills with holes
cargo run --example fill_advanced # Self-intersecting fills
cargo run --example koch # Recursive fractals
cargo run --example fill_demo # Multiple independent fills
cargo run --example fill_demo # Fill with holes (donut)
cargo run --example cheese_macro # Cheese example using macro
cargo run --example fill_advanced # Complex shapes (manual setup)
```
Most examples use `turtle_main` for simplicity. A few keep manual setup for custom UI or logging.
## Architecture Highlights
### Rendering Pipeline

View File

@ -0,0 +1,66 @@
//! Cheese example using the turtle_main macro
//!
//! This is a simplified version of cheese.rs that demonstrates how the
//! turtle_main macro reduces boilerplate code.
use turtle_lib_macroquad::*;
#[turtle_main("Cheese with Holes - Using Macro")]
fn draw_cheese(turtle: &mut TurtlePlan) {
// Set fill color to yellow (cheese color!)
turtle.set_pen_color(ORANGE);
turtle.set_pen_width(3.0);
turtle.set_fill_color(YELLOW);
println!("=== Starting cheese fill ===");
turtle.begin_fill();
// Draw outer boundary (large square)
println!("Drawing outer square boundary...");
for _ in 0..4 {
turtle.forward(400.0);
turtle.right(90.0);
}
// Close outer contour and start drawing holes
println!("Closing outer contour with pen_up");
turtle.pen_up();
// Draw triangular hole in the middle
println!("Drawing triangular hole...");
turtle.go_to(vec2(200.0, 120.0));
turtle.pen_down(); // Start new contour for hole
for _ in 0..3 {
turtle.forward(160.0);
turtle.right(120.0);
}
println!("Closing triangle contour with pen_up");
turtle.pen_up(); // Close triangle hole contour
// Draw circular hole (top-left) using circle_left
println!("Drawing circular hole (top-left) with circle_left...");
turtle.go_to(vec2(100.0, 100.0));
turtle.pen_down(); // Start new contour for hole
turtle.circle_left(30.0, 360.0, 36); // radius=30, full circle, 36 steps
println!("Closing circle contour with pen_up");
turtle.pen_up(); // Close circle hole contour
// Draw circular hole (bottom-right) using circle_right
println!("Drawing circular hole (bottom-right) with circle_right...");
turtle.go_to(vec2(280.0, 280.0));
turtle.pen_down(); // Start new contour for hole
turtle.circle_right(40.0, 360.0, 36); // radius=40, full circle, 36 steps
println!("Closing circle contour with pen_up");
turtle.pen_up(); // Close circle hole contour
// End fill - Lyon will automatically create holes!
println!("Calling end_fill - Lyon should create holes now!");
turtle.end_fill();
// Set animation speed
turtle.set_speed(300);
println!("Building and executing turtle plan...");
}

View File

@ -1,46 +1,29 @@
//! Test circle_left and circle_right commands
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Circle Test")]
async fn main() {
// Create a turtle plan
let mut plan = create_turtle();
plan.shape(ShapeType::Turtle);
#[turtle_main("Circle Test")]
fn draw(turtle: &mut TurtlePlan) {
turtle.shape(ShapeType::Turtle);
// Draw some circles
plan.set_pen_color(RED);
plan.set_pen_width(0.5);
plan.left(90.0);
plan.set_speed(999);
plan.circle_left(100.0, 540.0, 72); // partial circle to the left
turtle.set_pen_color(RED);
turtle.set_pen_width(0.5);
turtle.left(90.0);
turtle.set_speed(999);
turtle.circle_left(100.0, 540.0, 72); // partial circle to the left
plan.forward(150.0);
plan.set_speed(100);
plan.set_pen_color(BLUE);
plan.circle_right(50.0, 270.0, 72); // partial circle to the right
turtle.forward(150.0);
turtle.set_speed(100);
turtle.set_pen_color(BLUE);
turtle.circle_right(50.0, 270.0, 72); // partial circle to the right
// Set animation speed
plan.set_speed(20);
plan.forward(150.0);
plan.circle_left(50.0, 180.0, 12);
plan.circle_right(50.0, 180.0, 12);
turtle.set_speed(20);
turtle.forward(150.0);
turtle.circle_left(50.0, 180.0, 12);
turtle.circle_right(50.0, 180.0, 12);
plan.set_speed(700);
plan.set_pen_color(GREEN);
plan.circle_left(50.0, 180.0, 36); // Half circle to the left
// Create turtle app with animation (speed = 100 pixels/sec)
let mut app = TurtleApp::new().with_commands(plan.build());
// Main loop
loop {
clear_background(WHITE);
// Update and render
app.update();
app.render();
next_frame().await
}
turtle.set_speed(700);
turtle.set_pen_color(GREEN);
turtle.circle_left(50.0, 180.0, 36); // Half circle to the left
}

View File

@ -1,31 +1,13 @@
//! Test circle_left and circle_right commands
//! Test direction and movement commands
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Circle Test")]
async fn main() {
// Create a turtle plan
let mut plan = create_turtle();
#[turtle_main("Direction Test")]
fn draw(turtle: &mut TurtlePlan) {
// Set animation speed
plan.set_speed(50);
plan.right(45.0);
plan.forward(100.0);
plan.right(45.0);
plan.forward(100.0);
// Create turtle app with animation (speed = 100 pixels/sec)
let mut app = TurtleApp::new().with_commands(plan.build());
// Main loop
loop {
clear_background(WHITE);
// Update and render
app.update();
app.render();
next_frame().await
}
turtle.set_speed(50);
turtle.right(45.0);
turtle.forward(100.0);
turtle.right(45.0);
turtle.forward(100.0);
}

View File

@ -1,4 +1,6 @@
//! Advanced fill example with multiple holes and complex shapes
//!
//! This example uses manual setup to demonstrate custom window size and UI elements.
use macroquad::{miniquad::window::set_window_size, prelude::*};
use turtle_lib_macroquad::*;

View File

@ -1,55 +1,43 @@
//! Fill demonstration with holes
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Fill Demo")]
async fn main() {
let mut t = create_turtle();
#[turtle_main("Fill Demo")]
fn draw(turtle: &mut TurtlePlan) {
// Example from requirements: circle with hole (like a donut)
t.set_pen_color(BLUE);
t.set_pen_width(3.0);
t.right(90.0);
turtle.set_pen_color(BLUE);
turtle.set_pen_width(3.0);
turtle.right(90.0);
// Set fill color and begin fill
t.set_fill_color(RED);
t.begin_fill();
turtle.set_fill_color(RED);
turtle.begin_fill();
// Outer circle
t.circle_right(150.0, 360.0, 72);
turtle.circle_right(150.0, 360.0, 72);
// Move to start of inner circle (hole)
// pen_up doesn't matter for fill - vertices still recorded!
t.pen_up();
t.forward(50.0);
t.pen_down();
turtle.pen_up();
turtle.forward(50.0);
turtle.pen_down();
// Inner circle (creates a hole)
t.circle_right(150.0, 360.0, 72);
turtle.circle_right(150.0, 360.0, 72);
t.end_fill();
turtle.end_fill();
// Draw a square with no fill
t.pen_up();
t.forward(100.0);
t.pen_down();
t.set_pen_color(GREEN);
turtle.pen_up();
turtle.forward(100.0);
turtle.pen_down();
turtle.set_pen_color(GREEN);
for _ in 0..4 {
t.forward(100.0);
t.right(90.0);
turtle.forward(100.0);
turtle.right(90.0);
}
// Set animation speed
t.set_speed(100);
let mut app = TurtleApp::new().with_commands(t.build());
loop {
clear_background(WHITE);
app.update();
app.render();
next_frame().await
}
turtle.set_speed(100);
}

View File

@ -1,12 +1,9 @@
//! Example matching the original requirements exactly
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Fill Example - Original Requirements")]
async fn main() {
let mut turtle = create_turtle();
#[turtle_main("Fill Example - Original Requirements")]
fn draw(turtle: &mut TurtlePlan) {
turtle.right(90.0);
turtle.set_pen_width(3.0);
turtle.set_speed(900);
@ -37,30 +34,4 @@ async fn main() {
// Set speed for animation
turtle.set_speed(200);
let mut app = TurtleApp::new().with_commands(turtle.build());
loop {
clear_background(WHITE);
app.update();
app.render();
// Instructions
draw_text(
"Fill Example - Circle filled with red, square not filled",
10.0,
20.0,
20.0,
BLACK,
);
draw_text(
"Mouse: drag to pan, scroll to zoom",
10.0,
40.0,
16.0,
DARKGRAY,
);
next_frame().await
}
}

View File

@ -0,0 +1,14 @@
//! Minimal turtle example - just 10 lines!
//!
//! This is the simplest possible turtle program using the macro.
use turtle_lib_macroquad::*;
#[turtle_main("Hello Turtle")]
fn hello() {
turtle.set_pen_color(BLUE);
for _ in 0..4 {
turtle.forward(100.0);
turtle.right(90.0);
}
}

View File

@ -1,50 +1,37 @@
//! Koch snowflake fractal example
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
fn koch(depth: u32, plan: &mut TurtlePlan, distance: f32) {
fn koch(depth: u32, turtle: &mut TurtlePlan, distance: f32) {
if depth == 0 {
plan.forward(distance);
turtle.forward(distance);
} else {
let new_distance = distance / 3.0;
koch(depth - 1, plan, new_distance);
plan.left(60.0);
koch(depth - 1, plan, new_distance);
plan.right(120.0);
koch(depth - 1, plan, new_distance);
plan.left(60.0);
koch(depth - 1, plan, new_distance);
koch(depth - 1, turtle, new_distance);
turtle.left(60.0);
koch(depth - 1, turtle, new_distance);
turtle.right(120.0);
koch(depth - 1, turtle, new_distance);
turtle.left(60.0);
koch(depth - 1, turtle, new_distance);
}
}
#[macroquad::main("Koch Snowflake")]
async fn main() {
let mut plan = create_turtle();
#[turtle_main("Koch Snowflake")]
fn draw(turtle: &mut TurtlePlan) {
// Position turtle
plan.set_speed(1001);
plan.pen_up();
plan.backward(150.0);
turtle.set_speed(1001);
turtle.pen_up();
turtle.backward(150.0);
plan.pen_down();
turtle.pen_down();
// Draw Koch snowflake (triangle of Koch curves)
for _ in 0..3 {
koch(4, &mut plan, 300.0);
plan.right(120.0);
plan.set_speed(1200);
koch(4, turtle, 300.0);
turtle.right(120.0);
turtle.set_speed(1200);
}
plan.hide(); // Hide turtle when done
// Create app with animation
let mut app = TurtleApp::new().with_commands(plan.build());
loop {
clear_background(WHITE);
app.update();
app.render();
next_frame().await
}
turtle.hide(); // Hide turtle when done
}

View File

@ -17,6 +17,8 @@
//! RUST_LOG=turtle_lib_macroquad::tessellation=debug cargo run --example logging_example
//! RUST_LOG=turtle_lib_macroquad::execution=debug cargo run --example logging_example
//! ```
//!
//! Note: This example uses manual setup to demonstrate custom initialization logic.
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
@ -27,8 +29,7 @@ async fn main() {
// This will respect the RUST_LOG environment variable
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| {
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
// Default to showing info-level logs if RUST_LOG is not set
tracing_subscriber::EnvFilter::new("turtle_lib_macroquad=info")
}),
@ -40,7 +41,9 @@ async fn main() {
.init();
tracing::info!("Starting turtle graphics example with logging enabled");
tracing::info!("Try running with: RUST_LOG=turtle_lib_macroquad=debug cargo run --example logging_example");
tracing::info!(
"Try running with: RUST_LOG=turtle_lib_macroquad=debug cargo run --example logging_example"
);
// Create a turtle plan with fill operations to see detailed logging
let mut t = create_turtle();

View File

@ -0,0 +1,17 @@
//! Simple demo of the turtle_main macro
//!
//! This example shows how the turtle_main macro simplifies turtle programs
//! by automatically handling window setup, turtle creation, and the render loop.
use turtle_lib_macroquad::*;
#[turtle_main("Macro Demo - Simple Square")]
fn draw_square(turtle: &mut TurtlePlan) {
turtle.set_pen_color(BLUE);
turtle.set_pen_width(3.0);
for _ in 0..4 {
turtle.forward(150.0);
turtle.right(90.0);
}
}

View File

@ -0,0 +1,17 @@
//! Demo of the turtle_main macro with inline code
//!
//! This example shows that you can write your turtle code directly
//! in the function body without taking a turtle parameter.
use turtle_lib_macroquad::*;
#[turtle_main("Macro Demo - Inline Spiral")]
fn draw_spiral() {
turtle.set_pen_color(RED);
turtle.set_pen_width(2.0);
for i in 0..36 {
turtle.forward(i as f32 * 3.0);
turtle.right(25.0);
}
}

View File

@ -0,0 +1,57 @@
//! Comprehensive macro example showing various turtle features
//!
//! This example demonstrates:
//! - Colors and pen settings
//! - Fills
//! - Circles
//! - Animation speed
use turtle_lib_macroquad::*;
#[turtle_main("Turtle Macro - Full Demo")]
fn full_demo(turtle: &mut TurtlePlan) {
// Draw a colorful flower
turtle.set_speed(200);
// Center the drawing
turtle.pen_up();
turtle.go_to(vec2(200.0, 300.0));
turtle.pen_down();
// Draw petals
for i in 0..8 {
let hue = i as f32 / 8.0;
let color = Color::from_rgba(
((hue * 360.0).to_radians().sin() * 127.0 + 128.0) as u8,
((hue * 360.0 + 120.0).to_radians().sin() * 127.0 + 128.0) as u8,
((hue * 360.0 + 240.0).to_radians().sin() * 127.0 + 128.0) as u8,
255,
);
turtle.set_fill_color(color);
turtle.set_pen_color(color);
turtle.begin_fill();
// Draw a petal using circles
turtle.circle_left(50.0, 180.0, 20);
turtle.left(90.0);
turtle.circle_left(50.0, 180.0, 20);
turtle.left(90.0);
turtle.end_fill();
// Move to next petal position
turtle.right(45.0);
}
// Draw center circle
turtle.pen_up();
turtle.go_to(vec2(200.0, 300.0));
turtle.pen_down();
turtle.set_fill_color(YELLOW);
turtle.set_pen_color(ORANGE);
turtle.set_pen_width(2.0);
turtle.begin_fill();
turtle.circle_left(20.0, 360.0, 36);
turtle.end_fill();
}

View File

@ -1,74 +1,57 @@
//! Nikolaus example - draws a house-like figure
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
fn nikolausquadrat(plan: &mut TurtlePlan, groesse: f32) {
plan.forward(groesse);
plan.left(90.0);
plan.forward(groesse);
plan.left(90.0);
plan.forward(groesse);
plan.left(90.0);
plan.forward(groesse);
plan.left(90.0);
fn nikolausquadrat(turtle: &mut TurtlePlan, groesse: f32) {
turtle.forward(groesse);
turtle.left(90.0);
turtle.forward(groesse);
turtle.left(90.0);
turtle.forward(groesse);
turtle.left(90.0);
turtle.forward(groesse);
turtle.left(90.0);
}
fn nikolausdiag(plan: &mut TurtlePlan, groesse: f32) {
fn nikolausdiag(turtle: &mut TurtlePlan, groesse: f32) {
let quadrat = groesse * groesse;
let diag = (quadrat + quadrat).sqrt();
plan.left(45.0);
plan.forward(diag);
plan.left(45.0);
nikolausdach2(plan, groesse);
plan.left(45.0);
plan.forward(diag);
plan.left(45.0);
turtle.left(45.0);
turtle.forward(diag);
turtle.left(45.0);
nikolausdach2(turtle, groesse);
turtle.left(45.0);
turtle.forward(diag);
turtle.left(45.0);
}
fn nikolausdach2(plan: &mut TurtlePlan, groesse: f32) {
fn nikolausdach2(turtle: &mut TurtlePlan, groesse: f32) {
let quadrat = groesse * groesse;
let diag = (quadrat + quadrat).sqrt();
plan.left(45.0);
plan.forward(diag / 2.0);
plan.left(90.0);
plan.forward(diag / 2.0);
plan.left(45.0);
turtle.left(45.0);
turtle.forward(diag / 2.0);
turtle.left(90.0);
turtle.forward(diag / 2.0);
turtle.left(45.0);
}
fn nikolaus(plan: &mut TurtlePlan, groesse: f32) {
nikolausquadrat(plan, groesse);
nikolausdiag(plan, groesse);
fn nikolaus(turtle: &mut TurtlePlan, groesse: f32) {
nikolausquadrat(turtle, groesse);
nikolausdiag(turtle, groesse);
}
#[macroquad::main("Nikolaus")]
async fn main() {
// Create a turtle plan
let mut plan = create_turtle();
plan.shape(ShapeType::Turtle);
#[turtle_main("Nikolaus")]
fn draw(turtle: &mut TurtlePlan) {
turtle.shape(ShapeType::Turtle);
// Position the turtle (pen up, move, pen down)
plan.pen_up();
plan.backward(80.0);
plan.left(90.0);
plan.forward(50.0);
plan.right(90.0);
plan.pen_down();
turtle.pen_up();
turtle.backward(80.0);
turtle.left(90.0);
turtle.forward(50.0);
turtle.right(90.0);
turtle.pen_down();
nikolaus(&mut plan, 100.0);
// Create turtle app with animation
let mut app = TurtleApp::new().with_commands(plan.build());
// Main loop
loop {
clear_background(WHITE);
// Update and render
app.update();
app.render();
next_frame().await
}
nikolaus(turtle, 100.0);
}

View File

@ -1,50 +1,32 @@
//! Example demonstrating different turtle shapes
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Turtle Shapes")]
async fn main() {
// Create a turtle plan that demonstrates different shapes
let mut plan = create_turtle();
#[turtle_main("Turtle Shapes")]
fn draw(turtle: &mut TurtlePlan) {
// Start with triangle (default)
plan.forward(100.0);
plan.right(90.0);
turtle.forward(100.0);
turtle.right(90.0);
// Change to turtle shape
plan.shape(ShapeType::Turtle);
plan.forward(100.0);
plan.right(90.0);
turtle.shape(ShapeType::Turtle);
turtle.forward(100.0);
turtle.right(90.0);
// Change to circle
plan.shape(ShapeType::Circle);
plan.forward(100.0);
plan.right(90.0);
turtle.shape(ShapeType::Circle);
turtle.forward(100.0);
turtle.right(90.0);
// Change to square
plan.shape(ShapeType::Square);
plan.forward(100.0);
plan.right(90.0);
turtle.shape(ShapeType::Square);
turtle.forward(100.0);
turtle.right(90.0);
// Change to arrow
plan.shape(ShapeType::Arrow);
plan.forward(100.0);
turtle.shape(ShapeType::Arrow);
turtle.forward(100.0);
// Set animation speed
plan.set_speed(50);
// Create turtle app with animation (speed = 100 pixels/sec for slower animation)
let mut app = TurtleApp::new().with_commands(plan.build());
// Main loop
loop {
clear_background(WHITE);
// Update and render
app.update();
app.render();
next_frame().await
}
turtle.set_speed(50);
}

View File

@ -1,33 +1,16 @@
//! Simple square example demonstrating basic turtle graphics
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Turtle Square")]
async fn main() {
// Create a turtle plan
let mut plan = create_turtle();
plan.shape(ShapeType::Turtle);
#[turtle_main("Turtle Square")]
fn draw(turtle: &mut TurtlePlan) {
turtle.shape(ShapeType::Turtle);
// Draw a square
for _ in 0..4 {
plan.forward(100.0).right(90.0);
turtle.forward(100.0).right(90.0);
}
// Set animation speed
plan.set_speed(50);
// Create turtle app with animation
let mut app = TurtleApp::new().with_commands(plan.build());
// Main loop
loop {
clear_background(WHITE);
// Update and render
app.update();
app.render();
next_frame().await
}
turtle.set_speed(50);
}

View File

@ -1,38 +1,21 @@
//! Simple square example demonstrating basic turtle graphics
//! Star pattern example demonstrating complex turtle patterns
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Turtle Square")]
async fn main() {
// Create a turtle plan
let mut plan = create_turtle();
plan.shape(ShapeType::Turtle);
plan.set_speed(1500);
plan.set_pen_width(0.5);
#[turtle_main("Star Pattern")]
fn draw(turtle: &mut TurtlePlan) {
turtle.shape(ShapeType::Turtle);
turtle.set_speed(1500);
turtle.set_pen_width(0.5);
// Draw a 5-pointed star pattern repeatedly
for _i in 0..50000 {
plan.forward(200.0);
plan.circle_left(10.0, 72.0, 1000);
plan.circle_right(5.0, 360.0, 1000);
plan.circle_left(10.0, 72.0, 1000);
turtle.forward(200.0);
turtle.circle_left(10.0, 72.0, 1000);
turtle.circle_right(5.0, 360.0, 1000);
turtle.circle_left(10.0, 72.0, 1000);
}
// Set animation speed
plan.set_speed(300);
// Create turtle app with animation (speed = 100 pixels/sec)
let mut app = TurtleApp::new().with_commands(plan.build());
// Main loop
loop {
clear_background(WHITE);
// Update and render
app.update();
app.render();
next_frame().await
}
turtle.set_speed(300);
}

View File

@ -1,47 +1,30 @@
//! Simple square example demonstrating basic turtle graphics
//! Yin-Yang symbol example demonstrating multi-contour fills
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Turtle Square")]
async fn main() {
// Create a turtle plan
let mut t = create_turtle();
t.set_speed(900);
#[turtle_main("Yin-Yang")]
fn draw(turtle: &mut TurtlePlan) {
turtle.set_speed(900);
t.circle_left(90.0, 180.0, 36);
t.begin_fill();
t.circle_left(90.0, 180.0, 36);
t.circle_left(45.0, 180.0, 26);
t.circle_right(45.0, 180.0, 26);
t.pen_up();
t.right(90.0);
t.forward(37.0);
t.left(90.0);
t.pen_down();
t.circle_right(8.0, 360.0, 12);
t.pen_up();
t.right(90.0);
t.forward(90.0);
t.left(90.0);
t.pen_down();
t.circle_right(8.0, 360.0, 12);
t.end_fill();
turtle.circle_left(90.0, 180.0, 36);
turtle.begin_fill();
turtle.circle_left(90.0, 180.0, 36);
turtle.circle_left(45.0, 180.0, 26);
turtle.circle_right(45.0, 180.0, 26);
turtle.pen_up();
turtle.right(90.0);
turtle.forward(37.0);
turtle.left(90.0);
turtle.pen_down();
turtle.circle_right(8.0, 360.0, 12);
turtle.pen_up();
turtle.right(90.0);
turtle.forward(90.0);
turtle.left(90.0);
turtle.pen_down();
turtle.circle_right(8.0, 360.0, 12);
turtle.end_fill();
// Set animation speed
t.set_speed(1000);
// Create turtle app with animation (speed = 100 pixels/sec)
let mut app = TurtleApp::new().with_commands(t.build());
// Main loop
loop {
clear_background(WHITE);
// Update and render
app.update();
app.render();
next_frame().await
}
turtle.set_speed(1000);
}

View File

@ -3,7 +3,29 @@
//! This library provides a turtle graphics API for creating drawings and animations
//! using the Macroquad game framework.
//!
//! # Example
//! # Quick Start with `turtle_main` Macro
//!
//! The easiest way to create a turtle program is using the `turtle_main` macro:
//!
//! ```no_run
//! use macroquad::prelude::*;
//! use turtle_lib_macroquad::*;
//!
//! #[turtle_main("My Drawing")]
//! fn draw(turtle: &mut TurtlePlan) {
//! turtle.set_pen_color(RED);
//! turtle.forward(100.0);
//! turtle.right(90.0);
//! turtle.forward(100.0);
//! }
//! ```
//!
//! The macro automatically handles window setup, rendering loop, and quit handling.
//!
//! # Manual Setup Example
//!
//! For more control, you can set up the application manually:
//!
//! ```no_run
//! use macroquad::prelude::*;
//! use turtle_lib_macroquad::*;
@ -43,6 +65,14 @@ pub use shapes::{ShapeType, TurtleShape};
pub use state::{DrawCommand, TurtleState, TurtleWorld};
pub use tweening::TweenController;
// Re-export the turtle_main macro
pub use turtle_lib_macroquad_macros::turtle_main;
// Re-export common macroquad types and colors for convenience
pub use macroquad::prelude::{
vec2, BLACK, BLUE, DARKGRAY, GOLD, GREEN, ORANGE, PURPLE, RED, WHITE, YELLOW,
};
use macroquad::prelude::*;
/// Main turtle application struct