mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-27 16:22:20 +01:00
Merge current dev and refresh PR #6741 from the 7011 integration
This commit is contained in:
@@ -9,7 +9,10 @@ export function createTextEditOverlay({ state, onInput, onCommit, onCancel }) {
|
||||
let input = null;
|
||||
let layer = null;
|
||||
let resizeObserver = null;
|
||||
let handle = null;
|
||||
let frame = 0;
|
||||
let appliedWidth = 0;
|
||||
let resizing = false;
|
||||
|
||||
function position() {
|
||||
if (!input || !layer || !state.mainCanvas?.isConnected) return;
|
||||
@@ -20,13 +23,27 @@ export function createTextEditOverlay({ state, onInput, onCommit, onCancel }) {
|
||||
const text = layer.text;
|
||||
input.style.left = `${canvasRect.left + offset.x * scaleX}px`;
|
||||
input.style.top = `${canvasRect.top + offset.y * scaleY}px`;
|
||||
input.style.minWidth = `${Math.max(80, text.frameWidth * scaleX)}px`;
|
||||
input.style.width = `${Math.max(80, text.frameWidth * scaleX)}px`;
|
||||
// Only rewrite the width when the model's frame width (or the zoom) changed.
|
||||
// Writing it every animation frame overwrote the native resize handle's
|
||||
// width before the ResizeObserver could report it, so the box could not be
|
||||
// resized at all.
|
||||
const targetW = Math.max(80, text.frameWidth * scaleX);
|
||||
if (!resizing && Math.abs(targetW - appliedWidth) > 0.5) {
|
||||
input.style.width = `${targetW}px`;
|
||||
appliedWidth = targetW;
|
||||
}
|
||||
input.style.minWidth = '40px';
|
||||
input.style.minHeight = `${Math.max(36, layer.canvas.height * scaleY)}px`;
|
||||
input.style.font = textFont({ ...text, fontSize: text.fontSize * scaleY });
|
||||
input.style.letterSpacing = `${text.letterSpacing * scaleX}px`;
|
||||
input.style.textAlign = text.align;
|
||||
input.style.color = text.color;
|
||||
if (handle) {
|
||||
const rect = input.getBoundingClientRect();
|
||||
handle.style.left = `${rect.right - 8}px`;
|
||||
handle.style.top = `${rect.bottom - 8}px`;
|
||||
handle.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function loop() {
|
||||
@@ -39,6 +56,9 @@ export function createTextEditOverlay({ state, onInput, onCommit, onCancel }) {
|
||||
const value = input.value;
|
||||
resizeObserver?.disconnect();
|
||||
cancelAnimationFrame(frame);
|
||||
handle?.remove();
|
||||
handle = null;
|
||||
resizing = false;
|
||||
input.remove();
|
||||
input = null;
|
||||
const target = layer;
|
||||
@@ -49,6 +69,7 @@ export function createTextEditOverlay({ state, onInput, onCommit, onCancel }) {
|
||||
function open(target, { selectAll = false } = {}) {
|
||||
close(true);
|
||||
layer = target;
|
||||
appliedWidth = 0;
|
||||
input = document.createElement('textarea');
|
||||
input.className = 'ge-direct-text-editor';
|
||||
input.value = target.text.content;
|
||||
@@ -67,9 +88,51 @@ export function createTextEditOverlay({ state, onInput, onCommit, onCancel }) {
|
||||
});
|
||||
let ready = false;
|
||||
input.addEventListener('blur', () => setTimeout(() => {
|
||||
if (ready && input && document.activeElement !== input) close(true);
|
||||
if (ready && input && !resizing && document.activeElement !== input) close(true);
|
||||
}, 0));
|
||||
document.body.appendChild(input);
|
||||
// A visible grab square in the corner: the native textarea resize grip is
|
||||
// invisible in this theme and only a few pixels wide, so most drags missed
|
||||
// it. This handle also survives the animation-frame positioning pass.
|
||||
handle = document.createElement('div');
|
||||
handle.className = 'ge-direct-text-handle';
|
||||
handle.title = 'Drag to resize';
|
||||
handle.setAttribute('aria-hidden', 'true');
|
||||
handle.style.display = 'none';
|
||||
// Same dot-grip language as the layer-row drag handles, arranged as a
|
||||
// corner wedge so it reads as "resize from this corner".
|
||||
handle.innerHTML = '<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><circle cx="4" cy="9" r="1.1"/><circle cx="7" cy="9" r="1.1"/><circle cx="10" cy="9" r="1.1"/><circle cx="7" cy="6" r="1.1"/><circle cx="10" cy="6" r="1.1"/><circle cx="10" cy="3" r="1.1"/></svg>';
|
||||
let dragStart = null;
|
||||
handle.addEventListener('pointerdown', (event) => {
|
||||
if (!input || !layer || event.button !== 0) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const canvasRect = state.mainCanvas.getBoundingClientRect();
|
||||
const scale = canvasRect.width / Math.max(1, state.mainCanvas.width);
|
||||
const rect = input.getBoundingClientRect();
|
||||
dragStart = { x: event.clientX, y: event.clientY, w: rect.width, h: rect.height, scale };
|
||||
resizing = true;
|
||||
handle.setPointerCapture?.(event.pointerId);
|
||||
});
|
||||
handle.addEventListener('pointermove', (event) => {
|
||||
if (!dragStart || !input || !layer) return;
|
||||
const w = Math.max(80, Math.round(dragStart.w + (event.clientX - dragStart.x)));
|
||||
const h = Math.max(36, Math.round(dragStart.h + (event.clientY - dragStart.y)));
|
||||
input.style.width = `${w}px`;
|
||||
input.style.height = `${h}px`;
|
||||
appliedWidth = w;
|
||||
onInput?.(layer, input.value, input, w / Math.max(0.01, dragStart.scale));
|
||||
});
|
||||
const endDrag = () => {
|
||||
if (!dragStart) return;
|
||||
dragStart = null;
|
||||
resizing = false;
|
||||
if (input) appliedWidth = input.getBoundingClientRect().width;
|
||||
input?.focus({ preventScroll: true });
|
||||
};
|
||||
handle.addEventListener('pointerup', endDrag);
|
||||
handle.addEventListener('pointercancel', endDrag);
|
||||
document.body.appendChild(handle);
|
||||
resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => {
|
||||
if (!input || !layer) return;
|
||||
const canvasRect = state.mainCanvas.getBoundingClientRect();
|
||||
|
||||
Reference in New Issue
Block a user