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:
parent
ce493dcfaf
commit
a7fac3bcc4
1 changed files with 101 additions and 78 deletions
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue