Skip to content

Commit ffb2c51

Browse files
stevekwon211claude
andcommitted
feat(hud): v0.30.0 — Hotbar + Reticle + CompassStrip
Game / HUD expansion — the slintcn differentiator. Three new primitives composed from existing SlotTile + Rectangle math: - Hotbar — horizontal strip of SlotTiles with an active highlight. slots: [{ tone, label }]; active <=> int; selected(int) callback. - Reticle — crosshair overlay: 4 tick bars + optional center dot, all size/tint customizable. - CompassStrip — horizontal heading indicator; bind heading-deg (0..360), marks scroll under a center pointer. Slint mod(float, float) + abs(float) validated working in 1.16. 56 components total. The existing HUD trio (Keycap / HudPill / SlotTile) plus these three = a working game-shell HUD layer no shadcn equivalent covers. Cache-bust 0.30.0; WASM rebuilt. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d262c97 commit ffb2c51

16 files changed

Lines changed: 305 additions & 22 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
[Live docs](https://stevekwon211.github.io/slintcn/docs/) · [Live demo](https://stevekwon211.github.io/slintcn) · [npm](https://www.npmjs.com/package/slintcn)
1010

11-
53 UI components, 8 installable blocks, a theme system, and a static registry you can host yourself.
11+
56 UI components, 8 installable blocks, a theme system, and a static registry you can host yourself.
1212

1313
<p align="center">
1414
<img src="docs/img/snapshots/section-6-dashboard.png" alt="slintcn dashboard components" width="31%">
@@ -89,7 +89,7 @@ node /path/to/slintcn/bin/slintcn.mjs add button card input dialog
8989
| **v0.27** | **Calendar + Date Picker** — month grid (`Calendar`) + Popover-wrapped trigger (`DatePicker`); consumer owns date math → 48 components ||
9090
| **v0.28** | **App-shell**`Sidebar` (collapsible nav with icons + active highlight), `Empty` (zero-state surface), `AspectRatio` (layout helper) → 51 components ||
9191
| **v0.29** | **Catalog round-out**`Collapsible` (single show/hide section), `ButtonGroup` (joined Buttons) → 53 components ||
92-
| **v1.0** | Game HUD registry expansion — hotbar, reticle, full keycap hints | later |
92+
| **v0.30** | **Game HUD expansion**`Hotbar` (SlotTile strip), `Reticle` (crosshair overlay), `CompassStrip` (scrolling heading) → 56 components | |
9393

9494
SaaS-first is a **wedge**, not a ceiling. Once tokens + motion + hover semantics
9595
exist, a second registry (`registry/game/`) is just more `.slint` files.

docs/ROADMAP.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
# slintcn roadmap
22

3-
## v0.29 — catalog round-out (current)
3+
## v0.30 — Game HUD expansion (current)
4+
5+
The slintcn differentiator. Three new HUD primitives composed from existing
6+
SlotTile / Rectangle math:
7+
8+
- [x] **Hotbar** — horizontal strip of `SlotTile`s with an active highlight.
9+
`slots: [{ tone, label }]`, `active <=> int`, `selected(int)`.
10+
- [x] **Reticle** — crosshair overlay: 4 tick bars + optional center dot.
11+
Customizable `size`, `tint`, `stroke-width`, `tick-length`, `gap`.
12+
- [x] **CompassStrip** — horizontal heading indicator. Bind `heading-deg`
13+
(0–360); marks scroll under a center pointer. Slint `mod(float, float)`
14+
+ `abs(float)` validated working in 1.16.
15+
16+
56 components total. Existing HUD trio (Keycap, HudPill, SlotTile) plus the
17+
new three = a working "game / tools HUD" layer no shadcn equivalent covers.
18+
19+
## v0.29 — catalog round-out
420

521
- [x] **Collapsible** — single show / hide section with chevron-flip trigger.
622
The body collapses via `max-height` (Slint forbids `@children` inside
@@ -147,11 +163,11 @@ heavy, separate R&D track; the Game/HUD layer is the long-term differentiator.
147163
- **Directory page** (shadcn `/docs/directory` analog) for community registries.
148164
- Optional **`/create`-style preset page** (pick components → copy command).
149165

150-
### v1.0 — Game / HUD expansion (the differentiator)
151-
- Hotbar, reticle, health/mana/stamina bars, minimap frame, full keycap-hint
152-
system, damage numbers.
166+
### v1.0+ — Game / HUD round-out (the differentiator)
167+
- Hotbar / Reticle / CompassStrip shipped in v0.30. Future: health / mana /
168+
stamina bars (Progress variants), minimap frame, damage numbers, expanded
169+
keycap-hint system.
153170
- Why: slintcn's unique angle ("Slint for games + tools"); no shadcn equivalent.
154-
Widen this moat once catalog parity is "good enough."
155171

156172
> Prioritization note: Slint's audience is smaller than React's, so depth on
157173
> app-shell + the components Slint apps actually need + the HUD differentiator

examples/showcase/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/showcase/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "slintcn-showcase"
3-
version = "0.29.0"
3+
version = "0.30.0"
44
edition = "2021"
55
publish = false
66

examples/showcase/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ fn main() {
2525
"calendar", "date-picker",
2626
"sidebar", "empty", "aspect-ratio",
2727
"collapsible", "button-group",
28+
"hotbar", "reticle", "compass-strip",
2829
"sign-in", "login", "pricing", "dashboard", "settings",
2930
"team", "profile", "stats",
3031
])

examples/showcase/ui/app_window.slint

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ import { Empty } from "slintcn/components/empty.s
6767
import { AspectRatio } from "slintcn/components/aspect-ratio.slint";
6868
import { Collapsible } from "slintcn/components/collapsible.slint";
6969
import { ButtonGroup } from "slintcn/components/button-group.slint";
70+
import { Hotbar, HotbarSlot } from "slintcn/components/hotbar.slint";
71+
import { Reticle } from "slintcn/components/reticle.slint";
72+
import { CompassStrip } from "slintcn/components/compass-strip.slint";
7073
import { SignIn } from "slintcn/blocks/sign-in.slint";
7174
import { Login } from "slintcn/blocks/login.slint";
7275
import { Pricing } from "slintcn/blocks/pricing.slint";
@@ -349,6 +352,22 @@ component PreviewHost inherits Rectangle {
349352
Button { variant: ButtonVariant.outline; text: "Week"; }
350353
Button { variant: ButtonVariant.outline; text: "Month"; }
351354
}
355+
if root.name == "hotbar": Hotbar {
356+
slots: [
357+
{ tone: SlotTileTone.stone, label: "1" },
358+
{ tone: SlotTileTone.accent, label: "2" },
359+
{ tone: SlotTileTone.empty, label: "3" },
360+
{ tone: SlotTileTone.stone, label: "4" },
361+
{ tone: SlotTileTone.stone, label: "5" },
362+
];
363+
active: 1;
364+
}
365+
if root.name == "reticle": Reticle {
366+
size: 40px;
367+
}
368+
if root.name == "compass-strip": CompassStrip {
369+
heading-deg: 45.0;
370+
}
352371
if root.name == "aspect-ratio": AspectRatio {
353372
ratio: 16 / 9;
354373
width: 480px;
@@ -462,7 +481,7 @@ export component AppWindow inherits Window {
462481
font-weight: Tokens.typography-weight-semibold;
463482
}
464483
Badge {
465-
text: "v0.29";
484+
text: "v0.30";
466485
variant: BadgeVariant.secondary;
467486
size: BadgeSize.sm;
468487
}
@@ -670,7 +689,7 @@ export component AppWindow inherits Window {
670689
spacing: 8px;
671690
alignment: start;
672691
Label { text: "Sign up for the beta"; variant: LabelVariant.default; }
673-
Badge { text: "v0.29"; variant: BadgeVariant.secondary; size: BadgeSize.sm; }
692+
Badge { text: "v0.30"; variant: BadgeVariant.secondary; size: BadgeSize.sm; }
674693
}
675694

676695
VerticalLayout {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "slintcn",
3-
"version": "0.29.0",
3+
"version": "0.30.0",
44
"description": "Beautiful copy-paste Slint components — shadcn for native UI",
55
"keywords": [
66
"slint",

registry/default/a11y.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,8 @@
3434
"empty": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false },
3535
"aspect-ratio": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false },
3636
"collapsible": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false },
37-
"button-group": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false }
37+
"button-group": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false },
38+
"hotbar": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false },
39+
"reticle": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false },
40+
"compass-strip": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false }
3841
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Tokens } from "../theme/tokens.slint";
2+
3+
// Compass strip — a horizontal bar showing N/E/S/W (and intermediates) with
4+
// the current heading centered. Bind `heading-deg` 0..360 (0 = north, 90 =
5+
// east, …); the strip slides so the active cardinal aligns to the center
6+
// indicator. Common in driving / flight / open-world HUDs.
7+
export component CompassStrip inherits Rectangle {
8+
// Current heading in degrees, 0..360 (0 = N, 90 = E, 180 = S, 270 = W).
9+
in property <float> heading-deg: 0;
10+
// Pixel width of the visible strip; the marks scroll inside it.
11+
in property <length> strip-width: 240px;
12+
// Pixel height of the strip.
13+
in property <length> strip-height: 32px;
14+
// Color of the marks + cardinal labels.
15+
in property <color> tint: Tokens.color-foreground;
16+
// Surface color behind the marks.
17+
in property <color> surface: Tokens.color-surface-1;
18+
19+
background: transparent;
20+
width: root.strip-width;
21+
height: root.strip-height;
22+
23+
// Surface
24+
Rectangle {
25+
width: 100%;
26+
height: 100%;
27+
background: root.surface;
28+
border-radius: Tokens.radius-md;
29+
border-width: 1px;
30+
border-color: Tokens.color-border-hairline;
31+
}
32+
33+
// Cardinal labels — N at 0°, E at 90°, S at 180°, W at 270°. Each label
34+
// is positioned so its center sits at (cardinal-deg - heading-deg + 180)
35+
// * px-per-deg from the strip's left edge. px-per-deg = strip-width / 360.
36+
for cardinal[ci] in [
37+
{ label: "N", deg: 0 },
38+
{ label: "NE", deg: 45 },
39+
{ label: "E", deg: 90 },
40+
{ label: "SE", deg: 135 },
41+
{ label: "S", deg: 180 },
42+
{ label: "SW", deg: 225 },
43+
{ label: "W", deg: 270 },
44+
{ label: "NW", deg: 315 },
45+
]: Rectangle {
46+
property <float> rel-deg: mod(cardinal.deg - root.heading-deg + 540.0, 360.0) - 180.0;
47+
property <length> rel-x: rel-deg * (root.strip-width / 360.0);
48+
x: (parent.width / 2) + self.rel-x - self.width / 2;
49+
y: (parent.height - self.height) / 2;
50+
width: 26px;
51+
height: 22px;
52+
visible: abs(self.rel-deg) <= 90;
53+
Text {
54+
text: cardinal.label;
55+
color: root.tint;
56+
font-size: Tokens.typography-body-sm-size;
57+
font-weight: Tokens.typography-weight-semibold;
58+
horizontal-alignment: center;
59+
vertical-alignment: center;
60+
}
61+
}
62+
63+
// Center indicator — small downward arrow at the top center marking the
64+
// exact heading.
65+
Rectangle {
66+
x: (parent.width - 2px) / 2;
67+
y: 2px;
68+
width: 2px;
69+
height: 6px;
70+
background: root.tint;
71+
}
72+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Tokens } from "../theme/tokens.slint";
2+
import { SlotTile, SlotTileTone, SlotTileState } from "slot-tile.slint";
3+
4+
// Hotbar — a horizontal strip of SlotTiles for the player's active inventory
5+
// / abilities. Bind `active` to the selected index; clicking a slot updates
6+
// it and fires `selected(int)`. Empty slots use `tone: stone` + `state: idle`
7+
// by convention.
8+
export struct HotbarSlot {
9+
tone: SlotTileTone,
10+
label: string,
11+
}
12+
13+
export component Hotbar inherits Rectangle {
14+
// Slot data left-to-right. Each `tone` drives the tile's surface, `label`
15+
// is shown beneath each tile.
16+
in property <[HotbarSlot]> slots;
17+
// Two-way; index of the active slot (highlight + state: selected).
18+
in-out property <int> active: 0;
19+
// Pixel side length of each square slot.
20+
in property <length> tile-size: 56px;
21+
// Fired with the slot index when a tile is clicked.
22+
callback selected(int);
23+
24+
background: transparent;
25+
preferred-width: row.preferred-width;
26+
preferred-height: row.preferred-height;
27+
28+
row := HorizontalLayout {
29+
spacing: 6px;
30+
for slot[i] in root.slots: VerticalLayout {
31+
spacing: 4px;
32+
alignment: start;
33+
SlotTile {
34+
tone: slot.tone;
35+
state: i == root.active ? SlotTileState.active : SlotTileState.idle;
36+
interactive: true;
37+
size: root.tile-size;
38+
clicked => {
39+
root.active = i;
40+
root.selected(i);
41+
}
42+
}
43+
if slot.label != "": Text {
44+
text: slot.label;
45+
color: Tokens.color-muted-foreground;
46+
font-size: Tokens.typography-body-sm-size;
47+
horizontal-alignment: center;
48+
}
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)