update pen tool space drag spec
Adds a new scenario to the pen tool specifications defining the behavior for relocating the active vertex with spacebar during a curve drag, including asserting that the cursor remains a crosshair instead of switching to the hand tool. Made-with: Cursor
This commit is contained in:
parent
455f04d6f1
commit
0ff5a87ccb
|
|
@ -19,6 +19,15 @@ Pressing `P` SHALL activate the Pen tool. Pen-created vectors SHALL use vector-n
|
|||
- **WHEN** user drags while placing a new point
|
||||
- **THEN** non-zero tangents are recorded for bezier curvature
|
||||
|
||||
### Requirement: Vertex manipulation during creation
|
||||
While drawing with the Pen tool, users SHALL be able to relocate the currently placed vertex without exiting the active curve drag.
|
||||
|
||||
#### Scenario: Relocate vertex with Space
|
||||
- **WHEN** user drags to create a curve segment and holds `Space`
|
||||
- **THEN** the active vertex moves with the cursor
|
||||
- **AND** the tangent handle pull distance remains locked relative to the new vertex position
|
||||
- **AND** cursor visually remains a crosshair, not switching to the hand tool
|
||||
|
||||
### Requirement: Open and closed path commit behavior
|
||||
Path close intent SHALL be detected near the first vertex, but final close commit SHALL happen on `mouseUp`, not `mouseDown`.
|
||||
|
||||
|
|
|
|||
|
|
@ -522,8 +522,12 @@ function solveMergedTangents(
|
|||
const inner = { x: b1 * toRA.x + b2 * toRB.x, y: b1 * toRA.y + b2 * toRB.y }
|
||||
const c =
|
||||
Math.abs(inner.x) > Math.abs(inner.y)
|
||||
? (inner.x !== 0 ? rhs.x / inner.x : 1)
|
||||
: (inner.y !== 0 ? rhs.y / inner.y : 1)
|
||||
? inner.x !== 0
|
||||
? rhs.x / inner.x
|
||||
: 1
|
||||
: inner.y !== 0
|
||||
? rhs.y / inner.y
|
||||
: 1
|
||||
return {
|
||||
tangentStart: { x: c * toRA.x, y: c * toRA.y },
|
||||
tangentEnd: { x: c * toRB.x, y: c * toRB.y }
|
||||
|
|
|
|||
|
|
@ -161,7 +161,9 @@ export function createShapeActions(ctx: EditorContext) {
|
|||
const anchorIndex = isClosing ? 0 : ps.vertices.length - 1
|
||||
const lastSeg = ps.segments.length > 0 ? ps.segments[ps.segments.length - 1] : undefined
|
||||
const firstSeg = ps.segments.length > 0 ? ps.segments[0] : undefined
|
||||
const opposite = options?.oppositeTangent ?? ps.oppositeDragTangent ??
|
||||
const opposite =
|
||||
options?.oppositeTangent ??
|
||||
ps.oppositeDragTangent ??
|
||||
(lastSeg ? lastSeg.tangentEnd : { x: -tx, y: -ty })
|
||||
|
||||
if (options?.constrainToOpposite) {
|
||||
|
|
@ -199,6 +201,16 @@ export function createShapeActions(ctx: EditorContext) {
|
|||
ctx.requestRepaint()
|
||||
}
|
||||
|
||||
function penSetKnotPosition(x: number, y: number) {
|
||||
if (!ctx.state.penState) return
|
||||
const ps = ctx.state.penState
|
||||
const isClosing = !!ps.pendingClose && ps.vertices.length > 2
|
||||
const anchorIndex = isClosing ? 0 : ps.vertices.length - 1
|
||||
ps.vertices[anchorIndex].x = x
|
||||
ps.vertices[anchorIndex].y = y
|
||||
ctx.requestRender()
|
||||
}
|
||||
|
||||
function penCommit(closed: boolean) {
|
||||
const ps = ctx.state.penState
|
||||
if (!ps || ps.vertices.length < 2) {
|
||||
|
|
@ -350,6 +362,7 @@ export function createShapeActions(ctx: EditorContext) {
|
|||
penSetDragTangent,
|
||||
penSetClosingToFirst,
|
||||
penSetPendingClose,
|
||||
penSetKnotPosition,
|
||||
penCommit,
|
||||
penCancel,
|
||||
adoptNodesIntoSection,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { cloneVectorNetwork } from '../scene-graph'
|
||||
import { defineTool, nodeSummary } from './schema'
|
||||
|
||||
import type { FigmaAPI } from '../figma-api'
|
||||
import { cloneVectorNetwork } from '../scene-graph'
|
||||
import type { SceneNode, VectorNetwork } from '../scene-graph'
|
||||
|
||||
function getVectorNode(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ Drücken Sie <kbd>P</kbd>, um das Stiftwerkzeug zu aktivieren.
|
|||
|
||||
- **Klicken** setzt einen Eckpunkt (gerades Segment)
|
||||
- **Klicken + Ziehen** setzt einen Kurvenpunkt mit Bézier-Tangentengriffen
|
||||
- **<kbd>Space</kbd> gedrückt halten** beim Ziehen (ohne die Maustaste loszulassen), um den Punkt selbst zu verschieben
|
||||
|
||||
Klicken Sie mehrere Punkte, um einen Pfad Segment für Segment aufzubauen. Eine Vorschaulinie erstreckt sich vom letzten Punkt zu Ihrem Cursor.
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ Pulsa <kbd>P</kbd> para activar la herramienta pluma.
|
|||
|
||||
- **Clic** — punto de esquina (segmento recto)
|
||||
- **Clic + arrastrar** — punto de curva con manejadores de tangente Bézier — la dirección y longitud del arrastre controlan la forma de la curva
|
||||
- **Mantener <kbd>Space</kbd>** mientras arrastras (sin soltar el botón del ratón) para mover el punto en sí
|
||||
|
||||
## Cerrar un trazado
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ description: Tracés vectoriels avec courbes de Bézier in OpenPencil.
|
|||
## Placer des points
|
||||
- **Click** — corner point
|
||||
- **Click + drag** — curve point with Bézier tangent handles
|
||||
- **Hold <kbd>Space</kbd>** while dragging to move the point itself
|
||||
|
||||
## Fermer un tracé
|
||||
Click the first point to close into a loop.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ description: Percorsi vettoriali con curve di Bézier in OpenPencil.
|
|||
## Posizionare punti
|
||||
- **Click** — punto angolare (segmento rettilineo)
|
||||
- **Click + trascina** — punto curvo con maniglie tangenti di Bézier
|
||||
- **Tieni premuto <kbd>Space</kbd>** mentre trascini per spostare il punto stesso
|
||||
|
||||
## Chiudere un percorso
|
||||
Clicca sul primo punto del percorso per chiuderlo in un anello. I percorsi chiusi possono essere riempiti.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ description: Ścieżki wektorowe z krzywymi Béziera in OpenPencil.
|
|||
## Stawianie punktów
|
||||
- **Click** — corner point
|
||||
- **Click + drag** — curve point with Bézier tangent handles
|
||||
- **Hold <kbd>Space</kbd>** while dragging to move the point itself
|
||||
|
||||
## Zamykanie ścieżki
|
||||
Click the first point to close into a loop.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ description: Рисование векторных контуров с крив
|
|||
|
||||
- **Клик** — ставит угловую точку (прямолинейный сегмент)
|
||||
- **Клик + перетаскивание** — ставит точку кривой с касательными ручками Безье: направление и длина перетаскивания определяют форму кривой
|
||||
- **Удерживайте <kbd>Space</kbd>** во время перетаскивания (не отпуская кнопку мыши), чтобы переместить саму точку
|
||||
|
||||
Кликайте по нескольким точкам, чтобы строить контур сегмент за сегментом. Линия предпросмотра тянется от последней поставленной точки к вашему курсору при перемещении.
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ Press <kbd>P</kbd> to activate the pen tool.
|
|||
|
||||
- **Click** to place a corner point (straight-line segment)
|
||||
- **Click + drag** to place a curve point with bezier tangent handles — the drag direction and length control the curve shape
|
||||
- **Hold <kbd>Space</kbd>** while dragging (without releasing the mouse button) to move the point itself
|
||||
|
||||
Click multiple points to build a path segment by segment. A preview line extends from the last placed point to your cursor as you move.
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,13 @@ export function useCanvasInput(
|
|||
) {
|
||||
const drag = ref<DragState | null>(null)
|
||||
const cursorOverride = ref<string | null>(null)
|
||||
const spaceHeld = ref(false)
|
||||
useEventListener(window, 'keydown', (e: KeyboardEvent) => {
|
||||
if (e.code === 'Space') spaceHeld.value = true
|
||||
})
|
||||
useEventListener(window, 'keyup', (e: KeyboardEvent) => {
|
||||
if (e.code === 'Space') spaceHeld.value = false
|
||||
})
|
||||
let lastClickTime = 0
|
||||
let lastClickX = 0
|
||||
let lastClickY = 0
|
||||
|
|
@ -385,6 +392,10 @@ export function useCanvasInput(
|
|||
}
|
||||
|
||||
if (tool === 'PEN') {
|
||||
// Hide draft segment during drag
|
||||
editor.state.penCursorX = null
|
||||
editor.state.penCursorY = null
|
||||
|
||||
// In node edit mode with pen tool: click curve to add point, click vertex to remove
|
||||
const nodeEditState = (
|
||||
editor.state as Editor['state'] & { nodeEditState?: NodeEditState | null }
|
||||
|
|
@ -406,8 +417,14 @@ export function useCanvasInput(
|
|||
startX: first.x,
|
||||
startY: first.y,
|
||||
modifierMode: 'default',
|
||||
frozenOppositeTangent: null
|
||||
frozenOppositeTangent: null,
|
||||
spaceDown: false,
|
||||
spaceStartX: 0,
|
||||
spaceStartY: 0,
|
||||
knotStartX: first.x,
|
||||
knotStartY: first.y
|
||||
} as DragState
|
||||
cursorOverride.value = 'crosshair'
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -419,8 +436,14 @@ export function useCanvasInput(
|
|||
startX: cx,
|
||||
startY: cy,
|
||||
modifierMode: 'default',
|
||||
frozenOppositeTangent: null
|
||||
frozenOppositeTangent: null,
|
||||
spaceDown: false,
|
||||
spaceStartX: 0,
|
||||
spaceStartY: 0,
|
||||
knotStartX: cx,
|
||||
knotStartY: cy
|
||||
} as DragState
|
||||
cursorOverride.value = 'crosshair'
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -455,11 +478,13 @@ export function useCanvasInput(
|
|||
editor.state.penCursorX = cx
|
||||
editor.state.penCursorY = cy
|
||||
|
||||
if (!drag.value) {
|
||||
const first = editor.state.penState.vertices[0]
|
||||
if (editor.state.penState.vertices.length > 2) {
|
||||
const dist = Math.hypot(cx - first.x, cy - first.y)
|
||||
editor.penSetClosingToFirst(dist < PEN_CLOSE_THRESHOLD)
|
||||
}
|
||||
}
|
||||
editor.requestRepaint()
|
||||
}
|
||||
|
||||
|
|
@ -520,13 +545,42 @@ export function useCanvasInput(
|
|||
}
|
||||
|
||||
if (d.type === 'pen-drag') {
|
||||
const isSpace = spaceHeld.value
|
||||
const penState = editor.state.penState
|
||||
|
||||
if (!penState) return
|
||||
const isClosing = !!penState.pendingClose && penState.vertices.length > 2
|
||||
const anchorIndex = isClosing ? 0 : penState.vertices.length - 1
|
||||
const anchor = penState.vertices[anchorIndex]
|
||||
|
||||
if (isSpace) {
|
||||
if (!d.spaceDown) {
|
||||
d.spaceDown = true
|
||||
d.spaceStartX = cx
|
||||
d.spaceStartY = cy
|
||||
d.knotStartX = anchor.x
|
||||
d.knotStartY = anchor.y
|
||||
}
|
||||
|
||||
const dx = cx - d.spaceStartX
|
||||
const dy = cy - d.spaceStartY
|
||||
editor.penSetKnotPosition?.(d.knotStartX + dx, d.knotStartY + dy)
|
||||
} else {
|
||||
if (d.spaceDown) {
|
||||
d.spaceDown = false
|
||||
// Adjust startX/startY so the tangent pull is relative to the new knot position
|
||||
const dx = anchor.x - d.knotStartX
|
||||
const dy = anchor.y - d.knotStartY
|
||||
d.startX += dx
|
||||
d.startY += dy
|
||||
}
|
||||
|
||||
const tx = cx - d.startX
|
||||
const ty = cy - d.startY
|
||||
if (Math.hypot(tx, ty) > 2) {
|
||||
const penState = editor.state.penState
|
||||
const firstSeg = penState?.segments[0]
|
||||
const firstSeg = penState.segments[0]
|
||||
const closingOpposite =
|
||||
penState?.pendingClose && firstSeg
|
||||
penState.pendingClose && firstSeg
|
||||
? firstSeg.start === 0
|
||||
? firstSeg.tangentStart
|
||||
: firstSeg.end === 0
|
||||
|
|
@ -538,12 +592,12 @@ export function useCanvasInput(
|
|||
if (mode === 'default') {
|
||||
d.frozenOppositeTangent = null
|
||||
} else if (!d.frozenOppositeTangent) {
|
||||
const lastSeg = penState?.segments[penState.segments.length - 1]
|
||||
const lastSeg = penState.segments[penState.segments.length - 1]
|
||||
d.frozenOppositeTangent = lastSeg
|
||||
? closingOpposite
|
||||
? { ...closingOpposite }
|
||||
: { ...lastSeg.tangentEnd }
|
||||
: penState?.dragTangent
|
||||
: penState.dragTangent
|
||||
? { x: -penState.dragTangent.x, y: -penState.dragTangent.y }
|
||||
: { x: 0, y: 0 }
|
||||
}
|
||||
|
|
@ -565,7 +619,7 @@ export function useCanvasInput(
|
|||
editor.penSetDragTangent(
|
||||
tx,
|
||||
ty,
|
||||
penState?.pendingClose
|
||||
penState.pendingClose
|
||||
? {
|
||||
keepOpposite: true,
|
||||
oppositeTangent: closingOpposite
|
||||
|
|
@ -574,6 +628,7 @@ export function useCanvasInput(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { cloneVectorNetwork } from '@open-pencil/core'
|
||||
|
||||
import { hitTestHandle } from './geometry'
|
||||
|
||||
import type { DragResize, HandlePosition } from './types'
|
||||
import { cloneVectorNetwork } from '@open-pencil/core'
|
||||
import type { Rect, SceneNode } from '@open-pencil/core'
|
||||
import type { Editor } from '@open-pencil/core/editor'
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,11 @@ export interface DragPen {
|
|||
startY: number
|
||||
modifierMode: 'default' | 'continuous' | 'independent'
|
||||
frozenOppositeTangent: Vector | null
|
||||
spaceDown: boolean
|
||||
spaceStartX: number
|
||||
spaceStartY: number
|
||||
knotStartX: number
|
||||
knotStartY: number
|
||||
}
|
||||
|
||||
export interface DragTextSelect {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ const { panels } = useI18n()
|
|||
class="flex items-center gap-1.5 border-b border-border px-3 py-2"
|
||||
>
|
||||
<span class="text-[11px] text-muted">{{ panels.mixed }}</span>
|
||||
<span class="text-xs font-semibold">{{ panels.layersCount({ count: String(multiCount) }) }}</span>
|
||||
<span class="text-xs font-semibold">{{
|
||||
panels.layersCount({ count: String(multiCount) })
|
||||
}}</span>
|
||||
</div>
|
||||
<PositionSection />
|
||||
<AppearanceSection />
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@ import Tip from './ui/Tip.vue'
|
|||
import HsvColorArea from './HsvColorArea.vue'
|
||||
import ScrubInput from './ScrubInput.vue'
|
||||
import { colorToCSS } from '@open-pencil/core'
|
||||
import { GradientEditorRoot, GradientEditorBar, GradientEditorStop, useI18n } from '@open-pencil/vue'
|
||||
import {
|
||||
GradientEditorRoot,
|
||||
GradientEditorBar,
|
||||
GradientEditorStop,
|
||||
useI18n
|
||||
} from '@open-pencil/vue'
|
||||
|
||||
import type { Fill } from '@open-pencil/core'
|
||||
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ function handlePageDblClick(
|
|||
<PageListRoot v-slot="{ pages, currentPageId, isDivider, addPage, switchPage, renamePage }">
|
||||
<div data-test-id="pages-panel" class="flex min-h-0 flex-1 flex-col">
|
||||
<div class="flex shrink-0 items-center justify-between px-3 py-1.5">
|
||||
<span data-test-id="pages-header" class="text-[11px] tracking-wider text-muted uppercase"
|
||||
>{{ panels.pages }}</span
|
||||
>
|
||||
<span data-test-id="pages-header" class="text-[11px] tracking-wider text-muted uppercase">{{
|
||||
panels.pages
|
||||
}}</span>
|
||||
<Tip :label="panels.addPage">
|
||||
<button
|
||||
data-test-id="pages-add"
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ const ctx = useVariablesEditor({
|
|||
<DialogContent data-test-id="variables-dialog" :class="cls.content">
|
||||
<div v-if="!ctx.hasCollections" class="flex flex-1 flex-col">
|
||||
<div class="flex shrink-0 items-center justify-between border-b border-border px-4 py-3">
|
||||
<DialogTitle class="text-sm font-semibold text-surface">{{ dialogs.localVariables }}</DialogTitle>
|
||||
<DialogTitle class="text-sm font-semibold text-surface">{{
|
||||
dialogs.localVariables
|
||||
}}</DialogTitle>
|
||||
<DialogClose
|
||||
class="flex size-6 cursor-pointer items-center justify-center rounded border-none bg-transparent text-muted hover:bg-hover hover:text-surface"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -126,7 +126,13 @@ function onToggleCorners() {
|
|||
@commit="(v: number, p: number) => commitCornerProp('topLeftRadius', v, p)"
|
||||
>
|
||||
<template #icon>
|
||||
<svg class="size-3" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<svg
|
||||
class="size-3"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path d="M1 11V4a3 3 0 0 1 3-3h7" />
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -138,7 +144,13 @@ function onToggleCorners() {
|
|||
@commit="(v: number, p: number) => commitCornerProp('topRightRadius', v, p)"
|
||||
>
|
||||
<template #icon>
|
||||
<svg class="size-3" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<svg
|
||||
class="size-3"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path d="M11 11V4a3 3 0 0 0-3-3H1" />
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -150,7 +162,13 @@ function onToggleCorners() {
|
|||
@commit="(v: number, p: number) => commitCornerProp('bottomLeftRadius', v, p)"
|
||||
>
|
||||
<template #icon>
|
||||
<svg class="size-3" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<svg
|
||||
class="size-3"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path d="M1 1v7a3 3 0 0 0 3 3h7" />
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -162,7 +180,13 @@ function onToggleCorners() {
|
|||
@commit="(v: number, p: number) => commitCornerProp('bottomRightRadius', v, p)"
|
||||
>
|
||||
<template #icon>
|
||||
<svg class="size-3" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<svg
|
||||
class="size-3"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path d="M11 1v7a3 3 0 0 1-3 3H1" />
|
||||
</svg>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ type BindingApi = {
|
|||
unbindVariable: (nodeId: string, index: number) => void
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
const { item, index, activeNodeId, bindingApi, visibilityTestId, unbindTestId } = defineProps<{
|
||||
item: { opacity: number; visible: boolean }
|
||||
index: number
|
||||
activeNodeId?: string | null
|
||||
|
|
@ -97,9 +97,9 @@ const { panels, dialogs } = useI18n()
|
|||
@update:model-value="bindingApi.searchTerm.value = String($event)"
|
||||
/>
|
||||
<ComboboxContent class="max-h-48 overflow-y-auto p-1">
|
||||
<ComboboxEmpty class="px-2 py-3 text-center text-[11px] text-muted"
|
||||
>{{ panels.noVariablesFound }}</ComboboxEmpty
|
||||
>
|
||||
<ComboboxEmpty class="px-2 py-3 text-center text-[11px] text-muted">{{
|
||||
panels.noVariablesFound
|
||||
}}</ComboboxEmpty>
|
||||
<ComboboxItem
|
||||
v-for="v in bindingApi.filteredVariables.value"
|
||||
:key="v.id"
|
||||
|
|
|
|||
|
|
@ -77,12 +77,12 @@ function onToggleSides(activeNode: SceneNode) {
|
|||
class="w-[72px]"
|
||||
:model-value="strokeCtx.currentAlign(activeNode)"
|
||||
:options="strokeCtx.alignOptions"
|
||||
@update:model-value="strokeCtx.updateAlign($event as Stroke['align'], activeNode)"
|
||||
@update:model-value="strokeCtx.updateAlign($event as Stroke['align'], activeNode!)"
|
||||
/>
|
||||
<ScrubInput
|
||||
v-if="!expandedSides"
|
||||
class="flex-1"
|
||||
:model-value="activeNode.strokes[0]?.weight ?? 1"
|
||||
:model-value="activeNode!.strokes[0]?.weight ?? 1"
|
||||
:min="0"
|
||||
@update:model-value="patch(0, { weight: $event })"
|
||||
>
|
||||
|
|
@ -105,7 +105,7 @@ function onToggleSides(activeNode: SceneNode) {
|
|||
data-test-id="stroke-sides-toggle"
|
||||
class="flex size-[26px] shrink-0 cursor-pointer items-center justify-center rounded border border-border bg-input text-muted hover:bg-hover hover:text-surface"
|
||||
:class="{ '!border-accent !text-accent': expandedSides }"
|
||||
@click="onToggleSides(activeNode)"
|
||||
@click="onToggleSides(activeNode!)"
|
||||
>
|
||||
<svg class="size-3.5" viewBox="0 0 14 14" fill="currentColor">
|
||||
<rect x="1" y="1" width="5" height="5" rx="1" />
|
||||
|
|
@ -125,12 +125,12 @@ function onToggleSides(activeNode: SceneNode) {
|
|||
v-for="side in strokeCtx.borderSides"
|
||||
:key="side"
|
||||
:model-value="
|
||||
activeNode[
|
||||
activeNode![
|
||||
`border${side[0].toUpperCase()}${side.slice(1)}Weight` as keyof SceneNode
|
||||
] as number
|
||||
"
|
||||
:min="0"
|
||||
@update:model-value="strokeCtx.updateBorderWeight(side, $event, activeNode)"
|
||||
@update:model-value="strokeCtx.updateBorderWeight(side, $event, activeNode!)"
|
||||
>
|
||||
<template #icon>
|
||||
<svg class="size-3" viewBox="0 0 12 12" fill="none" stroke-width="1.5">
|
||||
|
|
|
|||
|
|
@ -14,12 +14,6 @@ import { openFileDialog } from './use-menu'
|
|||
|
||||
import type { ComputedRef } from 'vue'
|
||||
|
||||
type NodeEditKeyboardMethods = Partial<{
|
||||
nodeEditDeleteSelected: () => void
|
||||
nodeEditBreakAtVertex: () => void
|
||||
exitNodeEditMode: (commit: boolean) => void
|
||||
}>
|
||||
|
||||
function isEditing(e: Event) {
|
||||
return e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement
|
||||
}
|
||||
|
|
@ -105,7 +99,14 @@ export function useKeyboard() {
|
|||
|
||||
useEventListener(window, 'keydown', (e: KeyboardEvent) => {
|
||||
if (isEditing(e)) return
|
||||
if (e.code === 'Space' && !e.metaKey && !e.ctrlKey && !e.altKey && !e.repeat && toolBeforeSpace === null) {
|
||||
if (
|
||||
e.code === 'Space' &&
|
||||
!e.metaKey &&
|
||||
!e.ctrlKey &&
|
||||
!e.altKey &&
|
||||
!e.repeat &&
|
||||
toolBeforeSpace === null
|
||||
) {
|
||||
if (store.state.activeTool !== 'HAND') {
|
||||
toolBeforeSpace = store.state.activeTool
|
||||
store.setTool('HAND')
|
||||
|
|
@ -245,7 +246,7 @@ export function useKeyboard() {
|
|||
(store.state.nodeEditState.selectedVertexIndices.size > 0 ||
|
||||
store.state.nodeEditState.selectedHandles.size > 0)
|
||||
) {
|
||||
nodeEditStore.nodeEditDeleteSelected()
|
||||
store.nodeEditDeleteSelected()
|
||||
return
|
||||
}
|
||||
runCommand('selection.delete')
|
||||
|
|
@ -257,9 +258,9 @@ export function useKeyboard() {
|
|||
store.state.nodeEditState.selectedHandles.size > 0)
|
||||
) {
|
||||
if (keys['alt'].value) {
|
||||
nodeEditStore.nodeEditBreakAtVertex()
|
||||
store.nodeEditBreakAtVertex()
|
||||
} else {
|
||||
nodeEditStore.nodeEditDeleteSelected()
|
||||
store.nodeEditDeleteSelected()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -267,14 +268,14 @@ export function useKeyboard() {
|
|||
})
|
||||
whenever(plain('Enter'), () => {
|
||||
if (store.state.nodeEditState) {
|
||||
nodeEditStore.exitNodeEditMode(true)
|
||||
store.exitNodeEditMode(true)
|
||||
return
|
||||
}
|
||||
if (store.state.penState) store.penCommit(false)
|
||||
})
|
||||
whenever(plain('Escape'), () => {
|
||||
if (store.state.nodeEditState) {
|
||||
nodeEditStore.exitNodeEditMode(true)
|
||||
store.exitNodeEditMode(true)
|
||||
return
|
||||
}
|
||||
if (store.state.penState) {
|
||||
|
|
|
|||
|
|
@ -218,7 +218,8 @@ export function createEditorStore(initialGraph?: SceneGraph) {
|
|||
|
||||
function setTool(tool: Tool) {
|
||||
// If switching away from PEN while drawing, commit the open path
|
||||
if (state.penState && tool !== 'PEN') {
|
||||
// except when switching to HAND (e.g. holding Space to pan)
|
||||
if (state.penState && tool !== 'PEN' && tool !== 'HAND') {
|
||||
editor.penCommit(false)
|
||||
}
|
||||
state.activeTool = tool
|
||||
|
|
@ -789,7 +790,7 @@ export function createEditorStore(initialGraph?: SceneGraph) {
|
|||
if (v > hi) hi = v
|
||||
}
|
||||
|
||||
const target = align === 'min' ? lo : (align === 'max' ? hi : (lo + hi) / 2)
|
||||
const target = align === 'min' ? lo : align === 'max' ? hi : (lo + hi) / 2
|
||||
for (const i of indices) {
|
||||
es.vertices[i] = { ...es.vertices[i], [prop]: target }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue