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:
2026-11-08 14:46:04 +01:00
parent 5e7abfb56b
commit 7874a5f7c6
31 changed files with 4081 additions and 96 deletions
+61
View File
@@ -0,0 +1,61 @@
# cell-grid Specification
## Purpose
Stores and renders the Game of Life cell grid with a fixed cell size and wall boundaries, independent of future simulation rules.
## 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
+44
View File
@@ -0,0 +1,44 @@
# display-scaling Specification
## Purpose
Keeps the application rendering crisp on displays with integer or fractional scaling by converting between logical and physical coordinates.
## 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
+59
View File
@@ -0,0 +1,59 @@
# keyboard-bindings Specification
## Purpose
Allows users to configure keyboard shortcuts that map key combinations to application actions, with sensible built-in defaults when no configuration file exists.
## 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
+60
View File
@@ -0,0 +1,60 @@
# pattern-placement Specification
## 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.
## 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