diff --git a/src/assets/textures/rect.js b/src/assets/textures/rect.js index b5efc0a1..406f3e09 100644 --- a/src/assets/textures/rect.js +++ b/src/assets/textures/rect.js @@ -1,4 +1,5 @@ import { Graphics } from 'pixi.js'; +import { EachRadius } from '../../display/data-schema/primitive-schema'; import { getColor } from '../../utils/get'; import { cacheKey, generateTexture } from './utils'; @@ -15,10 +16,16 @@ export const createRectTexture = (renderer, theme, rectOpts) => { texture.id = cacheKey(renderer, rectOpts); texture.metadata = { slice: { - topHeight: borderWidth + 4, - leftWidth: borderWidth + 4, - rightWidth: borderWidth + 4, - bottomHeight: borderWidth + 4, + topHeight: + getSliceDimension(radius, 'topLeft', 'topRight') + (borderWidth ?? 0), + leftWidth: + getSliceDimension(radius, 'topLeft', 'bottomLeft') + (borderWidth ?? 0), + rightWidth: + getSliceDimension(radius, 'topRight', 'bottomRight') + + (borderWidth ?? 0), + bottomHeight: + getSliceDimension(radius, 'bottomLeft', 'bottomRight') + + (borderWidth ?? 0), }, borderWidth: borderWidth, config: { ...rectOpts }, @@ -31,8 +38,20 @@ const createRect = (theme, { fill, borderWidth, borderColor, radius }) => { const size = 20 + borderWidth; const xywh = [0, 0, size, size]; - if (radius > 0) { + const parsedRadius = EachRadius.safeParse(radius); + if (typeof radius === 'number' && radius > 0) { graphics.roundRect(...xywh, radius); + } else if (parsedRadius.success) { + const r = parsedRadius.data; + graphics.roundShape( + [ + { x: 0, y: 0, radius: r.topLeft }, + { x: size, y: 0, radius: r.topRight }, + { x: size, y: size, radius: r.bottomRight }, + { x: 0, y: size, radius: r.bottomLeft }, + ], + 0, + ); } else { graphics.rect(...xywh); } @@ -46,3 +65,9 @@ const createRect = (theme, { fill, borderWidth, borderColor, radius }) => { } return graphics; }; + +const getSliceDimension = (radius, key1, key2) => { + return typeof radius === 'number' + ? radius + : Math.max(radius?.[key1] ?? 0, radius?.[key2] ?? 0); +}; diff --git a/src/assets/textures/utils.js b/src/assets/textures/utils.js index 05092576..86b246c6 100644 --- a/src/assets/textures/utils.js +++ b/src/assets/textures/utils.js @@ -17,6 +17,6 @@ export const generateTexture = (target, renderer, opts = {}) => { export const cacheKey = (renderer, config) => { return [ renderer.uid, - ...TextureStyle.keyof().options.map((key) => config[key]), + ...TextureStyle.keyof().options.map((key) => JSON.stringify(config[key])), ].join('-'); }; diff --git a/src/display/data-schema/primitive-schema.js b/src/display/data-schema/primitive-schema.js index 0f637f0d..f6449003 100644 --- a/src/display/data-schema/primitive-schema.js +++ b/src/display/data-schema/primitive-schema.js @@ -198,13 +198,20 @@ export const Margin = z.preprocess( .default({}), ); +export const EachRadius = z.object({ + topLeft: z.number().nonnegative().default(0), + topRight: z.number().nonnegative().default(0), + bottomRight: z.number().nonnegative().default(0), + bottomLeft: z.number().nonnegative().default(0), +}); + export const TextureStyle = z .object({ type: z.enum(['rect']), fill: z.string().default('transparent'), borderWidth: z.number().default(0), borderColor: z.string().default('black'), - radius: z.number().default(0), + radius: z.union([z.number().nonnegative(), EachRadius]).default(0), }) .partial(); diff --git a/src/display/mixins/Sourceable.js b/src/display/mixins/Sourceable.js index ea38e8be..0c54dca9 100644 --- a/src/display/mixins/Sourceable.js +++ b/src/display/mixins/Sourceable.js @@ -9,7 +9,7 @@ export const Sourceable = (superClass) => { const { source } = relevantChanges; const { viewport, theme } = this.context; const texture = getTexture(viewport.app.renderer, theme, source); - this.texture = texture; + Object.assign(this, { texture, ...(texture?.metadata?.slice ?? {}) }); } }; MixedClass.registerHandler(