feat: corner click in edge-dock starts single-axis resize (exit dock); corners/center not dock
This commit is contained in:
1 parent
2dcb572b12
commit
ccc38a0561
2 files changed
+99
-15
No files matched your search
+32
-8
@@ -160,7 +160,7 @@ export default function () {
|
||||
// y = startY + (top? dy/zoom : 0)
|
||||
// Это устраняет "скачки": позиция не зависит от текущего кадра, а размер и позиция
|
||||
// переведены в одни единицы (px для размера, canvas/zoom для позиции).
|
||||
let resizeAnchor: { x: number; y: number; w: number; h: number; dir: string } | null = null
|
||||
let resizeAnchor: { x: number; y: number; w: number; h: number; dir: string; fixedAxis?: 'none' | 'x' | 'y' } | null = null
|
||||
function getPos () {
|
||||
try {
|
||||
const p = (figma.ui as any).getPosition()
|
||||
@@ -169,25 +169,35 @@ export default function () {
|
||||
return { x: 0, y: 0 }
|
||||
}
|
||||
}
|
||||
on('RESIZE_START', function (payload: { dir: string; startW: number; startH: number }) {
|
||||
on('RESIZE_START', function (payload: { dir: string; startW: number; startH: number; fixedAxis?: 'none' | 'x' | 'y' }) {
|
||||
const p = getPos()
|
||||
resizeAnchor = {
|
||||
x: p.x,
|
||||
y: p.y,
|
||||
w: payload.startW,
|
||||
h: payload.startH,
|
||||
dir: payload.dir
|
||||
dir: payload.dir,
|
||||
fixedAxis: payload.fixedAxis || 'none'
|
||||
}
|
||||
})
|
||||
on('RESIZE_WINDOW_DRAG', function (payload: { dx: number; dy: number; dir: string }) {
|
||||
on('RESIZE_WINDOW_DRAG', function (payload: { dx: number; dy: number; dir: string; fixedAxis?: 'none' | 'x' | 'y' }) {
|
||||
if (resizeAnchor == null) return
|
||||
const { dx, dy, dir } = payload
|
||||
const fixed = payload.fixedAxis || resizeAnchor.fixedAxis || 'none'
|
||||
let w = resizeAnchor.w
|
||||
let h = resizeAnchor.h
|
||||
if (dir.indexOf('right') !== -1) w = resizeAnchor.w + dx
|
||||
if (dir.indexOf('left') !== -1) w = resizeAnchor.w - dx
|
||||
if (dir.indexOf('bottom') !== -1) h = resizeAnchor.h + dy
|
||||
if (dir.indexOf('top') !== -1) h = resizeAnchor.h - dy
|
||||
// fixedAxis: меняем только ПЕРПЕНДИКУЛЯРНУЮ оси (в доке растянута противоположная
|
||||
// сжатой). fixedAxis='y' (док сжал высоту) → двигаем ширину; 'x' → высоту.
|
||||
const useX = fixed === 'none' || fixed === 'y'
|
||||
const useY = fixed === 'none' || fixed === 'x'
|
||||
if (useX) {
|
||||
if (dir.indexOf('right') !== -1) w = resizeAnchor.w + dx
|
||||
if (dir.indexOf('left') !== -1) w = resizeAnchor.w - dx
|
||||
}
|
||||
if (useY) {
|
||||
if (dir.indexOf('bottom') !== -1) h = resizeAnchor.h + dy
|
||||
if (dir.indexOf('top') !== -1) h = resizeAnchor.h - dy
|
||||
}
|
||||
const size = clampWindowSize({ width: w, height: h })
|
||||
figma.ui.resize(size.width, size.height)
|
||||
lastSize = { width: size.width, height: size.height }
|
||||
@@ -198,12 +208,18 @@ export default function () {
|
||||
|
||||
// Ресайз окна плагина (кастомные ручки по периметру -> RESIZE_WINDOW по pointerup)
|
||||
on('RESIZE_WINDOW', function (windowSize: { width: number; height: number }) {
|
||||
const wasFixedAxis = resizeAnchor != null ? (resizeAnchor.fixedAxis || 'none') : 'none'
|
||||
resizeAnchor = null
|
||||
const { width, height } = clampWindowSize(windowSize)
|
||||
lastSize = { width, height }
|
||||
figma.ui.resize(width, height)
|
||||
// Если окно задокено/в центре — переприжимаем ПОЗИЦИЮ к краю/центру, но размер
|
||||
// НЕ трогаем (canonical=false), чтобы ручной ресайз не сбрасывался на отпускании.
|
||||
// ИСКЛЮЧЕНИЕ: single-axis ресайз из дока (fixedAxis!='none') — окно "выезжает" из
|
||||
// дока, не ре-докаем (иначе вернётся к канону и сбросит ручной размер).
|
||||
if (wasFixedAxis !== 'none') {
|
||||
return
|
||||
}
|
||||
if (dockedPosition !== null) {
|
||||
applyDock(dockedPosition, width, height, false)
|
||||
} else if (centerLocked) {
|
||||
@@ -212,6 +228,14 @@ export default function () {
|
||||
}
|
||||
})
|
||||
|
||||
// Выход из дока (ручной single-axis ресайз угла через UI): сбрасываем док-состояние,
|
||||
// чтобы поллинг не переприжимал и RESIZE_WINDOW не ре-докал.
|
||||
on('UNDOCK', function () {
|
||||
dockedPosition = null
|
||||
centerLocked = false
|
||||
lastBoundsKey = ''
|
||||
})
|
||||
|
||||
// Эмуляция докинга: растянуть окно на зону и прижать к её краю (9 позиций).
|
||||
// Двухэтапно, чтобы позиция считалась от РЕАЛЬНОГО размера, который Figma
|
||||
// фактически отрисовала (она клампит resize до своего лимита при видимом UI).
|
||||
|
||||
+67
-7
@@ -50,7 +50,14 @@ function Section (props: { title: string; children: preact.ComponentChildren })
|
||||
|
||||
function Plugin () {
|
||||
const [currentWindowSize, setCurrentWindowSize] = useState(MIN_WINDOW_SIZE)
|
||||
const resizeRef = useRef({ active: false, dir: '', startX: 0, startY: 0, startW: 0, startH: 0 })
|
||||
const resizeRef = useRef({ active: false, dir: '', startX: 0, startY: 0, startW: 0, startH: 0, fixedAxis: 'none' as 'none' | 'x' | 'y' })
|
||||
// Текущая док-позиция (для детекта "окно в доке у края" в UI). Центр и углы НЕ пишем —
|
||||
// они не считаются доком (окно там просто лежит, не растянуто).
|
||||
const dockedPosRef = useRef<DockPosition | null>(null)
|
||||
// Флаг: ресайз выводит окно из дока (на отпускании не ре-докать, очистить dock-состояние).
|
||||
const exitingDockRef = useRef(false)
|
||||
// Та же величина, что DOCK_EDGE_SIZE в main.ts — признак "сжатая ось дока".
|
||||
const DOCK_EDGE = 320
|
||||
const [windowInfo, setWindowInfo] = useState<{
|
||||
x: number; y: number; width: number; height: number
|
||||
zoom: number
|
||||
@@ -65,18 +72,19 @@ function Plugin () {
|
||||
// Кастомный ресайз по ВСЕМУ периметру (8 невидимых ручек: 4 края + 4 угла).
|
||||
// Левый/верхний края двигают окно (main двигает через reposition), правый/нижний — только resize.
|
||||
// Live-обновление: RESIZE_WINDOW_DRAG во время drag'а, финальный RESIZE_WINDOW по pointerup.
|
||||
function startResize (e: PointerEvent, dir: string) {
|
||||
function startResize (e: PointerEvent, dir: string, fixedAxis: 'none' | 'x' | 'y' = 'none') {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
resizeRef.current.active = true
|
||||
resizeRef.current.dir = dir
|
||||
resizeRef.current.fixedAxis = fixedAxis
|
||||
resizeRef.current.startX = e.clientX
|
||||
resizeRef.current.startY = e.clientY
|
||||
resizeRef.current.startW = window.innerWidth
|
||||
resizeRef.current.startH = window.innerHeight
|
||||
// Сообщаем main зафиксировать якорь (старт-позиция + старт-размер) ДО первого движения.
|
||||
// Размер берём из window (UI-поток), т.к. в main-потоке window нет.
|
||||
emit('RESIZE_START', { dir: dir, startW: window.innerWidth, startH: window.innerHeight })
|
||||
emit('RESIZE_START', { dir: dir, startW: window.innerWidth, startH: window.innerHeight, fixedAxis: fixedAxis })
|
||||
}
|
||||
|
||||
function onPointerMove (e: PointerEvent) {
|
||||
@@ -84,7 +92,7 @@ function Plugin () {
|
||||
const dir = resizeRef.current.dir
|
||||
const dx = e.clientX - resizeRef.current.startX
|
||||
const dy = e.clientY - resizeRef.current.startY
|
||||
emit('RESIZE_WINDOW_DRAG', { dx: dx, dy: dy, dir: dir })
|
||||
emit('RESIZE_WINDOW_DRAG', { dx: dx, dy: dy, dir: dir, fixedAxis: resizeRef.current.fixedAxis })
|
||||
}
|
||||
|
||||
function onPointerUpResize () {
|
||||
@@ -94,14 +102,36 @@ function Plugin () {
|
||||
const h = window.innerHeight
|
||||
setCurrentWindowSize({ width: w, height: h })
|
||||
emit('RESIZE_WINDOW', { width: w, height: h })
|
||||
// Если ресайз выводил окно из дока — больше не держим док-состояние (чтобы
|
||||
// поллинг не переприжимал и RESIZE_WINDOW не ре-докал). Обычный (не из дока) ресайз
|
||||
// сам держит centerLocked/dock через applyDock в main.
|
||||
if (exitingDockRef.current) {
|
||||
exitingDockRef.current = false
|
||||
dockedPosRef.current = null
|
||||
emit('UNDOCK', {})
|
||||
}
|
||||
refreshInfo()
|
||||
}
|
||||
|
||||
// Клик-или-драг на периметральной ручке:
|
||||
// - смещение курсора > CLICK_THRESHOLD px → ресайз (startResize + live drag через глобальный onPointerMove)
|
||||
// - иначе (чистый клик) → док в соответствующую позицию (dockWindow)
|
||||
// - иначе (чистый клик) → зависит от позиции:
|
||||
// * угол (top-left/right, bottom-left/right):
|
||||
// - окно НЕ в доке у края → телепорт в угол (dockWindow)
|
||||
// - окно В ДОКЕ у края (одна ось сжата до DOCK_EDGE) → старт single-axis live-ресайза
|
||||
// сжатой оси (окно "выезжает" в угол, выход из дока), dir = угол, fixedAxis = сжатая ось
|
||||
// * край (top/bottom-center, center-left/right) → телепорт в край (dockWindow)
|
||||
// Центр НЕ входит в периметр — только кнопкой.
|
||||
const CLICK_THRESHOLD = 4
|
||||
const isCornerPos = (p: DockPosition) =>
|
||||
p === 'top-left' || p === 'top-right' || p === 'bottom-left' || p === 'bottom-right'
|
||||
// Окно "в доке у края" = одна ось сжата до DOCK_EDGE, другая существенно больше.
|
||||
function isEdgeDocked () {
|
||||
const w = window.innerWidth
|
||||
const h = window.innerHeight
|
||||
return (w <= DOCK_EDGE + 4 && h > DOCK_EDGE + 4) || (h <= DOCK_EDGE + 4 && w > DOCK_EDGE + 4)
|
||||
}
|
||||
|
||||
function onHandleDown (e: PointerEvent, dir: string, dockPos: DockPosition) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@@ -114,7 +144,7 @@ function Plugin () {
|
||||
const dy = ev.clientY - startY
|
||||
if (Math.sqrt(dx * dx + dy * dy) < CLICK_THRESHOLD) return
|
||||
started = true
|
||||
startResize(ev, dir)
|
||||
startResize(ev, dir, 'none')
|
||||
// Считаем ресайз от исходной точки (до порога), а не от текущей (>порога).
|
||||
resizeRef.current.startX = startX
|
||||
resizeRef.current.startY = startY
|
||||
@@ -125,7 +155,31 @@ function Plugin () {
|
||||
window.removeEventListener('pointermove', move)
|
||||
window.removeEventListener('pointerup', up)
|
||||
if (started) onPointerUpResize()
|
||||
else dockWindow(dockPos)
|
||||
else {
|
||||
// Чистый клик — поведение зависит от позиции ручки и состояния дока.
|
||||
if (isCornerPos(dockPos)) {
|
||||
if (isEdgeDocked()) {
|
||||
// Окно в доке у края: клик по смежному углу = single-axis ресайз сжатой оси.
|
||||
// Сжатая ось: верх/низ-док (h сжата) → фикс Y (меняем ширину);
|
||||
// лево/право-док (w сжата) → фикс X (меняем высоту).
|
||||
const fixedAxis = window.innerHeight <= DOCK_EDGE + 4 ? 'y' : 'x'
|
||||
exitingDockRef.current = true
|
||||
startResize(e, dir, fixedAxis) // active=true + emit RESIZE_START (старт от точки клика)
|
||||
// Финализатор на СЛЕДУЮЩИЙ pointerup (live-ресайз ведёт глобальный onPointerMove).
|
||||
const finish = function () {
|
||||
window.removeEventListener('pointerup', finish)
|
||||
onPointerUpResize()
|
||||
}
|
||||
window.addEventListener('pointerup', finish)
|
||||
return
|
||||
}
|
||||
// Не в доке: телепорт в угол.
|
||||
dockWindow(dockPos)
|
||||
} else {
|
||||
// Краевая ручка: телепорт в край.
|
||||
dockWindow(dockPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener('pointermove', move)
|
||||
window.addEventListener('pointerup', up)
|
||||
@@ -277,6 +331,12 @@ function Plugin () {
|
||||
| 'bottom-left' | 'bottom-center' | 'bottom-right'
|
||||
|
||||
function dockWindow (position: DockPosition) {
|
||||
// Запоминаем док-позицию только для КРАЁВ (растянуто у края). Углы и центр — не док
|
||||
// (окно там просто лежит), поэтому dockedPosRef оставляем как есть / не трогаем.
|
||||
const isEdge = position === 'top-center' || position === 'bottom-center' ||
|
||||
position === 'center-left' || position === 'center-right'
|
||||
if (isEdge) dockedPosRef.current = position
|
||||
else if (position !== 'center') dockedPosRef.current = null
|
||||
// Этап 1: просим main растянуть (попытка до канона) и запомнить режим.
|
||||
// main НЕ позиционирует сразу — позиция считается на этапе 2 от РЕАЛЬНОГО
|
||||
// размера, который Figma отрисует после клампа (видимый UI режет запрос).
|
||||
|
||||
Reference in new issue
Block a user