Skip to content

Commit 30a8a1f

Browse files
authored
Merge pull request #443 from sugarlabs/gsoc-dmp-2025/week-4/saumya
GSoC 2025 Week 4-5: feat(Masonry): Complete Brick Tower Rendering System Implementation
2 parents a805426 + 648fbb3 commit 30a8a1f

File tree

23 files changed

+1762
-1162
lines changed

23 files changed

+1762
-1162
lines changed

modules/masonry/src/brick/@types/brick.d.ts renamed to modules/masonry/src/@types/brick.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type TBrickRenderProps = {
3737
colorBg: TColor;
3838
colorFg: TColor;
3939
strokeColor: TColor;
40-
strokeWidth: number; //remove
40+
strokeWidth: number;
4141
scale: number;
4242
shadow: boolean;
4343
tooltip?: string;

modules/masonry/src/@types/tower.d.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** A simple Cartesian point */
2+
export type TPoint = { x: number; y: number };
3+
4+
/** All supported physical notch pairings */
5+
export type TNotchType = 'top-bottom' | 'right-left' | 'left-right' | 'nested';
6+
7+
/** A single connection‑point centroid */
8+
export type TConnectionPoint = { x: number; y: number };
9+
10+
/** One logical connection between two bricks */
11+
export type TBrickConnection = {
12+
from: string; // uuid of source brick
13+
to: string; // uuid of destination brick
14+
fromNotchId: string; // e.g. "right_0"
15+
toNotchId: string; // e.g. "left"
16+
type: TNotchType;
17+
};
18+
19+
/** Validation result for attempted connections */
20+
export type TConnectionValidation = {
21+
isValid: boolean;
22+
reason?: string;
23+
};

modules/masonry/src/brick/model/model.ts

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import type {
1111
TBrickRenderPropsExpression,
1212
IBrickCompound,
1313
TBrickRenderPropsCompound,
14-
} from '../@types/brick';
14+
} from '../../@types/brick';
1515
import type { TConnectionPoints as TCP } from '../../tree/model/model';
1616
import { generateBrickData } from '../utils/path';
1717
import type { TInputUnion } from '../utils/path';
18+
import { getLabelWidth } from '../utils/textMeasurement';
1819

