// 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()}
);
},
);
};