We refactored the builder pattern in [`turtle-lib`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib) to eliminate inherent method asymmetry and organize all turtle capabilities into six cohesive traits. [`builders.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/builders.rs) The legacy traits (`DirectionalMovement`, `Turnable`, `CurvedMovement`) and orphaned inherent methods have been reorganized into six domain-focused traits: - **[`Movement`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/builders.rs#L14)**: - `forward()` - `backward()` - `go_to()` - `circle_left()` - `circle_right()` - **[`Rotation`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/builders.rs#L191)**: - `left()` - `right()` - `set_heading()` - **[`Pen`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/builders.rs#L279)**: - `pen_up()` - `pen_down()` - `set_pen_color()` - `set_pen_width()` - **[`Fill`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/builders.rs#L393)**: - `begin_fill()` - `end_fill()` - `set_fill_color()` - **[`Cursor`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/builders.rs#L481)**: - `hide()` - `show()` - `shape()` - `set_shape()` - `set_speed()` - `reset()` - **[`Text`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/builders.rs#L646)**: - `write_text()` [`TurtlePlan`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/builders.rs#L688) `TurtlePlan`'s inherent methods are now strictly builder lifecycle controls: - `new() -> Self` - `build(self) -> CommandQueue` `TurtlePlan` implements `WithCommands`, `Movement`, `Rotation`, `Pen`, `Fill`, `Cursor`, and `Text`. - **[`lib.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/lib.rs#L62-L65)**: Re-exports `Cursor`, `Fill`, `Movement`, `Pen`, `Rotation`, `Text`, `TurtlePlan`, `WithCommands`. - **Examples**: Updated [`clock.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/clock.rs#L8), [`clock_threaded.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/clock_threaded.rs#L9), [`dashed_circle.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/dashed_circle.rs#L4), and [`bezier.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/bezier.rs#L4) to use `use turtle_lib::*;`. - **[`README.md`](file:///home/dietrich/Projekte/Source/turtlers/README.md#L344)**: Updated trait references in the architecture outline. --- - **Unit & Doc Tests**: ```bash cargo test --package turtle-lib ``` Result: 17 unit tests passed; 34 doc-tests passed (0 failed). - **All Examples**: ```bash cargo check --package turtle-lib --examples ``` Result: Successfully compiled all 30 examples. - **Clippy**: ```bash cargo clippy --package turtle-lib -- -Wclippy::pedantic \ -Aclippy::cast_precision_loss -Aclippy::cast_sign_loss -Aclippy::cast_possible_truncation ``` Result: 0 warnings in `builders.rs`.
115 lines
3.8 KiB
Rust
115 lines
3.8 KiB
Rust
//! Animated clock example using threading
|
|
//!
|
|
//! This example demonstrates how to use turtle command channels for animated updates.
|
|
//! A separate thread generates the clock drawing commands every second while the main
|
|
//! thread handles rendering via the Macroquad game loop.
|
|
|
|
use chrono::{Local, Timelike};
|
|
use macroquad::prelude::{clear_background, is_key_pressed, next_frame, KeyCode, WHITE};
|
|
use turtle_lib::*;
|
|
|
|
#[macroquad::main("Clock (Threaded)")]
|
|
async fn main() {
|
|
const HOURS: i32 = 12;
|
|
const MINUTES: f32 = 60.0;
|
|
const SECONDS: f32 = 60.0;
|
|
const FULL_CIRCLE: f32 = 360.0;
|
|
|
|
let mut app = TurtleApp::new();
|
|
let turtle_tx = app.create_turtle_channel(10);
|
|
|
|
// Spawn a thread that generates clock commands every second
|
|
std::thread::spawn(move || {
|
|
let mut last_second = -1i32;
|
|
|
|
loop {
|
|
let now = Local::now();
|
|
let current_second = now.second() as i32;
|
|
|
|
// Only generate commands when the time changes
|
|
if current_second != last_second {
|
|
let mut turtle = create_turtle_plan();
|
|
turtle.reset().set_speed(1100).left(90.0);
|
|
|
|
// Draw the clock circle and hour markers
|
|
for i in 1..=HOURS {
|
|
turtle
|
|
.pen_up()
|
|
.go_to(vec2(0.0, 0.0))
|
|
.right(FULL_CIRCLE / HOURS as f32)
|
|
.forward(205.0);
|
|
|
|
let pen_size = if (i) % 3 == 0 { 7.0 } else { 2.0 };
|
|
turtle
|
|
.set_pen_width(pen_size)
|
|
.pen_down()
|
|
.forward(10.0)
|
|
.right(90.0)
|
|
.pen_up()
|
|
.backward(pen_size.max(4.0) + (i / 10) as f32 * 4.0)
|
|
.pen_down()
|
|
.write_text(format!("{i}"), 2 * pen_size as i32 + 14)
|
|
.pen_up()
|
|
.forward(pen_size.max(4.0) + (i / 10) as f32 * 4.0)
|
|
.pen_down()
|
|
.left(90.0);
|
|
}
|
|
|
|
// Draw the hour hand
|
|
turtle
|
|
.pen_up()
|
|
.go_to(vec2(0.0, 0.0))
|
|
.set_heading(90.0)
|
|
.right(FULL_CIRCLE / HOURS as f32 * (now.hour() % 12) as f32)
|
|
.set_pen_width(7.0)
|
|
.pen_down()
|
|
.forward(120.0);
|
|
|
|
// Draw the minute hand
|
|
turtle
|
|
.pen_up()
|
|
.go_to(vec2(0.0, 0.0))
|
|
.set_heading(90.0)
|
|
.right(FULL_CIRCLE / MINUTES * now.minute() as f32)
|
|
.set_pen_width(3.0)
|
|
.pen_down()
|
|
.forward(150.0);
|
|
|
|
// Draw the second hand
|
|
turtle
|
|
.pen_up()
|
|
.go_to(vec2(0.0, 0.0))
|
|
.set_heading(90.0)
|
|
.right(FULL_CIRCLE / SECONDS * now.second() as f32)
|
|
.set_pen_width(1.0)
|
|
.pen_down()
|
|
.forward(180.0);
|
|
|
|
// Send the command queue to the main thread
|
|
let _ = turtle_tx.send(turtle.build());
|
|
last_second = current_second;
|
|
}
|
|
|
|
// Sleep briefly to avoid busy-waiting (update ~10 times per second)
|
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
}
|
|
});
|
|
|
|
// Main render loop
|
|
loop {
|
|
clear_background(WHITE);
|
|
|
|
// Process any pending commands from the worker thread
|
|
app.process_commands();
|
|
|
|
app.update();
|
|
app.render();
|
|
|
|
if is_key_pressed(KeyCode::Escape) || is_key_pressed(KeyCode::Q) {
|
|
break;
|
|
}
|
|
|
|
next_frame().await;
|
|
}
|
|
}
|