Use shape scale for geo shape min size (#4140)

This PR fixes a bug where a shape created with dynamic size would
preserve the origin

### Change type

- [x] `bugfix`

### Test plan

1. Turn on dynamic size
2. Zoom in
3. Create a text shape
4. Give a label
5. Try to resize the shape to be narrow

### Release notes

- Fixed a bug with the minimum size on dynamically scaled text shapes
This commit is contained in:
Steve Ruiz 2024-07-11 12:14:19 +01:00 committed by GitHub
parent ce493dcfaf
commit a7fac3bcc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -92,7 +92,6 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
const cx = w / 2 const cx = w / 2
const cy = h / 2 const cy = h / 2
const strokeWidth = STROKE_SIZES[shape.props.size] * shape.props.scale
const isFilled = shape.props.fill !== 'none' // || shape.props.text.trim().length > 0 const isFilled = shape.props.fill !== 'none' // || shape.props.text.trim().length > 0
let body: Geometry2d let body: Geometry2d
@ -318,20 +317,28 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
} }
} }
const labelSize = getLabelSize(this.editor, shape) const unscaledlabelSize = getUnscaledLabelSize(this.editor, shape)
const minWidth = Math.min(100, w / 2) // unscaled w and h
const minHeight = Math.min( const unscaledW = w / shape.props.scale
LABEL_FONT_SIZES[shape.props.size] * shape.props.scale * TEXT_PROPS.lineHeight + const unscaledH = h / shape.props.scale
LABEL_PADDING * 2, const unscaledminWidth = Math.min(100, unscaledW / 2)
h / 2 const unscaledMinHeight = Math.min(
LABEL_FONT_SIZES[shape.props.size] * TEXT_PROPS.lineHeight + LABEL_PADDING * 2,
unscaledH / 2
) )
const labelWidth = Math.min(w, Math.max(labelSize.w, Math.min(minWidth, Math.max(1, w - 8)))) const unscaledLabelWidth = Math.min(
const labelHeight = Math.min(h, Math.max(labelSize.h, Math.min(minHeight, Math.max(1, w - 8)))) unscaledW,
Math.max(unscaledlabelSize.w, Math.min(unscaledminWidth, Math.max(1, unscaledW - 8)))
)
const unscaledLabelHeight = Math.min(
unscaledH,
Math.max(unscaledlabelSize.h, Math.min(unscaledMinHeight, Math.max(1, unscaledH - 8)))
)
// not sure if bug // not sure if bug
const lines = getLines(shape.props, strokeWidth) const lines = getLines(shape.props, STROKE_SIZES[shape.props.size] * shape.props.scale)
const edges = lines ? lines.map((line) => new Polyline2d({ points: line })) : [] const edges = lines ? lines.map((line) => new Polyline2d({ points: line })) : []
// todo: use centroid for label position // todo: use centroid for label position
@ -344,16 +351,16 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
shape.props.align === 'start' shape.props.align === 'start'
? 0 ? 0
: shape.props.align === 'end' : shape.props.align === 'end'
? w - labelWidth ? (unscaledW - unscaledLabelWidth) * shape.props.scale
: (w - labelWidth) / 2, : ((unscaledW - unscaledLabelWidth) / 2) * shape.props.scale,
y: y:
shape.props.verticalAlign === 'start' shape.props.verticalAlign === 'start'
? 0 ? 0
: shape.props.verticalAlign === 'end' : shape.props.verticalAlign === 'end'
? h - labelHeight ? (unscaledH - unscaledLabelHeight) * shape.props.scale
: (h - labelHeight) / 2, : ((unscaledH - unscaledLabelHeight) / 2) * shape.props.scale,
width: labelWidth, width: unscaledLabelWidth * shape.props.scale,
height: labelHeight, height: unscaledLabelHeight * shape.props.scale,
isFilled: true, isFilled: true,
isLabel: true, isLabel: true,
}), }),
@ -443,7 +450,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
font={font} font={font}
fontSize={LABEL_FONT_SIZES[size] * shape.props.scale} fontSize={LABEL_FONT_SIZES[size] * shape.props.scale}
lineHeight={TEXT_PROPS.lineHeight} lineHeight={TEXT_PROPS.lineHeight}
padding={16 * shape.props.scale} padding={LABEL_PADDING * shape.props.scale}
fill={fill} fill={fill}
align={align} align={align}
verticalAlign={verticalAlign} verticalAlign={verticalAlign}
@ -569,49 +576,52 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
shape, shape,
{ handle, newPoint, scaleX, scaleY, initialShape } { handle, newPoint, scaleX, scaleY, initialShape }
) => { ) => {
const unscaledInitialW = initialShape.props.w / initialShape.props.scale
const unscaledInitialH = initialShape.props.h / initialShape.props.scale
const unscaledGrowY = initialShape.props.growY / initialShape.props.scale
// use the w/h from props here instead of the initialBounds here, // use the w/h from props here instead of the initialBounds here,
// since cloud shapes calculated bounds can differ from the props w/h. // since cloud shapes calculated bounds can differ from the props w/h.
let w = initialShape.props.w * scaleX let unscaledW = unscaledInitialW * scaleX
let h = (initialShape.props.h + initialShape.props.growY) * scaleY let unscaledH = (unscaledInitialH + unscaledGrowY) * scaleY
let overShrinkX = 0 let overShrinkX = 0
let overShrinkY = 0 let overShrinkY = 0
const min = MIN_SIZE_WITH_LABEL
if (shape.props.text.trim()) { if (shape.props.text.trim()) {
let newW = Math.max(Math.abs(w), MIN_SIZE_WITH_LABEL) let newW = Math.max(Math.abs(unscaledW), min)
let newH = Math.max(Math.abs(h), MIN_SIZE_WITH_LABEL) let newH = Math.max(Math.abs(unscaledH), min)
if (newW < MIN_SIZE_WITH_LABEL && newH === MIN_SIZE_WITH_LABEL) { if (newW < min && newH === min) newW = min
newW = MIN_SIZE_WITH_LABEL if (newW === min && newH < min) newH = min
}
if (newW === MIN_SIZE_WITH_LABEL && newH < MIN_SIZE_WITH_LABEL) { const unscaledLabelSize = getUnscaledLabelSize(this.editor, {
newH = MIN_SIZE_WITH_LABEL
}
const labelSize = getLabelSize(this.editor, {
...shape, ...shape,
props: { props: {
...shape.props, ...shape.props,
w: newW, w: newW * shape.props.scale,
h: newH, h: newH * shape.props.scale,
}, },
}) })
const nextW = Math.max(Math.abs(w), labelSize.w) * Math.sign(w) const nextW = Math.max(Math.abs(unscaledW), unscaledLabelSize.w) * Math.sign(unscaledW)
const nextH = Math.max(Math.abs(h), labelSize.h) * Math.sign(h) const nextH = Math.max(Math.abs(unscaledH), unscaledLabelSize.h) * Math.sign(unscaledH)
overShrinkX = Math.abs(nextW) - Math.abs(w) overShrinkX = Math.abs(nextW) - Math.abs(unscaledW)
overShrinkY = Math.abs(nextH) - Math.abs(h) overShrinkY = Math.abs(nextH) - Math.abs(unscaledH)
w = nextW unscaledW = nextW
h = nextH unscaledH = nextH
} }
const scaledW = unscaledW * shape.props.scale
const scaledH = unscaledH * shape.props.scale
const offset = new Vec(0, 0) const offset = new Vec(0, 0)
// x offsets // x offsets
if (scaleX < 0) { if (scaleX < 0) {
offset.x += w offset.x += scaledW
} }
if (handle === 'left' || handle === 'top_left' || handle === 'bottom_left') { if (handle === 'left' || handle === 'top_left' || handle === 'bottom_left') {
@ -621,7 +631,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
// y offsets // y offsets
if (scaleY < 0) { if (scaleY < 0) {
offset.y += h offset.y += scaledH
} }
if (handle === 'top' || handle === 'top_left' || handle === 'top_right') { if (handle === 'top' || handle === 'top_left' || handle === 'top_right') {
@ -634,8 +644,8 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
x, x,
y, y,
props: { props: {
w: Math.max(Math.abs(w), 1), w: Math.max(Math.abs(scaledW), 1),
h: Math.max(Math.abs(h), 1), h: Math.max(Math.abs(scaledH), 1),
growY: 0, growY: 0,
}, },
} }
@ -658,13 +668,13 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
} }
} }
const prevHeight = shape.props.h const unscaledPrevHeight = shape.props.h / shape.props.scale
const nextHeight = getLabelSize(this.editor, shape).h const unscaledNextHeight = getUnscaledLabelSize(this.editor, shape).h
let growY: number | null = null let growY: number | null = null
if (nextHeight > prevHeight) { if (unscaledNextHeight > unscaledPrevHeight) {
growY = nextHeight - prevHeight growY = unscaledNextHeight - unscaledPrevHeight
} else { } else {
if (shape.props.growY) { if (shape.props.growY) {
growY = 0 growY = 0
@ -676,7 +686,8 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
...shape, ...shape,
props: { props: {
...shape.props, ...shape.props,
growY, // scale the growY
growY: growY * shape.props.scale,
}, },
} }
} }
@ -686,6 +697,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
const prevText = prev.props.text const prevText = prev.props.text
const nextText = next.props.text const nextText = next.props.text
// No change to text, font, or size, no need to update update
if ( if (
prevText === nextText && prevText === nextText &&
prev.props.font === next.props.font && prev.props.font === next.props.font &&
@ -694,6 +706,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
return return
} }
// If we got rid of the text, cancel out any growY from the prev text
if (prevText && !nextText) { if (prevText && !nextText) {
return { return {
...next, ...next,
@ -704,23 +717,27 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
} }
} }
const prevWidth = prev.props.w // Get the prev width and height in unscaled values
const prevHeight = prev.props.h const unscaledPrevWidth = prev.props.w / prev.props.scale
const nextSize = getLabelSize(this.editor, next) const unscaledPrevHeight = prev.props.h / prev.props.scale
const nextWidth = nextSize.w const unscaledPrevGrowY = prev.props.growY / prev.props.scale
const nextHeight = nextSize.h
// Get the next width and height in unscaled values
const unscaledNextLabelSize = getUnscaledLabelSize(this.editor, next)
// When entering the first character in a label (not pasting in multiple characters...) // When entering the first character in a label (not pasting in multiple characters...)
if (!prevText && nextText && nextText.length === 1) { if (!prevText && nextText && nextText.length === 1) {
let w = Math.max(prevWidth, nextWidth) let unscaledW = Math.max(unscaledPrevWidth, unscaledNextLabelSize.w)
let h = Math.max(prevHeight, nextHeight) let unscaledH = Math.max(unscaledPrevHeight, unscaledNextLabelSize.h)
const min = MIN_SIZE_WITH_LABEL
// If both the width and height were less than the minimum size, make the shape square // If both the width and height were less than the minimum size, make the shape square
if (prev.props.w < MIN_SIZE_WITH_LABEL && prev.props.h < MIN_SIZE_WITH_LABEL) { if (unscaledPrevWidth < min && unscaledPrevHeight < min) {
w = Math.max(w, MIN_SIZE_WITH_LABEL) unscaledW = Math.max(unscaledW, min)
h = Math.max(h, MIN_SIZE_WITH_LABEL) unscaledH = Math.max(unscaledH, min)
w = Math.max(w, h) unscaledW = Math.max(unscaledW, unscaledH)
h = Math.max(w, h) unscaledH = Math.max(unscaledW, unscaledH)
} }
// Don't set a growY—at least, not until we've implemented a growX property // Don't set a growY—at least, not until we've implemented a growX property
@ -728,8 +745,9 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
...next, ...next,
props: { props: {
...next.props, ...next.props,
w, // Scale the results
h, w: unscaledW * next.props.scale,
h: unscaledH * next.props.scale,
growY: 0, growY: 0,
}, },
} }
@ -737,34 +755,39 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
let growY: number | null = null let growY: number | null = null
if (nextHeight > prevHeight) { if (unscaledNextLabelSize.h > unscaledPrevHeight) {
growY = nextHeight - prevHeight growY = unscaledNextLabelSize.h - unscaledPrevHeight
} else { } else {
if (prev.props.growY) { if (unscaledPrevGrowY) {
growY = 0 growY = 0
} }
} }
if (growY !== null) { if (growY !== null) {
const unscaledNextWidth = next.props.w / next.props.scale
return { return {
...next, ...next,
props: { props: {
...next.props, ...next.props,
growY, // Scale the results
w: Math.max(next.props.w, nextWidth), growY: growY * next.props.scale,
w: Math.max(unscaledNextWidth, unscaledNextLabelSize.w) * next.props.scale,
}, },
} }
} }
if (nextWidth > prev.props.w) { if (unscaledNextLabelSize.w > unscaledPrevWidth) {
return { return {
...next, ...next,
props: { props: {
...next.props, ...next.props,
w: nextWidth, // Scale the results
w: unscaledNextLabelSize.w * next.props.scale,
}, },
} }
} }
// otherwise, no update needed
} }
override onDoubleClick = (shape: TLGeoShape) => { override onDoubleClick = (shape: TLGeoShape) => {
@ -795,8 +818,8 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
} }
} }
function getLabelSize(editor: Editor, shape: TLGeoShape) { function getUnscaledLabelSize(editor: Editor, shape: TLGeoShape) {
const text = shape.props.text const { text, font, size, w } = shape.props
if (!text) { if (!text) {
return { w: 0, h: 0 } return { w: 0, h: 0 }
@ -804,8 +827,8 @@ function getLabelSize(editor: Editor, shape: TLGeoShape) {
const minSize = editor.textMeasure.measureText('w', { const minSize = editor.textMeasure.measureText('w', {
...TEXT_PROPS, ...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font], fontFamily: FONT_FAMILIES[font],
fontSize: LABEL_FONT_SIZES[shape.props.size] * shape.props.scale, fontSize: LABEL_FONT_SIZES[size],
maxWidth: 100, // ? maxWidth: 100, // ?
}) })
@ -817,23 +840,23 @@ function getLabelSize(editor: Editor, shape: TLGeoShape) {
xl: 10, xl: 10,
} }
const size = editor.textMeasure.measureText(text, { const textSize = editor.textMeasure.measureText(text, {
...TEXT_PROPS, ...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font], fontFamily: FONT_FAMILIES[font],
fontSize: LABEL_FONT_SIZES[shape.props.size] * shape.props.scale, fontSize: LABEL_FONT_SIZES[size],
minWidth: minSize.w, minWidth: minSize.w,
maxWidth: Math.max( maxWidth: Math.max(
// Guard because a DOM nodes can't be less 0 // Guard because a DOM nodes can't be less 0
0, 0,
// A 'w' width that we're setting as the min-width // A 'w' width that we're setting as the min-width
Math.ceil(minSize.w + sizes[shape.props.size]), Math.ceil(minSize.w + sizes[size]),
// The actual text size // The actual text size
Math.ceil(shape.props.w - LABEL_PADDING * 2) Math.ceil(w / shape.props.scale - LABEL_PADDING * 2)
), ),
}) })
return { return {
w: size.w + LABEL_PADDING * 2, w: textSize.w + LABEL_PADDING * 2,
h: size.h + LABEL_PADDING * 2, h: textSize.h + LABEL_PADDING * 2,
} }
} }