1920
export abstract class BrickModel implements IBrick {
2021
protected _uuid: string;
@@ -134,6 +135,22 @@ export abstract class BrickModel implements IBrick {
134135

135136
/** Must assemble the full render props for this brick. */
136137
public abstract get renderProps(): TBrickRenderProps;
138+
139+
/** Must update geometry when label or other properties change. */
140+
public abstract updateGeometry(): void;
141+
142+
get label(): string {
143+
return this._label;
144+
}
145+
146+
set label(value: string) {
147+
this._label = value;
148+
this.updateGeometry();
149+
}
150+
151+
get labelType(): 'text' | 'glyph' | 'icon' | 'thumbnail' {
152+
return this._labelType;
153+
}
137154
}
138155

139156
/**
@@ -184,14 +201,12 @@ export class SimpleBrick extends BrickModel implements IBrickSimple {
184201
type: 'type1',
185202
strokeWidth: this._strokeWidth,
186203
scaleFactor: this._scale,
187-
bBoxLabel: { w: this._label.length * 8, h: 20 },
204+
bBoxLabel: { w: getLabelWidth(this._label), h: 20 },
188205
bBoxArgs: this._bboxArgs,
189206
hasNotchAbove: this._topNotch,
190207
hasNotchBelow: this._bottomNotch,
191208
};
192209
const data = generateBrickData(config);
193-
194-
// use the public setters
195210
this.connectionPoints = data.connectionPoints;
196211
this.boundingBox = data.boundingBox;
197212
}
@@ -260,7 +275,7 @@ export class ExpressionBrick extends BrickModel implements IBrickExpression {
260275
type: 'type2',
261276
strokeWidth: this._strokeWidth,
262277
scaleFactor: this._scale,
263-
bBoxLabel: { w: this._label.length * 8, h: 20 },
278+
bBoxLabel: { w: getLabelWidth(this._label), h: 20 },
264279
bBoxArgs: this._bboxArgs,
265280
};
266281
const data = generateBrickData(config);
@@ -341,7 +356,7 @@ export default class CompoundBrick extends BrickModel implements IBrickCompound
341356
type: 'type3',
342357
strokeWidth: this._strokeWidth,
343358
scaleFactor: this._scale,
344-
bBoxLabel: { w: this._label.length * 8, h: 20 },
359+
bBoxLabel: { w: getLabelWidth(this._label), h: 20 },
345360
bBoxArgs: this._bboxArgs,
346361
hasNotchAbove: this._topNotch,
347362
hasNotchBelow: this._bottomNotch,
@@ -372,6 +387,33 @@ export default class CompoundBrick extends BrickModel implements IBrickCompound
372387
this._bboxNest = extents;
373388
}
374389

390+
/**
391+
* Recursively update bounding box and connection points to fit nested children.
392+
* Call this after all children are attached, before rendering.
393+
*/
394+
public updateLayoutWithChildren(nestedChildren: BrickModel[]): void {
395+
// If there are nested children, calculate the total bounding box
396+
if (nestedChildren && nestedChildren.length > 0) {
397+
// Calculate the bounding box that fits all nested children
398+
let _minX = 0,
399+
_minY = 0,
400+
maxX = 0,
401+
maxY = 0;
402+
nestedChildren.forEach((child) => {
403+
const bbox = child.boundingBox;
404+
// For simplicity, assume children are stacked vertically for now
405+
maxY += bbox.h;
406+
maxX = Math.max(maxX, bbox.w);
407+
});
408+
// Expand this brick's bboxNest to fit the children
409+
this._bboxNest = [{ w: maxX, h: maxY }];
410+
} else {
411+
this._bboxNest = [];
412+
}
413+
// Update geometry with new bboxNest
414+
this.updateGeometry();
415+
}
416+
375417
public override get renderProps(): TBrickRenderPropsCompound {
376418
return {
377419
...this.getCommonRenderProps(),
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// brickFactory.ts
2+
import { SimpleBrick, ExpressionBrick } from '../model/model';
3+
import CompoundBrick from '../model/model';
4+
import type { TBrickType, TColor, TExtent } from '../../@types/brick';
5+
6+
let idCounter = 0;
7+
function generateUUID(prefix: string): string {
8+
return `${prefix}_${++idCounter}`;
9+
}
10+
11+
// Default colors
12+
const defaultColors = {
13+
simple: {
14+
colorBg: '#bbdefb' as TColor,
15+
colorFg: '#222' as TColor,
16+
strokeColor: '#1976d2' as TColor,
17+
},
18+
expression: {
19+
colorBg: '#b2fab4' as TColor,
20+
colorFg: '#222' as TColor,
21+
strokeColor: '#2e7d32' as TColor,
22+
},
23+
compound: {
24+
colorBg: '#b9f6ca' as TColor,
25+
colorFg: '#222' as TColor,
26+
strokeColor: '#43a047' as TColor,
27+
},
28+
};
29+
30+
const defaultLabelType = 'text' as const;
31+
const defaultScale = 1;
32+
const defaultBBoxArgs: TExtent[] = [{ w: 40, h: 20 }];
33+
34+
export function createSimpleBrick(
35+
overrides: Partial<ConstructorParameters<typeof SimpleBrick>[0]> = {},
36+
) {
37+
const idx = idCounter + 1;
38+
// By default, SimpleBrick has two argument slots (for arguments/inputs)
39+
return new SimpleBrick({
40+
uuid: generateUUID('simple'),
41+
name: overrides.name ?? `Simple${idx}`,
42+
label: overrides.label ?? `Simple${idx}`,
43+
labelType: overrides.labelType ?? defaultLabelType,
44+
colorBg: overrides.colorBg ?? defaultColors.simple.colorBg,
45+
colorFg: overrides.colorFg ?? defaultColors.simple.colorFg,
46+
strokeColor: overrides.strokeColor ?? defaultColors.simple.strokeColor,
47+
shadow: overrides.shadow ?? false,
48+
scale: overrides.scale ?? defaultScale,
49+
bboxArgs: overrides.bboxArgs ?? [
50+
{ w: 40, h: 20 },
51+
{ w: 40, h: 20 },
52+
],
53+
topNotch: overrides.topNotch ?? true,
54+
bottomNotch: overrides.bottomNotch ?? true,
55+
tooltip: overrides.tooltip,
56+
...overrides,
57+
});
58+
}
59+
60+
// ExpressionBrick is used as an argument value, not as an argument-receiving brick
61+
export function createExpressionBrick(
62+
overrides: Partial<ConstructorParameters<typeof ExpressionBrick>[0]> = {},
63+
) {
64+
const idx = idCounter + 1;
65+
return new ExpressionBrick({
66+
uuid: generateUUID('expr'),
67+
name: overrides.name ?? `Expr${idx}`,
68+
label: overrides.label ?? `Expr${idx}`,
69+
labelType: overrides.labelType ?? defaultLabelType,
70+
colorBg: overrides.colorBg ?? defaultColors.expression.colorBg,
71+
colorFg: overrides.colorFg ?? defaultColors.expression.colorFg,
72+
strokeColor: overrides.strokeColor ?? defaultColors.expression.strokeColor,
73+
shadow: overrides.shadow ?? false,
74+
scale: overrides.scale ?? defaultScale,
75+
bboxArgs: overrides.bboxArgs ?? [{ w: 40, h: 20 }],
76+
value: overrides.value,
77+
isValueSelectOpen: overrides.isValueSelectOpen ?? false,
78+
tooltip: overrides.tooltip,
79+
...overrides,
80+
});
81+
}
82+
83+
export function createCompoundBrick(
84+
overrides: Partial<ConstructorParameters<typeof CompoundBrick>[0]> = {},
85+
) {
86+
const idx = idCounter + 1;
87+
return new CompoundBrick({
88+
uuid: generateUUID('compound'),
89+
name: overrides.name ?? `Compound${idx}`,
90+
label: overrides.label ?? `Compound${idx}`,
91+
labelType: overrides.labelType ?? defaultLabelType,
92+
colorBg: overrides.colorBg ?? defaultColors.compound.colorBg,
93+
colorFg: overrides.colorFg ?? defaultColors.compound.colorFg,
94+
strokeColor: overrides.strokeColor ?? defaultColors.compound.strokeColor,
95+
shadow: overrides.shadow ?? false,
96+
scale: overrides.scale ?? defaultScale,
97+
bboxArgs: overrides.bboxArgs ?? defaultBBoxArgs,
98+
bboxNest: overrides.bboxNest ?? [],
99+
topNotch: overrides.topNotch ?? true,
100+
bottomNotch: overrides.bottomNotch ?? true,
101+
isFolded: overrides.isFolded ?? false,
102+
tooltip: overrides.tooltip,
103+
...overrides,
104+
});
105+
}
106+
107+
export function resetFactoryCounter() {
108+
idCounter = 0;
109+
}
110+
111+
export function getFactoryCounter() {
112+
return idCounter;
113+
}
114+
115+
export function createBrick(
116+
type: TBrickType,
117+
overrides: Partial<
118+
| ConstructorParameters<typeof SimpleBrick>[0]
119+
| ConstructorParameters<typeof ExpressionBrick>[0]
120+
| ConstructorParameters<typeof CompoundBrick>[0]
121+
> = {},
122+
) {
123+
switch (type) {
124+
case 'Simple':
125+
return createSimpleBrick(overrides);
126+
case 'Expression':
127+
return createExpressionBrick(overrides);
128+
case 'Compound':
129+
return createCompoundBrick(overrides);
130+
default:
131+
throw new Error(`Unsupported brick type: ${type}`);
132+
}
133+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export {
2+
createSimpleBrick,
3+
createExpressionBrick,
4+
createCompoundBrick,
5+
createBrick,
6+
resetFactoryCounter,
7+
getFactoryCounter,
8+
} from './brickFactory';
9+
export { generateBrickData } from './path';
10+
export type { TInputUnion } from './path';
11+
export {
12+
measureTextWidth,
13+
estimateTextWidth,
14+
getLabelWidth,
15+
measureLabel,
16+
} from './textMeasurement';

0 commit comments

Comments
 (0)