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 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
let body: Geometry2d
@ -318,20 +317,28 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
}
}
const labelSize = getLabelSize(this.editor, shape)
const minWidth = Math.min(100, w / 2)
const minHeight = Math.min(
LABEL_FONT_SIZES[shape.props.size] * shape.props.scale * TEXT_PROPS.lineHeight +
LABEL_PADDING * 2,
h / 2
const unscaledlabelSize = getUnscaledLabelSize(this.editor, shape)
// unscaled w and h
const unscaledW = w / shape.props.scale
const unscaledH = h / shape.props.scale
const unscaledminWidth = Math.min(100, unscaledW / 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 labelHeight = Math.min(h, Math.max(labelSize.h, Math.min(minHeight, Math.max(1, w - 8))))
const unscaledLabelWidth = Math.min(
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
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 })) : []
// todo: use centroid for label position
@ -344,16 +351,16 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
shape.props.align === 'start'
? 0
: shape.props.align === 'end'
? w - labelWidth
: (w - labelWidth) / 2,
? (unscaledW - unscaledLabelWidth) * shape.props.scale
: ((unscaledW - unscaledLabelWidth) / 2) * shape.props.scale,
y:
shape.props.verticalAlign === 'start'
? 0
: shape.props.verticalAlign === 'end'
? h - labelHeight
: (h - labelHeight) / 2,
width: labelWidth,
height: labelHeight,
? (unscaledH - unscaledLabelHeight) * shape.props.scale
: ((unscaledH - unscaledLabelHeight) / 2) * shape.props.scale,
width: unscaledLabelWidth * shape.props.scale,
height: unscaledLabelHeight * shape.props.scale,
isFilled: true,
isLabel: true,
}),
@ -443,7 +450,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
font={font}
fontSize={LABEL_FONT_SIZES[size] * shape.props.scale}
lineHeight={TEXT_PROPS.lineHeight}
padding={16 * shape.props.scale}
padding={LABEL_PADDING * shape.props.scale}
fill={fill}
align={align}
verticalAlign={verticalAlign}
@ -569,49 +576,52 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
shape,
{ 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,
// since cloud shapes calculated bounds can differ from the props w/h.
let w = initialShape.props.w * scaleX
let h = (initialShape.props.h + initialShape.props.growY) * scaleY
let unscaledW = unscaledInitialW * scaleX
let unscaledH = (unscaledInitialH + unscaledGrowY) * scaleY
let overShrinkX = 0
let overShrinkY = 0
const min = MIN_SIZE_WITH_LABEL
if (shape.props.text.trim()) {
let newW = Math.max(Math.abs(w), MIN_SIZE_WITH_LABEL)
let newH = Math.max(Math.abs(h), MIN_SIZE_WITH_LABEL)
let newW = Math.max(Math.abs(unscaledW), min)
let newH = Math.max(Math.abs(unscaledH), min)
if (newW < MIN_SIZE_WITH_LABEL && newH === MIN_SIZE_WITH_LABEL) {
newW = MIN_SIZE_WITH_LABEL
}
if (newW < min && newH === min) newW = min
if (newW === min && newH < min) newH = min
if (newW === MIN_SIZE_WITH_LABEL && newH < MIN_SIZE_WITH_LABEL) {
newH = MIN_SIZE_WITH_LABEL
}
const labelSize = getLabelSize(this.editor, {
const unscaledLabelSize = getUnscaledLabelSize(this.editor, {
...shape,
props: {
...shape.props,
w: newW,
h: newH,
w: newW * shape.props.scale,
h: newH * shape.props.scale,
},
})
const nextW = Math.max(Math.abs(w), labelSize.w) * Math.sign(w)
const nextH = Math.max(Math.abs(h), labelSize.h) * Math.sign(h)
overShrinkX = Math.abs(nextW) - Math.abs(w)
overShrinkY = Math.abs(nextH) - Math.abs(h)
const nextW = Math.max(Math.abs(unscaledW), unscaledLabelSize.w) * Math.sign(unscaledW)
const nextH = Math.max(Math.abs(unscaledH), unscaledLabelSize.h) * Math.sign(unscaledH)
overShrinkX = Math.abs(nextW) - Math.abs(unscaledW)
overShrinkY = Math.abs(nextH) - Math.abs(unscaledH)
w = nextW
h = nextH
unscaledW = nextW
unscaledH = nextH
}
const scaledW = unscaledW * shape.props.scale
const scaledH = unscaledH * shape.props.scale
const offset = new Vec(0, 0)
// x offsets
if (scaleX < 0) {
offset.x += w
offset.x += scaledW
}
if (handle === 'left' || handle === 'top_left' || handle === 'bottom_left') {
@ -621,7 +631,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
// y offsets
if (scaleY < 0) {
offset.y += h
offset.y += scaledH
}
if (handle === 'top' || handle === 'top_left' || handle === 'top_right') {
@ -634,8 +644,8 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
x,
y,
props: {
w: Math.max(Math.abs(w), 1),
h: Math.max(Math.abs(h), 1),
w: Math.max(Math.abs(scaledW), 1),
h: Math.max(Math.abs(scaledH), 1),
growY: 0,
},
}
@ -658,13 +668,13 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
}
}
const prevHeight = shape.props.h
const nextHeight = getLabelSize(this.editor, shape).h
const unscaledPrevHeight = shape.props.h / shape.props.scale
const unscaledNextHeight = getUnscaledLabelSize(this.editor, shape).h
let growY: number | null = null
if (nextHeight > prevHeight) {
growY = nextHeight - prevHeight
if (unscaledNextHeight > unscaledPrevHeight) {
growY = unscaledNextHeight - unscaledPrevHeight
} else {
if (shape.props.growY) {
growY = 0
@ -676,7 +686,8 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
...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 nextText = next.props.text
// No change to text, font, or size, no need to update update
if (
prevText === nextText &&
prev.props.font === next.props.font &&
@ -694,6 +706,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
return
}
// If we got rid of the text, cancel out any growY from the prev text
if (prevText && !nextText) {
return {
...next,
@ -704,23 +717,27 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
}
}
const prevWidth = prev.props.w
const prevHeight = prev.props.h
const nextSize = getLabelSize(this.editor, next)
const nextWidth = nextSize.w
const nextHeight = nextSize.h
// Get the prev width and height in unscaled values
const unscaledPrevWidth = prev.props.w / prev.props.scale
const unscaledPrevHeight = prev.props.h / prev.props.scale
const unscaledPrevGrowY = prev.props.growY / prev.props.scale
// 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...)
if (!prevText && nextText && nextText.length === 1) {
let w = Math.max(prevWidth, nextWidth)
let h = Math.max(prevHeight, nextHeight)
let unscaledW = Math.max(unscaledPrevWidth, unscaledNextLabelSize.w)
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 (prev.props.w < MIN_SIZE_WITH_LABEL && prev.props.h < MIN_SIZE_WITH_LABEL) {
w = Math.max(w, MIN_SIZE_WITH_LABEL)
h = Math.max(h, MIN_SIZE_WITH_LABEL)
w = Math.max(w, h)
h = Math.max(w, h)
if (unscaledPrevWidth < min && unscaledPrevHeight < min) {
unscaledW = Math.max(unscaledW, min)
unscaledH = Math.max(unscaledH, min)
unscaledW = Math.max(unscaledW, unscaledH)
unscaledH = Math.max(unscaledW, unscaledH)
}
// 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,
props: {
...next.props,
w,
h,
// Scale the results
w: unscaledW * next.props.scale,
h: unscaledH * next.props.scale,
growY: 0,
},
}
@ -737,34 +755,39 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
let growY: number | null = null
if (nextHeight > prevHeight) {
growY = nextHeight - prevHeight
if (unscaledNextLabelSize.h > unscaledPrevHeight) {
growY = unscaledNextLabelSize.h - unscaledPrevHeight
} else {
if (prev.props.growY) {
if (unscaledPrevGrowY) {
growY = 0
}
}
if (growY !== null) {
const unscaledNextWidth = next.props.w / next.props.scale
return {
...next,
props: {
...next.props,
growY,
w: Math.max(next.props.w, nextWidth),
// Scale the results
growY: growY * next.props.scale,
w: Math.max(unscaledNextWidth, unscaledNextLabelSize.w) * next.props.scale,
},
}
}
if (nextWidth > prev.props.w) {
if (unscaledNextLabelSize.w > unscaledPrevWidth) {
return {
...next,
props: {
...next.props,
w: nextWidth,
// Scale the results
w: unscaledNextLabelSize.w * next.props.scale,
},
}
}
// otherwise, no update needed
}
override onDoubleClick = (shape: TLGeoShape) => {
@ -795,8 +818,8 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
}
}
function getLabelSize(editor: Editor, shape: TLGeoShape) {
const text = shape.props.text
function getUnscaledLabelSize(editor: Editor, shape: TLGeoShape) {
const { text, font, size, w } = shape.props
if (!text) {
return { w: 0, h: 0 }
@ -804,8 +827,8 @@ function getLabelSize(editor: Editor, shape: TLGeoShape) {
const minSize = editor.textMeasure.measureText('w', {
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font],
fontSize: LABEL_FONT_SIZES[shape.props.size] * shape.props.scale,
fontFamily: FONT_FAMILIES[font],
fontSize: LABEL_FONT_SIZES[size],
maxWidth: 100, // ?
})
@ -817,23 +840,23 @@ function getLabelSize(editor: Editor, shape: TLGeoShape) {
xl: 10,
}
const size = editor.textMeasure.measureText(text, {
const textSize = editor.textMeasure.measureText(text, {
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font],
fontSize: LABEL_FONT_SIZES[shape.props.size] * shape.props.scale,
fontFamily: FONT_FAMILIES[font],
fontSize: LABEL_FONT_SIZES[size],
minWidth: minSize.w,
maxWidth: Math.max(
// Guard because a DOM nodes can't be less 0
0,
// 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
Math.ceil(shape.props.w - LABEL_PADDING * 2)
Math.ceil(w / shape.props.scale - LABEL_PADDING * 2)
),
})
return {
w: size.w + LABEL_PADDING * 2,
h: size.h + LABEL_PADDING * 2,
w: textSize.w + LABEL_PADDING * 2,
h: textSize.h + LABEL_PADDING * 2,
}
}