All issues identified across Sections 3.1, 3.2, and 3.3 have been
resolved, verified with unit tests, automated compilation checks under
`-D float_literal_f32_fallback`, and headless SVG export runs.
-
**[angle.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/general/angle.rs)**:
- Implemented `From<f64>` and `From<usize>` for `Degrees`.
- Added unit tests `from_integer` and `from_f64` to verify conversion
accuracy.
-
**[fontsize.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/general/fontsize.rs)**:
- Implemented `From<f64>` for `FontSize`.
- Refactored `FontSize::value(self)` to pass Copy type by value.
- Added unit test `font_size_conversions`.
-
**[general.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/general.rs)**:
- Implemented `From<f64>`, `From<i32>`, and `From<usize>` for
`AnimationSpeed`.
- Added unit test `animation_speed_conversions`.
- Re-exported `macroquad` crate (`pub use macroquad;`) so downstream
code and macro expansions have reliable direct access.
-
**[export.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/export.rs)**:
- Made `parse_svg_export_arg()` public.
- Implemented `run_headless_svg_export<F>(mut build_commands: F,
filename: &str) -> Result<(), ExportError>` that executes commands
using `app.step_animations()`, avoiding all window/GUI dependencies
and never calling `std::process::exit`.
- Updated `handle_svg_export` to delegate to
`run_headless_svg_export`.
-
**[lib.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/lib.rs)**:
- Extracted `pub fn step_animations(&mut self)` from `update(&mut
self)`, allowing command queue draining and tween stepping
headlessly without querying window mouse position or events.
-
**[state.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/state.rs)**:
- Changed `TurtleWorld::new()` to initialize camera with
`Camera2D::default()` instead of querying `screen_width()` /
`screen_height()`, eliminating panics when running without a
Macroquad window.
-
**[turtle-lib-macros/src/lib.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib-macros/src/lib.rs)**:
- Added `validate_input` helper providing clean compile diagnostics
with spans for:
- Multiple arguments: `#[turtle_main] functions must take either 0
arguments or a single &mut TurtlePlan`
- Non-unit return types: `#[turtle_main] functions cannot have a
return type`
- Async functions: `#[turtle_main] functions cannot be async`
- Replaced `#[macroquad::main]` wrapper expansion with a native `fn
main()` that inspects CLI arguments first. If `--export-svg` is
present, it runs `run_headless_svg_export` directly and returns
cleanly without opening a window. Otherwise, it launches
`macroquad::Window::new(#window_title, async { ... })`.
- Added 5 unit tests in `turtle-lib-macros` testing signature
validation.
-
**[.vscode/launch.json](file:///home/dietrich/Projekte/Source/turtlers/.vscode/launch.json)**:
- Removed stale references to nonexistent `turtle-example` and
`turtle-ui`.
- Added debug configurations for `turtle-lib` tests,
`turtle-lib-macros` tests, `hello_turtle`, and `breadboard`.
-
**[breadboard.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/breadboard.rs)**:
- Refactored example to use `#[turtle_main("Breadboard")]`.
- Removed `#[cfg(feature = "svg")]` and the early-exit message; the
example now renders directly on screen by default and supports
`--features svg -- --export-svg breadboard.svg`.
-
**[README.md](file:///home/dietrich/Projekte/Source/turtlers/README.md)**:
- Documented optional user-level `~/.cargo/config.toml` mold/lld
fast-linking configuration under "Building and Running".
---
```bash
cargo test --workspace
```
- **Result**: 21 passed (16 in `turtle-lib`, 5 in `turtle-lib-macros`),
34 doctests passed, 0 failed.
```bash
RUSTFLAGS="-D float_literal_f32_fallback" cargo check --workspace
--all-targets --all-features
RUSTFLAGS="-D float_literal_f32_fallback" cargo check --package
turtle-lib --examples --all-features
```
- **Result**: Passed with 0 errors and 0 fallback warnings across all
workspace crates and all 30 examples.
```bash
cargo run --package turtle-lib --example hello_turtle --features svg --
--export-svg hello.svg
cargo run --package turtle-lib --example breadboard --features svg --
--export-svg breadboard.svg
```
- **Result**: Both exported SVG files successfully and exited with code
0 without creating or flashing a graphical window.
```bash
cargo check --package turtle-lib --example breadboard
```
- **Result**: Compiled cleanly with 0 errors when SVG feature is
disabled.
183 lines
4.7 KiB
Rust
183 lines
4.7 KiB
Rust
//! Angle unit newtypes: `Degrees` and `Radians`.
|
|
//!
|
|
//! ## Design
|
|
//!
|
|
//! Two separate types instead of a single enum so that function signatures are
|
|
//! self-documenting and the compiler rejects wrong-unit arguments.
|
|
//!
|
|
//! - **`Degrees`** — public API boundary. Builder methods and `TurtleCommand`
|
|
//! fields that originate from user input store this type. Convert with
|
|
//! `as_radians()` before entering the rendering pipeline.
|
|
//!
|
|
//! - **`Radians`** — internal pipeline. All geometry functions and
|
|
//! `TurtleParams` arithmetic work in radians. Extract the raw `f32` with
|
|
//! `value()` only where stdlib trig functions (`sin`, `cos`, …) require it.
|
|
//!
|
|
//! There is intentionally **no** conversion from `Radians` back to `f32` that
|
|
//! strips the unit tag silently — use `.value()` explicitly and at the last
|
|
//! possible moment.
|
|
|
|
use super::Precision;
|
|
use std::ops::Neg;
|
|
|
|
/// An angle measured in degrees.
|
|
///
|
|
/// Used at the public API boundary. Convert to [`Radians`] with `as_radians()`
|
|
/// before passing into internal rendering functions.
|
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Default)]
|
|
pub struct Degrees(pub Precision);
|
|
|
|
impl Degrees {
|
|
/// Construct from a raw degrees value.
|
|
#[must_use]
|
|
pub fn new(v: Precision) -> Self {
|
|
Self(v)
|
|
}
|
|
|
|
/// Convert to [`Radians`] for use in the rendering pipeline.
|
|
///
|
|
/// This is the **only** correct way to enter the internal math layer.
|
|
#[must_use]
|
|
pub fn as_radians(self) -> Radians {
|
|
Radians(self.0.to_radians())
|
|
}
|
|
|
|
/// The raw degrees value.
|
|
///
|
|
/// Use only for degree-to-degree arithmetic (e.g. negating a turn angle
|
|
/// before storing it as a command). Do not pass this to trig functions.
|
|
#[must_use]
|
|
pub fn value(self) -> Precision {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl Neg for Degrees {
|
|
type Output = Self;
|
|
fn neg(self) -> Self {
|
|
Self(-self.0)
|
|
}
|
|
}
|
|
|
|
impl From<f32> for Degrees {
|
|
fn from(v: f32) -> Self {
|
|
Self(v)
|
|
}
|
|
}
|
|
|
|
impl From<f64> for Degrees {
|
|
fn from(v: f64) -> Self {
|
|
Self(v as Precision)
|
|
}
|
|
}
|
|
|
|
impl From<i32> for Degrees {
|
|
fn from(v: i32) -> Self {
|
|
Self(v as Precision)
|
|
}
|
|
}
|
|
|
|
impl From<i16> for Degrees {
|
|
fn from(v: i16) -> Self {
|
|
Self(Precision::from(v))
|
|
}
|
|
}
|
|
|
|
impl From<usize> for Degrees {
|
|
fn from(v: usize) -> Self {
|
|
Self(v as Precision)
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/// An angle measured in radians.
|
|
///
|
|
/// Used in all internal function signatures and geometry math. Extract the
|
|
/// raw `f32` with [`value()`](Radians::value) only when calling stdlib trig
|
|
/// functions (`sin`, `cos`, etc.).
|
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Default)]
|
|
pub struct Radians(pub Precision);
|
|
|
|
impl Radians {
|
|
/// Construct from a raw radians value.
|
|
#[must_use]
|
|
pub fn new(v: Precision) -> Self {
|
|
Self(v)
|
|
}
|
|
|
|
/// Convert to [`Degrees`] for display or user-facing output.
|
|
#[must_use]
|
|
pub fn as_degrees(self) -> Degrees {
|
|
Degrees(self.0.to_degrees())
|
|
}
|
|
|
|
/// The raw radians value.
|
|
///
|
|
/// Use only when calling stdlib trig functions or other `f32`-based
|
|
/// math APIs. Keep `Radians` as the type at all internal function
|
|
/// boundaries.
|
|
#[must_use]
|
|
pub fn value(self) -> Precision {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl Neg for Radians {
|
|
type Output = Self;
|
|
fn neg(self) -> Self {
|
|
Self(-self.0)
|
|
}
|
|
}
|
|
|
|
impl From<f32> for Radians {
|
|
fn from(v: f32) -> Self {
|
|
Self(v)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::f32::consts::PI;
|
|
|
|
#[test]
|
|
fn degrees_to_radians_roundtrip() {
|
|
let deg = Degrees::new(180.0);
|
|
let rad = deg.as_radians();
|
|
assert!(
|
|
(rad.value() - PI).abs() < 1e-6,
|
|
"expected π, got {}",
|
|
rad.value()
|
|
);
|
|
let back = rad.as_degrees();
|
|
assert!(
|
|
(back.value() - 180.0).abs() < 1e-4,
|
|
"expected 180°, got {}",
|
|
back.value()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn negation() {
|
|
assert_eq!(-Degrees::new(90.0), Degrees::new(-90.0));
|
|
assert_eq!(-Radians::new(1.0), Radians::new(-1.0));
|
|
}
|
|
|
|
#[test]
|
|
fn from_integer() {
|
|
let d: Degrees = 90_i32.into();
|
|
assert_eq!(d, Degrees::new(90.0));
|
|
let d2: Degrees = 45_i16.into();
|
|
assert_eq!(d2, Degrees::new(45.0));
|
|
let d3: Degrees = 180_usize.into();
|
|
assert_eq!(d3, Degrees::new(180.0));
|
|
}
|
|
|
|
#[test]
|
|
fn from_f64() {
|
|
let d: Degrees = 90.0_f64.into();
|
|
assert_eq!(d, Degrees::new(90.0));
|
|
}
|
|
}
|