Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/animation.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,9 @@ keep the default value `1`.

Besides the timing, `tick()` empties the window event queue. It collects all new
mouse moves, button clicks, and key presses so you can read them from the global
lists in `drawzero.utils.events`. If you skip `tick()`, you will not see user
input and the window may stop responding.
lists such as `keysdown`, `keysup`, `mousemotions`, and `mousebuttonsdown`
that appear after `from drawzero import *`. If you skip `tick()`, you will not
see user input and the window may stop responding.

### `sleep(t=1)`

Expand Down
5 changes: 3 additions & 2 deletions docs/animation.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,9 @@ DrawZero выполнит внутреннее обновление кадра `

Помимо тайминга, `tick()` очищает очередь событий окна. Он собирает все новые
движения мыши, нажатия кнопок и клавиш, чтобы вы могли прочитать их из
глобальных списков в `drawzero.utils.events`. Если вы пропустите `tick()`, вы не
увидите ввод пользователя, и окно может перестать отвечать.
глобальных списков `keysdown`, `keysup`, `mousemotions`, `mousebuttonsdown`,
которые появляются после `from drawzero import *`. Если вы пропустите `tick()`,
вы не увидите ввод пользователя, и окно может перестать отвечать.

### `sleep(t=1)`

Expand Down
91 changes: 31 additions & 60 deletions docs/architecture.en.md
Original file line number Diff line number Diff line change
@@ -1,81 +1,52 @@
# DrawZero Architecture Overview

This document summarizes the internal layout of the DrawZero project so that contributors can quickly orient themselves in the codebase.
This document summarizes how DrawZero is put together without exposing low-level implementation details. It highlights the responsibilities of each part so that contributors know where to add new features while keeping the public API simple for learners.

## Package layout
## Public API surface

```
src/drawzero/
├── __init__.py # Re-exported public API
├── __main__.py # CLI entry point (copies examples, draws demo frame)
├── examples/ # Tutorial-style scripts executed by tests
└── utils/ # Core rendering, math, and convenience helpers
```

Other notable top-level folders:

* `docs/` – MkDocs documentation shipped on PyPI; suitable place for additional guides.
* `tests/` – Pytest and unittest suites that import examples, validate converters, gradients, points, and internationalization helpers.

## Public API surface (`src/drawzero/__init__.py`)

The package exposes a “flat” API meant to mimic turtle/pygame zero ergonomics. All drawing helpers are imported from `utils.draw`, while math helpers come from sibling modules:
Everything in the library is re-exported, so one import is enough:

* Drawing primitives: `line`, `circle`, `rect`, `polygon`, `text`, `image`, etc.
* Filled variants share the same renderer entry points by passing `line_width=0`.
* Animation and timing helpers: `tick`, `sleep`, `run`, `fps`, `quit`.
* Input helpers proxy the renderer’s key/mouse state (`get_keys_pressed`, `keysdown`, `mousebuttonsdown`, ...).
* Utilities: `Pt` (vector/turtle hybrid), `Gradient`, `copy_examples`, color constants (`C`, `COLORS`, `ALL_COLORS`).

Every user-facing function ultimately routes through the renderer stack described below.

## Rendering pipeline (`utils.draw` → `utils.renderer`)

`utils.draw` performs argument validation and coercion before delegating to the actual drawing backend. Common steps for each primitive:

1. Convert user-supplied data through converter helpers (`utils.converters`). These functions normalize coordinates, radii, rectangles, and colors, while collecting rich error messages via `BadDrawParmsError` and localized strings in `utils.i18n`.
2. Choose the backend module depending on the `EJUDGE_MODE` environment variable. GUI builds import `utils.renderer` (pygame-based). Text-mode/CI builds fall back to `utils.renderer_ejudge`, which prints commands for automated judges.
3. After the renderer call, update cached input event lists so that consumers see mouse/key positions in logical (virtual) coordinates.
```python
from drawzero import *
```

### `utils.renderer`
The call exposes:

The pygame renderer lazily creates a resizable window and keeps an off-screen copy to survive resizes. Important responsibilities:
* Drawing helpers such as `line`, `circle`, `rect`, `polygon`, `text`, `image`, and their filled variants.
* Animation and timing helpers: `run`, `tick`, `sleep`, `fps`, and `quit`.
* Input helpers for reading the keyboard and mouse: `get_keys_pressed`, `keys_mods_pressed`, `get_mouse_pressed`, plus the shared lists `keysdown`, `keysup`, `mousemotions`, `mousebuttonsdown`, and `mousebuttonsup`.
* Utility objects: the `screen` shim used by Pygame Zero style scripts, the `Pt` vector/turtle hybrid, the `Gradient` color ramp builder, localization via `set_lang`, the color namespaces (`C`, `COLORS`, `THECOLORS`, `ALL_COLORS`), keyboard constants (`K`, `KEY`), and `copy_examples()` for scaffolding tutorials.
* Canvas configuration helpers such as `set_virtual_size()`.

* Surface management: `_create_surface()` defers window creation until the first drawing call; `_resize()` rescales existing content when SDL emits a resize event.
* Primitive drawing: each `draw_*` function handles optional alpha blending by drawing into temporary `pygame.Surface` buffers when needed.
* Event pump: `draw_tick()` advances the clock (30 FPS target), flushes the event queue, and populates global lists (`keysdown`, `mousebuttonsdown`, etc.) consumed by `utils.draw`.
* Lifecycle hooks: `atexit.register(_draw_go)` keeps the pygame loop alive until the process exits; `_init()` configures DPI-awareness on Windows and centers the window.
* Coordinate scaling: setter functions from `utils.screen_size` maintain the mapping between “virtual” 1000×1000 coordinates (used by the API) and the actual window size.
All user guides in the documentation assume this import style.

### `utils.renderer_ejudge`
## Rendering flow

A lightweight stub used when `EJUDGE_MODE=true` (e.g., tests running without a GUI). It mirrors the renderer API but simply prints serialized drawing commands. Screen size is fixed to 1000×1000 so coordinate conversion remains consistent.
Every drawing helper validates and normalizes its arguments (coordinates, angles, colors, and radii) before forwarding them to the active renderer. The renderer choice depends on the `EJUDGE_MODE` environment variable:

## Coordinate transforms (`utils.screen_size`)
* When the variable is unset or false, DrawZero opens a graphical window backed by pygame and renders directly to the screen.
* When `EJUDGE_MODE` is true (used in automated graders and CI), DrawZero switches to a text-mode backend that prints serialized drawing commands instead of opening a window.

DrawZero always exposes a 1000×1000 virtual canvas. `set_virtual_size()` can change that logical resolution, and `set_real_size()` is called by the renderer when it knows the real pixel size. The module exposes helpers to convert between coordinate spaces:
Regardless of the backend, calling `tick()` processes window events, updates the shared input lists, and keeps the animation running near 30 FPS. Skipping `tick()` prevents new events from reaching the program, so the documentation emphasizes placing it in every frame loop.

* `to_canvas_x` / `to_canvas_y` – convert virtual coordinates to actual pixels.
* `from_canvas_x` / `from_canvas_y` – convert back to logical coordinates (used when reporting mouse positions).
## Virtual canvas and coordinate scaling

All converters and event wrappers rely on this module, so adjust it carefully if supporting non-square canvases.
The library works with a virtual 1000×1000 canvas. Drawing helpers accept integers or floats; values are converted to the current virtual size and then mapped to the actual window size. `set_virtual_size()` lets scripts change the logical resolution when they want to draw in a different coordinate space. Mouse helpers always return positions in the same virtual coordinate system, regardless of the real window dimensions.

## Math and utility helpers
## Utility helpers and constants

* `utils.pt.Pt` implements a mutable 2D vector with turtle-style movement, arithmetic operators, and convenience methods (`forward`, `rotate_around`, `distance`, etc.). Examples rely on it for animation logic.
* `utils.gradient.Gradient` constructs color ramps over a numeric domain (default 0–1). It uses the same converter/error infrastructure to provide consistent validation.
* `utils.colors` exposes pygame’s color table both as attribute access (`C.red`) and dictionaries (`COLORS`, `ALL_COLORS`).
* `utils.key_flags` mirrors pygame key constants so code can use `K.<name>` even when pygame is absent (constants are loaded lazily when available).
* `utils.copy_examples.copy_examples()` copies `src/drawzero/examples` into the current working directory; the CLI entry point calls it to bootstrap learners.
Several helpers ship with the public API and are heavily used by examples and tests:

## Examples (`src/drawzero/examples`)
* `Pt` stores a position and heading, supports turtle-style movement, and acts like a mutable 2D vector.
* `Gradient` converts numbers into colors by interpolating across a list of samples.
* Color namespaces (`C`, `COLORS`, `THECOLORS`, `ALL_COLORS`) make pygame's color table easy to access without typing RGB triples.
* Keyboard constants `K` and `KEY` mirror pygame's naming so programs can use `K.space`, `K.left`, and similar attributes in both GUI and headless environments.
* `copy_examples()` copies the bundled examples into the working directory to help newcomers start experimenting.

Short, bilingual scripts demonstrate the API: drawing primitives, loops, animations, gradients, images, and simple interactive games. Tests import many of them to ensure they still execute, so keep side effects (e.g., infinite loops) behind guards if you add new ones.
## Examples and documentation

## Tests (`tests/`)
The package includes bilingual example scripts that demonstrate primitives, animations, gradients, image loading, and keyboard/mouse interaction. The MkDocs documentation mirrors those scripts with detailed explanations. Tests import many of the examples directly, so new examples must avoid side effects at import time unless they are guarded by `if __name__ == "__main__":`.

* `test_examples_gui_mode.py` imports each example module to ensure it runs against the real renderer.
* `test_examples_text_mode.py` sets `EJUDGE_MODE` and asserts the text renderer paths work.
* The remaining pytest modules cover specific utilities: converter validation, localized error messages, gradient interpolation, the `Pt` vector API, and `copy_examples()` behavior.
## Test suite

Use these tests as references when extending validation logic or adding new primitives.
Automated tests cover the drawing helpers, gradients, localization messages, point arithmetic, and example imports. Continuous integration runs them in both graphical mode and text mode (with `EJUDGE_MODE=true`) to ensure the public API behaves the same in either environment.
Loading
Loading