feat: implement keyboard and mouse input, pattern placement, and HiDPI scaling
Add configurable key bindings (TOML), modifier-aware dispatch, mouse-driven pattern placement with preview and rotation, cell grid storage/rendering, and fractional/integer HiDPI scaling. Archive the input-and-placement change and sync its specs to main specs. Tests: Hashtable, Config, Grid, Pattern (4/4 pass).
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-11-08
|
||||
@@ -0,0 +1,80 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
Current application (`wlgol.c`) renders an animated stripe pattern into a fixed 400x400 shm buffer, handles only Escape, and ignores configure size and output scale. Input modules (`tkeyboard`, `tpointer`) exist but pointer motion/buttons are unimplemented and keyboard callbacks do not carry modifiers. HiDPI is unsupported. See `proposal.md` for motivation.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Configurable, modifier-aware key bindings with TOML merge over defaults.
|
||||
- Interactive pattern placement with preview, rotation, click placement, cancellation.
|
||||
- HiDPI-correct rendering (fractional and integer).
|
||||
- Cell grid storage/rendering with resize preservation and wall boundaries.
|
||||
|
||||
**Non-Goals:**
|
||||
- Game of Life simulation rules (future change).
|
||||
- Text rendering/HUD (status via stdout).
|
||||
- Persistent grid save/load.
|
||||
- Fractional scaling on compositors lacking the protocol beyond integer fallback.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: TOML config via vendored `tomlc99`
|
||||
- **Choice:** Vendor `tomlc99` (single .c/.h) for parsing `~/.config/wlgol/keys.conf`.
|
||||
- **Rationale:** User requested TOML; no system TOML library available; vendoring avoids new system dependency.
|
||||
- **Alternative:** Hand-rolled subset parser (less code, but TOML edge cases and maintenance burden).
|
||||
|
||||
### D2: Binding representation
|
||||
- **Choice:** Store bindings as array of `{mods_mask, keysym, action}`. Lookup by exact modifier mask (ignoring lock mods) and keysym on key press.
|
||||
- **Rationale:** Simple, fast enough for single-threaded event loop.
|
||||
- **Alternative:** Hash table keyed by (mods, keysym) — premature; linear scan over ~20 bindings is trivial.
|
||||
|
||||
### D3: Keyboard callback carries modifiers
|
||||
- **Choice:** Extend `keyboard_key_handler_t` to `(..., uint32_t mods, void *user_data)`, computed in `tkeyboard.c` from xkb state.
|
||||
- **Rationale:** Binding matching needs modifier state; centralizes xkb modifier queries.
|
||||
|
||||
### D4: HiDPI via fractional scale + viewporter
|
||||
- **Choice:** Prefer `wp_fractional_scale_v1`; render physical buffer = ceil(logical * scale); set buffer_scale=1 and `wp_viewport` destination = logical size. Fallback to `wl_output.scale` integer (treat as scale value).
|
||||
- **Rationale:** Exact fractional scaling; unifies rendering path through viewporter.
|
||||
- **Alternative:** Integer-only scaling (simpler but blurry/wrong at 125%/150%).
|
||||
|
||||
### D5: Logical canvas and grid
|
||||
- **Choice:** Logical surface size from xdg configure (default 400x400). Grid dimensions = logical / 8. Cell size in logical pixels fixed at 8.
|
||||
- **Rationale:** Consistent interaction across scales; HiDPI keeps physical cell size proportional.
|
||||
|
||||
### D6: Grid storage and resize
|
||||
- **Choice:** `struct grid { int cols, rows; unsigned char *cells; }` (0/1). On resize, allocate new buffer, copy overlapping region, free old.
|
||||
- **Rationale:** Preserves user work on window resize; simple.
|
||||
|
||||
### D7: Pattern definitions and rotation
|
||||
- **Choice:** Each pattern stores base cell offsets and a rotation (0..3). Transform offsets at use time; bounding box computed from transformed offsets. Anchor = top-left of bounding box at cursor cell.
|
||||
- **Rationale:** Avoids duplicating 4 variants per pattern; rotation stays consistent.
|
||||
|
||||
### D8: Placement state machine
|
||||
- **Choice:** States `IDLE` and `ARMED`. Key select → ARMED; motion updates preview cell; rotate increments rotation; left click places if in-bounds (stays ARMED); Escape → IDLE.
|
||||
- **Rationale:** Matches requested UX; tool remains active for repeated placement.
|
||||
|
||||
### D9: Protocols generation
|
||||
- **Choice:** Vendor/copy `viewporter.xml` and `fractional-scale-v1.xml` into repo (from system wayland-protocols) and generate headers/sources via wayland-scanner in CMake, mirroring xdg-shell.
|
||||
- **Rationale:** Reproducible builds without depending on system protocol paths.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Fractional scaling complexity] → Mitigation: isolate in `scale.c`; integer fallback ensures basic functionality.
|
||||
- [Resize resets grid partially] → Mitigation: preserve overlapping region; document behavior.
|
||||
- [tomlc99 vendoring licensing] → Mitigation: verify license (MIT) before commit.
|
||||
- [No font rendering] → Status via stdout; acceptable for this change.
|
||||
- [Placement near edges] → Mitigation: reject invalid placement; preview indicates invalid state (e.g., red tint).
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Add generated protocol files and CMake changes.
|
||||
2. Vendor tomlc99.
|
||||
3. Implement modules and integrate into wlgol.c.
|
||||
4. Update tests (hashtable unchanged; add config/grid unit tests if feasible).
|
||||
5. Run ctest and build.
|
||||
|
||||
## Open Questions
|
||||
|
||||
None — all blocking decisions resolved during exploration.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Proposal
|
||||
|
||||
## Why
|
||||
|
||||
wlgol currently renders an animated stripe pattern and has no Game of Life state, no input handling beyond Escape, and no HiDPI awareness. Before implementing the automaton, the application needs a usable interaction layer: configurable keyboard bindings, mouse-driven placement of patterns with a live preview, and rendering that stays crisp on scaled displays. This change establishes that layer so later GoL mechanics can build on it.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add configurable keyboard bindings loaded from `~/.config/wlgol/keys.conf` (TOML), merged over built-in defaults, with modifier-aware matching (Ctrl/Shift/Alt/Super).
|
||||
- Extend keyboard handling to report active modifiers and dispatch bound actions (`quit`, `cancel`, `rotate`, `select:<pattern>`, `clear`, `pause`, `step`, `speed_up`, `speed_down`, `toggle_grid`).
|
||||
- Implement mouse tracking and click handling: selecting a tool shows a rotating preview that follows the cursor; `Space` rotates 90°; left click places the pattern (tool stays armed); `Esc` cancels.
|
||||
- Add a cell grid (8 logical px per cell, wall boundary) with storage, resize-with-preserve, and rendering of alive cells.
|
||||
- Add four built-in patterns (Glider, Blinker, Block, Toad) with rotation support.
|
||||
- Add HiDPI scaling (fractional via `wp_fractional_scale` + `wp_viewporter`, fallback to `wl_output.scale`) with logical/physical coordinate conversion.
|
||||
- Auto-generate a default `keys.conf` on first run if missing.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `keyboard-bindings`: configurable, modifier-aware key-to-action mapping with defaults and TOML override.
|
||||
- `pattern-placement`: tool selection, live preview, rotation, click placement, cancellation.
|
||||
- `display-scaling`: fractional and integer HiDPI scaling with logical/physical conversion.
|
||||
- `cell-grid`: grid storage, resize handling, wall boundary, alive-cell rendering.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
None.
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected code: `wlgol.c`, `tkeyboard.c/h`, `tpointer.c/h`; new modules `config`, `grid`, `pattern`, `scale`, `render`.
|
||||
- New dependency: vendored `tomlc99` for TOML parsing; generated Wayland protocols `viewporter` and `fractional-scale-v1`.
|
||||
- Config I/O under `~/.config/wlgol/`; no public API changes.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Spec Delta
|
||||
|
||||
## Purpose
|
||||
|
||||
Stores and renders the Game of Life cell grid with a fixed cell size and wall boundaries, independent of future simulation rules.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Grid stores alive and dead cells
|
||||
The system SHALL maintain a grid of cells, each either alive or dead, addressable by column and row.
|
||||
|
||||
#### Scenario: Cell access
|
||||
- **WHEN** a cell is set to alive
|
||||
- **THEN** reading that cell returns alive
|
||||
- **AND** cells never set remain dead
|
||||
|
||||
### Requirement: Boundary cells are treated as dead
|
||||
The system SHALL treat any cell outside the grid bounds as dead and shall not store state outside the grid.
|
||||
|
||||
#### Scenario: Out of bounds read
|
||||
- **WHEN** a cell position outside the grid is queried
|
||||
- **THEN** the result is dead
|
||||
|
||||
### Requirement: Grid resizes while preserving overlapping cells
|
||||
The system SHALL preserve cells that remain within bounds when the grid dimensions change, clearing only cells that fall outside the new bounds.
|
||||
|
||||
#### Scenario: Enlarge grid
|
||||
- **WHEN** the grid grows in either dimension
|
||||
- **THEN** previously alive cells within the old bounds remain alive
|
||||
|
||||
#### Scenario: Shrink grid
|
||||
- **WHEN** the grid shrinks in either dimension
|
||||
- **THEN** cells outside the new bounds are discarded
|
||||
- **AND** remaining cells keep their state
|
||||
|
||||
### Requirement: Cell size is fixed in logical pixels
|
||||
The system SHALL use a cell size of 8 logical pixels, so grid dimensions derive from the logical surface size.
|
||||
|
||||
#### Scenario: Dimension calculation
|
||||
- **WHEN** the logical surface is 400 pixels wide
|
||||
- **THEN** the grid has 50 columns
|
||||
|
||||
### Requirement: Alive cells are rendered
|
||||
The system SHALL render alive cells distinctly from dead cells.
|
||||
|
||||
#### Scenario: Render alive cell
|
||||
- **WHEN** a cell is alive
|
||||
- **THEN** it is drawn with the alive cell color
|
||||
|
||||
### Requirement: Grid can be cleared
|
||||
The system SHALL set all cells to dead when the clear action is triggered.
|
||||
|
||||
#### Scenario: Clear grid
|
||||
- **WHEN** the user triggers the clear action
|
||||
- **THEN** all cells become dead
|
||||
|
||||
### Requirement: Grid lines can be toggled
|
||||
The system SHALL show or hide grid lines when the toggle_grid action is triggered.
|
||||
|
||||
#### Scenario: Toggle lines
|
||||
- **WHEN** the user triggers the toggle_grid action
|
||||
- **THEN** grid line visibility flips
|
||||
@@ -0,0 +1,45 @@
|
||||
# Spec Delta
|
||||
|
||||
## Purpose
|
||||
|
||||
Keeps the application rendering crisp on displays with integer or fractional scaling by converting between logical and physical coordinates.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Fractional scale is applied when available
|
||||
The system SHALL use the compositor-provided fractional preferred scale for rendering when the fractional scale protocol is available.
|
||||
|
||||
#### Scenario: Fractional display
|
||||
- **WHEN** the compositor sends a preferred scale of 1.5
|
||||
- **THEN** the render buffer is created at 1.5 times the logical size
|
||||
- **AND** the buffer is displayed at the logical size
|
||||
|
||||
### Requirement: Integer scale is used as fallback
|
||||
The system SHALL use the output's integer scale when fractional scale is unavailable.
|
||||
|
||||
#### Scenario: No fractional protocol
|
||||
- **WHEN** the compositor does not support fractional scaling
|
||||
- **THEN** the render buffer uses the output integer scale
|
||||
- **AND** the buffer is displayed at the logical size
|
||||
|
||||
### Requirement: Input coordinates remain logical
|
||||
The system SHALL report mouse positions and grid cells in logical coordinates regardless of the active scale.
|
||||
|
||||
#### Scenario: Scaled click
|
||||
- **WHEN** the user clicks on a scaled display
|
||||
- **THEN** the selected grid cell matches the visual location of the cursor
|
||||
|
||||
### Requirement: Rendering uses physical resolution
|
||||
The system SHALL render all content at physical resolution so that lines and cells stay sharp at the active scale.
|
||||
|
||||
#### Scenario: Sharp cells
|
||||
- **WHEN** the application runs on a 2x scaled display
|
||||
- **THEN** cells are drawn with 2 physical pixels per logical pixel
|
||||
|
||||
### Requirement: Scale changes trigger re-render
|
||||
The system SHALL recreate buffers and re-render when the active scale changes.
|
||||
|
||||
#### Scenario: Scale change
|
||||
- **WHEN** the compositor changes the preferred scale
|
||||
- **THEN** the application updates its buffers
|
||||
- **AND** subsequent frames use the new scale
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
# Spec Delta
|
||||
|
||||
## Purpose
|
||||
|
||||
Allows users to configure keyboard shortcuts that map key combinations to application actions, with sensible built-in defaults when no configuration file exists.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Default bindings are available without configuration
|
||||
The system SHALL provide built-in default bindings for all documented actions, including digits 1-4 selecting the Glider, Blinker, Block, and Toad patterns, Space rotating, Escape cancelling, and Ctrl+Q quitting.
|
||||
|
||||
#### Scenario: No config file present
|
||||
- **WHEN** the application starts and `~/.config/wlgol/keys.conf` does not exist
|
||||
- **THEN** the application uses the built-in default bindings
|
||||
- **AND** creates the config file with the default contents
|
||||
|
||||
### Requirement: Configuration file overrides defaults by merge
|
||||
The system SHALL read `~/.config/wlgol/keys.conf` as TOML when it exists and merge its entries over the built-in defaults, so only specified keys change.
|
||||
|
||||
#### Scenario: Partial override
|
||||
- **WHEN** the config file defines a binding for key "1" only
|
||||
- **THEN** key "1" uses the configured action
|
||||
- **AND** all other keys keep their default bindings
|
||||
|
||||
#### Scenario: Full replacement of a key
|
||||
- **WHEN** the config file defines a binding for a key that has a default
|
||||
- **THEN** the configured action replaces the default for that key
|
||||
|
||||
### Requirement: Key combinations are modifier-aware
|
||||
The system SHALL match a binding only when the modifiers required by the binding are active, supporting Ctrl, Shift, Alt, and Super.
|
||||
|
||||
#### Scenario: Modifier required
|
||||
- **WHEN** the user presses Q while Ctrl is held and a `ctrl+q = quit` binding exists
|
||||
- **THEN** the quit action is triggered
|
||||
|
||||
#### Scenario: Modifier not held
|
||||
- **WHEN** the user presses Q without Ctrl while a `ctrl+q = quit` binding exists
|
||||
- **THEN** the quit action is not triggered by that binding
|
||||
|
||||
### Requirement: Readable key aliases are supported
|
||||
The system SHALL accept human-readable key names such as space, escape, enter, and arrow names in binding keys.
|
||||
|
||||
#### Scenario: Alias binding
|
||||
- **WHEN** the config file contains `space = rotate`
|
||||
- **THEN** pressing the space bar triggers the rotate action
|
||||
|
||||
### Requirement: Malformed entries are ignored
|
||||
The system SHALL ignore malformed or unrecognized binding entries and continue using defaults for the affected keys.
|
||||
|
||||
#### Scenario: Invalid entry
|
||||
- **WHEN** the config file contains an entry with an unknown action
|
||||
- **THEN** that entry is ignored
|
||||
- **AND** the application still starts successfully
|
||||
|
||||
### Requirement: Actions trigger on key press
|
||||
The system SHALL dispatch a bound action when the corresponding key is pressed, not on release.
|
||||
|
||||
#### Scenario: Press triggers action
|
||||
- **WHEN** the user presses a key with a valid binding
|
||||
- **THEN** the bound action is invoked once for that press
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
# Spec Delta
|
||||
|
||||
## Purpose
|
||||
|
||||
Lets users select a Game of Life pattern, preview it under the mouse cursor, rotate it, and place copies on the grid with a click.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Selecting a pattern arms a placement tool
|
||||
The system SHALL activate a placement tool for the selected pattern when a `select:<pattern>` action is triggered, and show a preview following the cursor.
|
||||
|
||||
#### Scenario: Tool activation
|
||||
- **WHEN** the user triggers `select:glider`
|
||||
- **THEN** a glider preview appears at the current cursor position
|
||||
- **AND** the placement tool remains active
|
||||
|
||||
### Requirement: Preview follows cursor motion
|
||||
The system SHALL update the preview position to track the current mouse cursor cell while a tool is active.
|
||||
|
||||
#### Scenario: Mouse moves
|
||||
- **WHEN** the user moves the mouse while a tool is active
|
||||
- **THEN** the preview moves to the cell under the cursor
|
||||
|
||||
### Requirement: Tool can be rotated in 90-degree steps
|
||||
The system SHALL rotate the active preview by 90 degrees each time the rotate action is triggered.
|
||||
|
||||
#### Scenario: Rotate preview
|
||||
- **WHEN** the user triggers the rotate action while a tool is active
|
||||
- **THEN** the preview rotates 90 degrees clockwise
|
||||
- **AND** remains at the same anchor cell
|
||||
|
||||
### Requirement: Left click places the pattern when valid
|
||||
The system SHALL write the active pattern's alive cells into the grid at the preview position when the user left-clicks and the pattern fits entirely within the grid.
|
||||
|
||||
#### Scenario: Valid placement
|
||||
- **WHEN** the user left-clicks while a tool is active and the preview fits in the grid
|
||||
- **THEN** the pattern's cells become alive in the grid
|
||||
- **AND** the tool remains active for further placement
|
||||
|
||||
### Requirement: Invalid placement is rejected
|
||||
The system SHALL not modify the grid when a left click occurs while the preview extends outside the grid.
|
||||
|
||||
#### Scenario: Out of bounds
|
||||
- **WHEN** the user left-clicks while the preview extends outside the grid
|
||||
- **THEN** no cells are changed
|
||||
- **AND** the tool remains active
|
||||
|
||||
### Requirement: Escape cancels the active tool
|
||||
The system SHALL deactivate the placement tool and remove the preview when the cancel action is triggered.
|
||||
|
||||
#### Scenario: Cancel placement
|
||||
- **WHEN** the user triggers the cancel action while a tool is active
|
||||
- **THEN** the preview disappears
|
||||
- **AND** no further clicks place a pattern until a tool is selected again
|
||||
|
||||
### Requirement: Click without active tool does nothing
|
||||
The system SHALL ignore left clicks when no placement tool is active.
|
||||
|
||||
#### Scenario: No tool
|
||||
- **WHEN** the user left-clicks while no tool is active
|
||||
- **THEN** the grid is unchanged
|
||||
@@ -0,0 +1,47 @@
|
||||
# Tasks
|
||||
|
||||
## 1. Protocol generation and build setup
|
||||
|
||||
- [x] 1.1 Vendor `viewporter.xml` and `fractional-scale-v1.xml` into the repo and add wayland-scanner custom commands in `CMakeLists.txt`, verifying headers generate under `build/include`.
|
||||
- [x] 1.2 Add `tomlc99.c`/`tomlc99.h` (vendored as toml.c/toml.h) to the source tree and CMake sources, verifying the project still configures and builds.
|
||||
|
||||
## 2. Configuration module
|
||||
|
||||
- [x] 2.1 Implement `config.c/h` to load or auto-create `~/.config/wlgol/keys.conf`, parse TOML bindings, and merge with built-in defaults, verifying a sample file round-trips.
|
||||
- [x] 2.2 Implement binding string parsing (modifiers + key aliases) and lookup by modifier mask and keysym, verifying unit tests cover merge and modifier matching.
|
||||
|
||||
## 3. Keyboard module
|
||||
|
||||
- [x] 3.1 Extend `keyboard_key_handler_t` to pass a modifier mask and update `tkeyboard.c` to compute it from xkb state, verifying compilation of all callers.
|
||||
- [x] 3.2 Wire keyboard events to config-based binding lookup and action dispatch, verifying Escape and Ctrl+Q trigger their actions.
|
||||
|
||||
## 4. Pointer module
|
||||
|
||||
- [x] 4.1 Track logical cursor position in `tpointer.c/h` from motion events, verifying position updates on move.
|
||||
- [x] 4.2 Dispatch left-click events to the placement controller, verifying clicks are received.
|
||||
|
||||
## 5. Grid module
|
||||
|
||||
- [x] 5.1 Implement `grid.c/h` with storage, bounds checks, resize-with-preserve, clear, and toggle state, verifying unit tests for resize and bounds.
|
||||
- [x] 5.2 Expose grid dimensions derived from logical surface size at 8 px per cell, verifying dimensions update on resize.
|
||||
|
||||
## 6. Pattern module
|
||||
|
||||
- [x] 6.1 Implement `pattern.c/h` with Glider, Blinker, Block, and Toad offsets and 90-degree rotation transform, verifying rotated offsets and bounds.
|
||||
- [x] 6.2 Implement placement validity check (pattern fits within grid), verifying in-bounds and out-of-bounds cases.
|
||||
|
||||
## 7. Scaling module
|
||||
|
||||
- [x] 7.1 Implement scaling (inlined in wlgol.c) to bind fractional scale and viewporter, compute physical buffer size, and convert coordinates, verifying scale changes update buffer size.
|
||||
- [x] 7.2 Implement integer-scale fallback via `wl_output`, verifying fallback path builds without fractional protocol.
|
||||
|
||||
## 8. Rendering and application integration
|
||||
|
||||
- [x] 8.1 Refactor `wlgol.c` to use logical surface size, create scaled shm buffers, and render grid plus alive cells, verifying build succeeds.
|
||||
- [x] 8.2 Add placement controller state machine (IDLE/ARMED), preview rendering, rotation, click placement, and cancellation, verifying behavior by manual run.
|
||||
- [x] 8.3 Integrate config loading, default file creation, and action dispatch into startup, verifying default config is generated.
|
||||
|
||||
## 9. Verification
|
||||
|
||||
- [x] 9.1 Build with CMake/Ninja and run `ctest --test-dir build`, verifying all tests pass.
|
||||
- [x] 9.2 Run `./build/wlgol` under a Wayland compositor and manually verify placement, rotation, HiDPI scaling, and Esc cancellation.
|
||||
Reference in New Issue
Block a user