Skip to content

Commit 18a2486

Browse files
committed
Merge PR #29 floating parameters
2 parents fe6026b + ea34f5b commit 18a2486

6 files changed

Lines changed: 262 additions & 9 deletions

File tree

ops.ts

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,24 @@ export function pack(
168168
o += 4;
169169
view.setFloat32(o, f.y ?? 0, true);
170170
o += 4;
171+
view.setFloat32(o, f.expand?.width ?? 0, true);
172+
o += 4;
173+
view.setFloat32(o, f.expand?.height ?? 0, true);
174+
o += 4;
171175
view.setUint32(o, f.parent ?? 0, true);
172176
o += 4;
173177
view.setUint32(
174178
o,
175-
(f.attachTo ?? 0) | ((f.attachPoints ?? 0) << 8) |
176-
((f.zIndex ?? 0) << 16),
179+
encodeAttachTo(f.attachTo) |
180+
(encodeAttachPoint(f.attachPoints?.element) << 8) |
181+
(encodeAttachPoint(f.attachPoints?.parent) << 16) |
182+
(encodePointerCaptureMode(f.pointerCaptureMode) << 24),
183+
true,
184+
);
185+
o += 4;
186+
view.setUint32(
187+
o,
188+
encodeClipTo(f.clipTo) | (((f.zIndex ?? 0) & 0xffff) << 8),
177189
true,
178190
);
179191
o += 4;
@@ -284,14 +296,81 @@ export interface OpenElement {
284296
floating?: {
285297
x?: number;
286298
y?: number;
299+
expand?: { width?: number; height?: number };
287300
parent?: number;
288-
attachTo?: number;
289-
attachPoints?: number;
301+
attachTo?: AttachTo;
302+
attachPoints?: { element?: AttachPoint; parent?: AttachPoint };
303+
pointerCaptureMode?: PointerCaptureMode;
304+
clipTo?: ClipTo;
290305
zIndex?: number;
291306
};
292307
transition?: Transition;
293308
}
294309

310+
export type AttachPoint =
311+
| "left-top"
312+
| "left-center"
313+
| "left-bottom"
314+
| "center-top"
315+
| "center-center"
316+
| "center-bottom"
317+
| "right-top"
318+
| "right-center"
319+
| "right-bottom";
320+
321+
export type AttachTo = "none" | "parent" | "element" | "root";
322+
323+
export type PointerCaptureMode = "capture" | "passthrough";
324+
325+
export type ClipTo = "none" | "attached-parent";
326+
327+
const ATTACH_POINT: Record<AttachPoint, number> = {
328+
"left-top": 0,
329+
"left-center": 1,
330+
"left-bottom": 2,
331+
"center-top": 3,
332+
"center-center": 4,
333+
"center-bottom": 5,
334+
"right-top": 6,
335+
"right-center": 7,
336+
"right-bottom": 8,
337+
};
338+
339+
const ATTACH_TO: Record<AttachTo, number> = {
340+
none: 0,
341+
parent: 1,
342+
element: 2,
343+
root: 3,
344+
};
345+
346+
const POINTER_CAPTURE_MODE: Record<PointerCaptureMode, number> = {
347+
capture: 0,
348+
passthrough: 1,
349+
};
350+
351+
const CLIP_TO: Record<ClipTo, number> = {
352+
none: 0,
353+
"attached-parent": 1,
354+
};
355+
356+
function encodeAttachPoint(value: AttachPoint | undefined): number {
357+
return value === undefined ? 0 : ATTACH_POINT[value];
358+
}
359+
360+
function encodeAttachTo(value: AttachTo | undefined): number {
361+
return value === undefined ? 0 : ATTACH_TO[value];
362+
}
363+
364+
function encodePointerCaptureMode(
365+
value: PointerCaptureMode | undefined,
366+
): number {
367+
return value === undefined ? 0 : POINTER_CAPTURE_MODE[value];
368+
}
369+
370+
function encodeClipTo(value: ClipTo | undefined): number {
371+
return value === undefined ? 0 : CLIP_TO[value];
372+
}
373+
295374
export interface Text {
296375
directive: typeof OP_TEXT;
297376
content: string;

specs/renderer-spec.md

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,10 +612,58 @@ The `open()` constructor currently accepts the following property groups in its
612612
- **`cornerRadius`** — per-corner radius values, producing rounded box-drawing
613613
characters
614614
- **`clip`** — clip region configuration for scroll containers
615-
- **`floating`** — floating-element configuration (offset, parent reference,
616-
attach points, z-index)
615+
- **`floating`** — floating-element configuration (offset, expansion, parent
616+
reference, attach target, structured attach points, pointer capture mode, clip
617+
target, z-index)
617618
- **`scroll`** — scroll container configuration
618619

620+
The current floating surface is:
621+
622+
```ts
623+
floating?: {
624+
x?: number;
625+
y?: number;
626+
expand?: { width?: number; height?: number };
627+
parent?: number;
628+
attachTo?: "none" | "parent" | "element" | "root";
629+
attachPoints?: {
630+
element?:
631+
| "left-top"
632+
| "left-center"
633+
| "left-bottom"
634+
| "center-top"
635+
| "center-center"
636+
| "center-bottom"
637+
| "right-top"
638+
| "right-center"
639+
| "right-bottom";
640+
parent?:
641+
| "left-top"
642+
| "left-center"
643+
| "left-bottom"
644+
| "center-top"
645+
| "center-center"
646+
| "center-bottom"
647+
| "right-top"
648+
| "right-center"
649+
| "right-bottom";
650+
};
651+
pointerCaptureMode?: "capture" | "passthrough";
652+
clipTo?: "none" | "attached-parent";
653+
zIndex?: number;
654+
}
655+
```
656+
657+
The `floating` object configures Clay floating layout behavior. `x` and `y`
658+
provide the floating offset. `expand` expands the floating bounds. `parent`
659+
identifies the target element when `attachTo` is `"element"`. `attachTo` selects
660+
whether the element is attached to no target, its parent, an element, or the
661+
layout root. `attachPoints.element` describes the anchor on the floating
662+
element, and `attachPoints.parent` describes the anchor on the attached target.
663+
`pointerCaptureMode` controls whether the floating element captures pointer
664+
input or lets it pass through, `clipTo` controls inherited clipping, and
665+
`zIndex` controls floating order.
666+
619667
The `text()` constructor currently accepts: `color`, `fontSize`,
620668
`letterSpacing`, `lineHeight`, and attribute flags (`bold`, `italic`,
621669
`underline`, `strikethrough`).

src/clayterm.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,13 +560,19 @@ void reduce(struct Clayterm *ct, uint32_t *buf, int len, int mode, int row,
560560
if (mask & PROP_FLOATING) {
561561
decl.floating.offset.x = rdf(buf, len, &i);
562562
decl.floating.offset.y = rdf(buf, len, &i);
563+
decl.floating.expand.width = rdf(buf, len, &i);
564+
decl.floating.expand.height = rdf(buf, len, &i);
563565
decl.floating.parentId = rd(buf, len, &i);
564566

565567
uint32_t fc = rd(buf, len, &i);
566568
decl.floating.attachTo = fc & 0xff;
567569
decl.floating.attachPoints.element = (fc >> 8) & 0xff;
568570
decl.floating.attachPoints.parent = (fc >> 16) & 0xff;
569-
decl.floating.zIndex = (int16_t)((fc >> 24) & 0xff);
571+
decl.floating.pointerCaptureMode = (fc >> 24) & 0xff;
572+
573+
uint32_t fd = rd(buf, len, &i);
574+
decl.floating.clipTo = fd & 0xff;
575+
decl.floating.zIndex = (int16_t)(fd >> 8);
570576
}
571577

572578
if (mask & PROP_TRANSITION) {

test/term.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,50 @@ describe("term", () => {
191191
});
192192
});
193193

194+
it("renders a floating frame with structured attach points", () => {
195+
let out = print(
196+
decode(
197+
term.render([
198+
open("root", {
199+
layout: { width: fixed(40), height: fixed(10), direction: "ttb" },
200+
}),
201+
open("frame", {
202+
layout: {
203+
width: fixed(12),
204+
height: fixed(5),
205+
direction: "ttb",
206+
padding: { left: 1, top: 1 },
207+
},
208+
border: {
209+
color: rgba(255, 255, 255),
210+
left: 1,
211+
right: 1,
212+
top: 1,
213+
bottom: 1,
214+
},
215+
floating: {
216+
x: 3,
217+
y: 1,
218+
attachTo: "root",
219+
attachPoints: {
220+
element: "center-center",
221+
parent: "center-center",
222+
},
223+
},
224+
}),
225+
text("box"),
226+
close(),
227+
close(),
228+
]).output,
229+
),
230+
40,
231+
10,
232+
);
233+
234+
expect(out).toContain("│box │");
235+
expect(out).toContain("┌──────────┐");
236+
});
237+
194238
describe("row offset", () => {
195239
it("renders two frames at the offset position", async () => {
196240
let term = await createTerm({ width: 20, height: 5 });

test/validate.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,44 @@ describe("validate", () => {
7878
it("rejects fractional color", () => {
7979
expect(validate([text("hi", { color: 1.5 })])).toBe(false);
8080
});
81+
82+
it("accepts structured floating attach points", () => {
83+
expect(validate([
84+
open("x", {
85+
floating: {
86+
attachPoints: { element: "center-center", parent: "center-center" },
87+
},
88+
}),
89+
close(),
90+
])).toBe(true);
91+
});
92+
93+
it("accepts floating expand and clipping fields", () => {
94+
expect(validate([
95+
open("x", {
96+
floating: {
97+
expand: { width: 2, height: 3 },
98+
pointerCaptureMode: "passthrough",
99+
clipTo: "attached-parent",
100+
zIndex: 1024,
101+
},
102+
}),
103+
close(),
104+
])).toBe(true);
105+
});
106+
107+
it("rejects numeric floating enum values", () => {
108+
expect(validate([
109+
// deno-lint-ignore no-explicit-any
110+
open("x", { floating: { attachTo: 3 as any } }),
111+
close(),
112+
])).toBe(false);
113+
expect(validate([
114+
// deno-lint-ignore no-explicit-any
115+
open("x", { floating: { attachPoints: 4 as any } }),
116+
close(),
117+
])).toBe(false);
118+
});
81119
});
82120

83121
describe("validated", () => {

validate.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,50 @@ const Clip = Type.Object({
8080
vertical: Type.Optional(Type.Boolean()),
8181
});
8282

83+
const AttachPoint = Type.Union([
84+
Type.Literal("left-top"),
85+
Type.Literal("left-center"),
86+
Type.Literal("left-bottom"),
87+
Type.Literal("center-top"),
88+
Type.Literal("center-center"),
89+
Type.Literal("center-bottom"),
90+
Type.Literal("right-top"),
91+
Type.Literal("right-center"),
92+
Type.Literal("right-bottom"),
93+
]);
94+
95+
const AttachTo = Type.Union([
96+
Type.Literal("none"),
97+
Type.Literal("parent"),
98+
Type.Literal("element"),
99+
Type.Literal("root"),
100+
]);
101+
102+
const PointerCaptureMode = Type.Union([
103+
Type.Literal("capture"),
104+
Type.Literal("passthrough"),
105+
]);
106+
107+
const ClipTo = Type.Union([
108+
Type.Literal("none"),
109+
Type.Literal("attached-parent"),
110+
]);
111+
83112
const Floating = Type.Object({
84113
x: Type.Optional(Type.Number()),
85114
y: Type.Optional(Type.Number()),
115+
expand: Type.Optional(Type.Object({
116+
width: Type.Optional(Type.Number()),
117+
height: Type.Optional(Type.Number()),
118+
})),
86119
parent: Type.Optional(Type.Integer({ minimum: 0 })),
87-
attachTo: Type.Optional(u8),
88-
attachPoints: Type.Optional(u8),
120+
attachTo: Type.Optional(AttachTo),
121+
attachPoints: Type.Optional(Type.Object({
122+
element: Type.Optional(AttachPoint),
123+
parent: Type.Optional(AttachPoint),
124+
})),
125+
pointerCaptureMode: Type.Optional(PointerCaptureMode),
126+
clipTo: Type.Optional(ClipTo),
89127
zIndex: Type.Optional(u16),
90128
});
91129

0 commit comments

Comments
 (0)