From 8bbd1f0f3bd39f0e364cd9efbb196e02793aed62 Mon Sep 17 00:00:00 2001 From: miyanokomiya Date: Sun, 15 Dec 2024 10:55:34 +0900 Subject: [PATCH] feat: Implement simultaneous corner radius moving for bubble shapes --- .../shapeHandlers/bubbleHandler.ts | 40 ++++++++++---- .../appCanvas/bubble/bubbleSelectedState.ts | 54 ++++++++++++++----- 2 files changed, 73 insertions(+), 21 deletions(-) diff --git a/src/composables/shapeHandlers/bubbleHandler.ts b/src/composables/shapeHandlers/bubbleHandler.ts index 41fc76e9..626bb38d 100644 --- a/src/composables/shapeHandlers/bubbleHandler.ts +++ b/src/composables/shapeHandlers/bubbleHandler.ts @@ -5,7 +5,7 @@ import { applyFillStyle } from "../../utils/fillStyle"; import { TAU } from "../../utils/geometry"; import { defineShapeHandler } from "./core"; import { BubbleShape, getBeakControls, getBeakSize } from "../../shapes/polygons/bubble"; -import { applyLocalSpace, renderOutlinedCircle, scaleGlobalAlpha } from "../../utils/renderer"; +import { applyLocalSpace, renderOutlinedCircle, renderValueLabel, scaleGlobalAlpha } from "../../utils/renderer"; import { getShapeDetransform } from "../../shapes/rectPolygon"; import { getLocalAbsolutePoint } from "../../shapes/simplePolygon"; import { applyStrokeStyle } from "../../utils/strokeStyle"; @@ -88,7 +88,7 @@ export const newBubbleHandler = defineShapeHandler((opt }); if (hitResult?.type === "cornerXC" || hitResult?.type === "cornerYC") { - renderCornerGuidlines(ctx, shape, style, scale); + renderCornerGuidlines(ctx, style, scale, shape); } } @@ -185,19 +185,41 @@ function renderRootGuid( export function renderCornerGuidlines( renderCtx: CanvasRenderingContext2D, - shape: BubbleShape, style: StyleScheme, scale: number, + shape: BubbleShape, + showLabel = false, ) { - applyStrokeStyle(renderCtx, { - color: style.selectionSecondaly, - width: 2 * scale, - dash: "short", - }); - const shapeRect = { x: shape.p.x, y: shape.p.y, width: shape.width, height: shape.height }; applyLocalSpace(renderCtx, shapeRect, shape.rotation, () => { const [cornerXC, cornerYC] = getLocalCornerControl(shape, scale); + + if (showLabel) { + const margin = 20 * scale; + renderValueLabel( + renderCtx, + `${Math.round(shape.cornerC.x * 200)}%`, + { x: 0, y: -margin }, + -shape.rotation, + scale, + true, + ); + renderValueLabel( + renderCtx, + `${Math.round(shape.cornerC.y * 200)}%`, + { x: -margin, y: 0 }, + -shape.rotation, + scale, + true, + ); + } + + applyStrokeStyle(renderCtx, { + color: style.selectionSecondaly, + width: 2 * scale, + dash: "short", + }); + renderCtx.beginPath(); renderCtx.rect(0, 0, cornerXC.x, cornerYC.y); renderCtx.stroke(); diff --git a/src/composables/states/appCanvas/bubble/bubbleSelectedState.ts b/src/composables/states/appCanvas/bubble/bubbleSelectedState.ts index f386ba63..8c4a5f9c 100644 --- a/src/composables/states/appCanvas/bubble/bubbleSelectedState.ts +++ b/src/composables/states/appCanvas/bubble/bubbleSelectedState.ts @@ -10,6 +10,8 @@ import { getLocalAbsolutePoint, getLocalRelativeRate } from "../../../../shapes/ import { IVec2, applyAffine, clamp, getDistance, getInner, getPedal, sub } from "okageo"; import { BubbleShape, getBeakControls, getMaxBeakSize } from "../../../../shapes/polygons/bubble"; import { defineSingleSelectedHandlerState } from "../singleSelectedHandlerState"; +import { COMMAND_EXAM_SRC } from "../commandExams"; +import { snapNumber } from "../../../../utils/geometry"; export const newBubbleSelectedState = defineSingleSelectedHandlerState( (getters) => { @@ -59,46 +61,74 @@ export const newBubbleSelectedState = defineSingleSelectedHandlerState( renderFn: renderBeakControls, snapType: "disabled", }); - case "cornerXC": + case "cornerXC": { + let showLabel = !event.data.options.ctrl; return () => movingShapeControlState({ targetId: targetShape.id, - patchFn: (s, p) => { - const rate = getLocalRelativeRate(s, applyAffine(getShapeDetransform(s), p)); - return { cornerC: { x: clamp(0, 0.5, rate.x), y: s.cornerC.y } }; + snapType: "custom", + extraCommands: [COMMAND_EXAM_SRC.RESIZE_PROPORTIONALLY], + patchFn: (s, p, movement) => { + let nextRate = clamp( + 0, + 0.5, + getLocalRelativeRate(s, applyAffine(getShapeDetransform(s), p)).x, + ); + if (movement.ctrl) { + showLabel = false; + } else { + nextRate = snapNumber(nextRate, 0.01); + showLabel = true; + } + return { cornerC: { x: nextRate, y: movement.shift ? nextRate : s.cornerC.y } }; }, getControlFn: (s, scale) => applyAffine(getShapeTransform(s), getLocalCornerControl(s, scale)[0]), renderFn: (stateCtx, renderCtx, latestShape) => { renderCornerGuidlines( renderCtx, - latestShape, stateCtx.getStyleScheme(), stateCtx.getScale(), + latestShape, + showLabel, ); }, - snapType: "disabled", }); - case "cornerYC": + } + case "cornerYC": { + let showLabel = !event.data.options.ctrl; return () => movingShapeControlState({ targetId: targetShape.id, - patchFn: (s, p) => { - const rate = getLocalRelativeRate(s, applyAffine(getShapeDetransform(s), p)); - return { cornerC: { x: s.cornerC.x, y: clamp(0, 0.5, rate.y) } }; + snapType: "custom", + extraCommands: [COMMAND_EXAM_SRC.RESIZE_PROPORTIONALLY], + patchFn: (s, p, movement) => { + let nextRate = clamp( + 0, + 0.5, + getLocalRelativeRate(s, applyAffine(getShapeDetransform(s), p)).y, + ); + if (movement.ctrl) { + showLabel = false; + } else { + nextRate = snapNumber(nextRate, 0.01); + showLabel = true; + } + return { cornerC: { x: movement.shift ? nextRate : s.cornerC.x, y: nextRate } }; }, getControlFn: (s, scale) => applyAffine(getShapeTransform(s), getLocalCornerControl(s, scale)[1]), renderFn: (stateCtx, renderCtx, latestShape) => { renderCornerGuidlines( renderCtx, - latestShape, stateCtx.getStyleScheme(), stateCtx.getScale(), + latestShape, + showLabel, ); }, - snapType: "disabled", }); + } default: return; }