// view modifiers (inspired by SwiftUI and Flutter) are components with a single child // unlike other components, view modifiers may be chained after components in an extensible way import { BBox, Component, Modifier, Position, SizeInterval } from './componentTypes'; import { nanoid } from 'nanoid'; import { union, inflate } from './bboxUtil'; import { measureText } from './measureText'; export const position = (position: Position) => (component: Component) => new Modifier( component, (interval: SizeInterval, child: Component) => { child.layout(interval); return { size: { width: interval.width.ub, height: interval.height.ub, }, position, }; }, (bbox: BBox, child: Component) => { // return // {children[0].paint()} // ; return child.paint(); }, ); export const position2 = (position: Position) => (component: Component) => new Modifier( component, (interval: SizeInterval, child: Component) => { child.layout(interval); return { size: { width: child.size!.width, height: child.size!.height, }, ownPosition: position, position: { x: 0, y: 0 }, }; }, (bbox: BBox, child: Component) => { return {child.paint()}; }, ); type Padding = number | Partial<{ top: number; right: number; bottom: number; left: number }>; type ElaboratedPadding = { top: number; right: number; bottom: number; left: number }; export const padding = (padding: Padding) => (component: Component) => { const elaboratedPadding: ElaboratedPadding = typeof padding === 'number' ? { top: padding, right: padding, bottom: padding, left: padding } : { top: padding.top ?? 0, right: padding.right ?? 0, bottom: padding.bottom ?? 0, left: padding.left ?? 0, }; return new Modifier( component, (interval: SizeInterval, child: Component) => { // subtract padding from interval const { width, height } = interval; const { top, right, bottom, left } = elaboratedPadding; const newInterval = { width: { ub: width.ub - left - right, lb: width.lb - left - right }, height: { ub: height.ub - top - bottom, lb: height.lb - left - right }, }; child.layout(newInterval); console.log('child position', child.position); return { size: { width: child.size!.width + left + right, height: child.size!.height + top + bottom, }, position: { x: elaboratedPadding.left + (child.position?.x ?? 0), y: elaboratedPadding.top + (child.position?.y ?? 0), }, }; }, (bbox: BBox, child: Component) => { return {child.paint()}; }, ); }; export const background = (background: Component) => (component: Component) => { return new Component( [component, background], (interval: SizeInterval, children: Component[]) => { const [child, background] = children; child.layout(interval); background.layout({ width: { ub: interval.width.ub, lb: child.size!.width }, height: { ub: interval.height.ub, lb: child.size!.height }, }); return { size: { width: background.size!.width, height: background.size!.height, }, positions: [ { x: 0, y: 0 }, { x: 0, y: 0 }, ], }; }, (bbox: BBox, children: Component[]) => { const [child, background] = children; return ( {background.paint()} {child.paint()} ); }, ); }; // type BracketOptions = {}; // export const bracket = (options: BracketOptions) => (component: Component) => { // return new Component( // [component, background], // (interval: SizeInterval, children: Component[]) => { // const [child, background] = children; // child.layout(interval); // background.layout({ // width: { ub: interval.width.ub, lb: child.size!.width }, // height: { ub: interval.height.ub, lb: child.size!.height }, // }); // return { // size: { // width: background.size!.width, // height: background.size!.height, // }, // positions: [ // { x: 0, y: 0 }, // { x: 0, y: 0 }, // ], // }; // }, // (bbox: BBox, children: Component[]) => { // const [child, background] = children; // return ( // // {background.paint()} // {child.paint()} // // ); // }, // ); // }; type BoundaryLabelOptions = Omit, 'href'> & React.SVGProps; export const boundaryLabel = (label: string, options: BoundaryLabelOptions) => (component: Component) => { const id = nanoid(); const textSize = measureText( label, `${options.fontStyle ?? ''} ${options.fontWeight ?? ''} ${options.fontSize ?? ''} ${options.fontFamily ?? ''}`, ); const labelComponent = new Component( [], (interval: SizeInterval, children: Component[]) => { return { size: { width: interval.width.lb, height: interval.height.lb, }, positions: [], }; }, // TODO: this boundary information is a big hack! // this is not the real boundary of the shape, we're just abusing it to send the boundary from // the shape to the label (bbox: BBox, children: Component[], boundary?: string) => { return ( {label} ); }, ); return new Component( [component, labelComponent], (interval: SizeInterval, children: Component[]) => { const [child, label] = children; child.layout(interval); console.log('setting label boundary', child.boundary); label.boundary = child.boundary; label.layout({ width: { ub: interval.width.ub, lb: child.size!.width }, height: { ub: interval.height.ub, lb: child.size!.height }, }); const size = inflate({ x: 0, y: 0, ...child.size! }, textSize.fontHeight); console.log('child size', child.size); console.log('size', size); return { size, positions: [{}, {}], }; }, (bbox: BBox, children: Component[]) => { const [child, label] = children; console.log('painting label', label.boundary); return ( {child.paint()} {label.paint()} ); }, ); };