From a38f7188ee23e2eff07fe6310426964caad4edf9 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Wed, 9 Sep 2026 18:44:30 +0200 Subject: [PATCH] feat: dock buttons stretch window to zone (edge/corner) + reposition; center keeps size --- src/main.ts | 53 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/main.ts b/src/main.ts index 5bbf155..8409b28 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,8 +9,8 @@ export default function () { let lastSize = { width: 320, height: 200 } // Текущий режим докинга. null = окно свободно (не прижато). - // Пока не null — позиция пересчитывается при каждом изменении viewport - // (через поллинг, т.к. события viewportchange в API нет), иначе + // Пока не null — состояние (размер+позиция) пересчитывается при каждом изменении + // viewport (через поллинг, т.к. события viewportchange в API нет), иначе // canvas-space-точка "уплывает" на экране при смене зума/пана. let dockedPosition: DockPosition | null = null @@ -25,36 +25,52 @@ export default function () { // верх шапки, поэтому полная высота окна = (PLUGIN_HEADER_PX + height) / zoom. const PLUGIN_HEADER_PX = 38 - // Прижать окно к краю видимой области canvas (bounds) в canvas-space. - // ВАЖНО: reposition()/getPosition() и bounds — в canvas-space (единицы документа), - // а figma.ui.resize(width,height) — в ЭКРАННЫХ пикселях. Размер окна в canvas-units =\n // width / zoom (по ширине) и (PLUGIN_HEADER_PX + height) / zoom (по высоте, т.к. окно\n // включает шапку). Без учёта шапки прижатый к низу/центру окно висит на 38px выше нужного. - function applyDock (position: DockPosition, width: number, height: number) { + // Вычислить целевое состояние окна для позиции докинга: + // имитация настоящего дока — края/углы РАСТЯГИВАЮТ окно на всю соответствующую + // сторону viewport (по ширине тянутся лево/право/углы, по высоте — верх/низ/углы), + // центр НЕ растягивает (оставляет текущий размер). Возвращает размер (экранные px) + // и позицию (canvas-space, прижатую к нужному краю). + function computeDock (position: DockPosition, currentWidth: number, currentHeight: number) { const b = figma.viewport.bounds const zoom = figma.viewport.zoom || 1 - const wCanvas = width / zoom - const hCanvas = (PLUGIN_HEADER_PX + height) / zoom + // Растягивать ли по осям: + const stretchX = position !== 'center' // всё кроме центра тянется по ширине + const stretchY = + position === 'top-left' || position === 'top-center' || position === 'top-right' || + position === 'bottom-left' || position === 'bottom-center' || position === 'bottom-right' + const targetW = stretchX ? Math.round(b.width * zoom) : currentWidth + const targetH = stretchY ? Math.round(b.height * zoom) - PLUGIN_HEADER_PX : currentHeight + // canvas-units от целевого размера + const wCanvas = targetW / zoom + const hCanvas = (PLUGIN_HEADER_PX + targetH) / zoom let x = b.x let y = b.y - // По горизонтали if (position === 'top-center' || position === 'center' || position === 'bottom-center') { x = b.x + (b.width - wCanvas) / 2 } else if (position === 'top-right' || position === 'center-right' || position === 'bottom-right') { x = b.x + b.width - wCanvas } - // По вертикали (hCanvas учитывает шапку окна) if (position === 'center-left' || position === 'center' || position === 'center-right') { y = b.y + (b.height - hCanvas) / 2 } else if (position === 'bottom-left' || position === 'bottom-center' || position === 'bottom-right') { y = b.y + b.height - hCanvas } - figma.ui.reposition(x, y) + return { width: targetW, height: targetH, x, y } } - // Поллинг viewport: держим окно прижатым к краю при любом зуме/панорамировании. + // Применить состояние докинга: ресайзим окно (растяжение) + reposition (прижатие). + // Сначала resize, иначе reposition считает позицию для старого размера. + function applyDock (position: DockPosition, width: number, height: number) { + const state = computeDock(position, width, height) + figma.ui.resize(state.width, state.height) + figma.ui.reposition(state.x, state.y) + lastSize = { width: state.width, height: state.height } + return state + } + + // Поллинг viewport: держим окно в док-состоянии (размер+позиция) при любом зуме/панораме. // События viewportchange в Figma API НЕТ (нельзя подписаться через figma.on), - // поэтому опрашиваем bounds и репозиционируем только когда он реально изменился. - // Это предотвращает "уплывание" окна на экране: reposition() работает в canvas-space, - // а bounds при зуме сжимается/растягивается — без пересчёта точка уезжает. + // поэтому опрашиваем bounds и пере-применяем док только когда он реально изменился. let lastBoundsKey = '' const dockInterval = setInterval(function () { if (dockedPosition === null) return @@ -92,15 +108,12 @@ export default function () { } }) - // Эмуляция докинга: прижать окно к краям canvas (9 позиций). + // Эмуляция докинга: растянуть окно на зону и прижать к её краю (9 позиций). on('DOCK_WINDOW', function (payload: { position: DockPosition; width: number; height: number }) { const { position } = payload - const { width, height } = clampWindowSize(payload) - lastSize = { width, height } - figma.ui.resize(width, height) dockedPosition = position lastBoundsKey = '' // форсируем пересчёт на ближайшем тике - applyDock(position, width, height) + applyDock(position, payload.width, payload.height) }) // Останавливаем поллинг при закрытии плагина, чтобы не висел в памяти.