make rotate/scale menus draggable
This commit is contained in:
parent
be9333c274
commit
05c69f32cd
3 changed files with 206 additions and 12 deletions
|
|
@ -218,6 +218,117 @@ function ui_sync() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup_keybd_nav() {
|
function setup_keybd_nav() {
|
||||||
|
const panelPosKeys = {
|
||||||
|
'panel-rotate': 'win.roate',
|
||||||
|
'panel-scale': 'win.scale'
|
||||||
|
};
|
||||||
|
|
||||||
|
function parsePanelPos(key) {
|
||||||
|
if (!key) return null;
|
||||||
|
const raw = api.local.get(key);
|
||||||
|
if (!raw) return null;
|
||||||
|
if (typeof raw === 'object' && raw.left !== undefined && raw.top !== undefined) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
if (parsed && parsed.left !== undefined && parsed.top !== undefined) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore malformed stored values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function placePanel(panel, pos) {
|
||||||
|
if (!panel || !pos) return;
|
||||||
|
panel.style.left = `${Math.round(pos.left)}px`;
|
||||||
|
panel.style.top = `${Math.round(pos.top)}px`;
|
||||||
|
panel.style.right = 'auto';
|
||||||
|
panel.style.bottom = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
function placePanelDefault(panel) {
|
||||||
|
if (!panel) return;
|
||||||
|
const rect = panel.getBoundingClientRect();
|
||||||
|
const width = rect.width || 260;
|
||||||
|
const modeToolsRect = $('mode-tools')?.getBoundingClientRect();
|
||||||
|
const baseTop = modeToolsRect ? (modeToolsRect.bottom + 10) : 92;
|
||||||
|
const minPad = 8;
|
||||||
|
const maxLeft = Math.max(minPad, window.innerWidth - width - minPad);
|
||||||
|
const left = Math.min(maxLeft, Math.max(minPad, (window.innerWidth - width) / 2));
|
||||||
|
placePanel(panel, { left, top: baseTop });
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSelectionPanel(pid) {
|
||||||
|
const panel = $(pid);
|
||||||
|
if (!panel) return;
|
||||||
|
panel.classList.remove('hide');
|
||||||
|
const key = panelPosKeys[pid];
|
||||||
|
const saved = parsePanelPos(key);
|
||||||
|
if (saved) {
|
||||||
|
placePanel(panel, saved);
|
||||||
|
} else {
|
||||||
|
placePanelDefault(panel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideSelectionPanel(pid) {
|
||||||
|
const panel = $(pid);
|
||||||
|
if (!panel) return;
|
||||||
|
panel.classList.add('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelectionPanel(pid) {
|
||||||
|
const el = $(pid);
|
||||||
|
if (!el) return;
|
||||||
|
if (el.classList.contains('hide')) {
|
||||||
|
showSelectionPanel(pid);
|
||||||
|
} else {
|
||||||
|
hideSelectionPanel(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makePanelDraggable(panelId, handleId, storageKey) {
|
||||||
|
const panel = $(panelId);
|
||||||
|
const handle = $(handleId);
|
||||||
|
if (!panel || !handle) return;
|
||||||
|
let sx = 0, sy = 0, px = 0, py = 0, dragging = false;
|
||||||
|
handle.onmousedown = (ev) => {
|
||||||
|
if (ev.button !== 0) return;
|
||||||
|
dragging = true;
|
||||||
|
sx = ev.clientX;
|
||||||
|
sy = ev.clientY;
|
||||||
|
const rect = panel.getBoundingClientRect();
|
||||||
|
px = rect.left;
|
||||||
|
py = rect.top;
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
};
|
||||||
|
document.addEventListener('mousemove', (ev) => {
|
||||||
|
if (!dragging) return;
|
||||||
|
const nx = px + (ev.clientX - sx);
|
||||||
|
const ny = py + (ev.clientY - sy);
|
||||||
|
panel.style.left = `${Math.round(nx)}px`;
|
||||||
|
panel.style.top = `${Math.round(ny)}px`;
|
||||||
|
panel.style.right = 'auto';
|
||||||
|
panel.style.bottom = 'auto';
|
||||||
|
});
|
||||||
|
document.addEventListener('mouseup', () => {
|
||||||
|
if (dragging && storageKey) {
|
||||||
|
const rect = panel.getBoundingClientRect();
|
||||||
|
api.local.set(storageKey, JSON.stringify({
|
||||||
|
left: Math.round(rect.left),
|
||||||
|
top: Math.round(rect.top)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
dragging = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// bind interface action elements
|
// bind interface action elements
|
||||||
ui.acct.help.onclick = (ev) => { ev.stopPropagation(); api.help.show() };
|
ui.acct.help.onclick = (ev) => { ev.stopPropagation(); api.help.show() };
|
||||||
ui.acct.don8.onclick = (ev) => { ev.stopPropagation(); api.modal.show('don8') };
|
ui.acct.don8.onclick = (ev) => { ev.stopPropagation(); api.modal.show('don8') };
|
||||||
|
|
@ -296,6 +407,12 @@ function setup_keybd_nav() {
|
||||||
$('mesh-split').onclick = selection.isolateBodies;
|
$('mesh-split').onclick = selection.isolateBodies;
|
||||||
$('context-duplicate').onclick = selection.duplicate;
|
$('context-duplicate').onclick = selection.duplicate;
|
||||||
$('context-mirror').onclick = selection.mirror;
|
$('context-mirror').onclick = selection.mirror;
|
||||||
|
$('context-rotate-panel').onclick = () => toggleSelectionPanel('panel-rotate');
|
||||||
|
$('context-scale-panel').onclick = () => toggleSelectionPanel('panel-scale');
|
||||||
|
$('panel-rotate-close').onmousedown = (ev) => { ev.stopPropagation(); };
|
||||||
|
$('panel-scale-close').onmousedown = (ev) => { ev.stopPropagation(); };
|
||||||
|
$('panel-rotate-close').onclick = (ev) => { ev.stopPropagation(); hideSelectionPanel('panel-rotate'); };
|
||||||
|
$('panel-scale-close').onclick = (ev) => { ev.stopPropagation(); hideSelectionPanel('panel-scale'); };
|
||||||
$('context-layflat').onclick = view_tools.startLayFlat;
|
$('context-layflat').onclick = view_tools.startLayFlat;
|
||||||
$('context-lefty').onclick = view_tools.startLeftAlign;
|
$('context-lefty').onclick = view_tools.startLeftAlign;
|
||||||
$('context-setfocus').onclick = () => {
|
$('context-setfocus').onclick = () => {
|
||||||
|
|
@ -305,6 +422,9 @@ function setup_keybd_nav() {
|
||||||
$('view-fit').onclick = api.const.SPACE.view.fit;
|
$('view-fit').onclick = api.const.SPACE.view.fit;
|
||||||
$('wassup').onmouseover = () => { $('suppopp').classList.remove('hide') };
|
$('wassup').onmouseover = () => { $('suppopp').classList.remove('hide') };
|
||||||
|
|
||||||
|
makePanelDraggable('panel-rotate', 'panel-rotate-head', 'win.roate');
|
||||||
|
makePanelDraggable('panel-scale', 'panel-scale-head', 'win.scale');
|
||||||
|
|
||||||
// enable modal hiding
|
// enable modal hiding
|
||||||
$('mod-x').onclick = api.modal.hide;
|
$('mod-x').onclick = api.modal.hide;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,18 @@ function toolbarToolNozzle() {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toolbarToolRotate(actions) {
|
function rotatePanel(actions) {
|
||||||
return span({ id: 'tool-rotate' }, [
|
return div({ id: 'panel-rotate', class: 'selection-panel hide' }, [
|
||||||
i({ class: 'fas fa-rotate-right', title: 'rotate selection' }),
|
div({ id: 'panel-rotate-head', class: 'selection-panel-head' }, [
|
||||||
div({ id: 'ft-rotate', class: 'grid pop' }, [
|
div({ class: 'selection-panel-head-title' }, [
|
||||||
|
i({ class: 'fas fa-rotate-right' }),
|
||||||
|
label({ _: 'Rotate' })
|
||||||
|
]),
|
||||||
|
button({ id: 'panel-rotate-close', class: 'selection-panel-close', title: 'close' }, [
|
||||||
|
i({ class: 'fas fa-times' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
div({ id: 'ft-rotate', class: 'grid selection-panel-body' }, [
|
||||||
div({ id: 'rot_x_lt', ...on(actions, 'rot_x_lt') }, icon('fas fa-chevron-left')),
|
div({ id: 'rot_x_lt', ...on(actions, 'rot_x_lt') }, icon('fas fa-chevron-left')),
|
||||||
label({ _: 'X' }),
|
label({ _: 'X' }),
|
||||||
div({ id: 'rot_x_gt', ...on(actions, 'rot_x_gt') }, icon('fas fa-chevron-right')),
|
div({ id: 'rot_x_gt', ...on(actions, 'rot_x_gt') }, icon('fas fa-chevron-right')),
|
||||||
|
|
@ -81,10 +89,18 @@ function toolbarToolRotate(actions) {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toolbarToolScale(actions) {
|
function scalePanel(actions) {
|
||||||
return span({ id: 'tool-scale' }, [
|
return div({ id: 'panel-scale', class: 'selection-panel hide' }, [
|
||||||
i({ class: 'fas fa-expand', title: 'scale or resize selection' }),
|
div({ id: 'panel-scale-head', class: 'selection-panel-head' }, [
|
||||||
div({ id: 'ft-scale', class: 'grid pop' }, [
|
div({ class: 'selection-panel-head-title' }, [
|
||||||
|
i({ class: 'fas fa-expand' }),
|
||||||
|
label({ _: 'Scale / Size' })
|
||||||
|
]),
|
||||||
|
button({ id: 'panel-scale-close', class: 'selection-panel-close', title: 'close' }, [
|
||||||
|
i({ class: 'fas fa-times' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
div({ id: 'ft-scale', class: 'grid selection-panel-body' }, [
|
||||||
div([label({ _: 'X' }), input({ id: 'lock_x', type: 'checkbox', _checked: true })]),
|
div([label({ _: 'X' }), input({ id: 'lock_x', type: 'checkbox', _checked: true })]),
|
||||||
div([label({ _: 'Y' }), input({ id: 'lock_y', type: 'checkbox', _checked: true })]),
|
div([label({ _: 'Y' }), input({ id: 'lock_y', type: 'checkbox', _checked: true })]),
|
||||||
div([label({ _: 'Z' }), input({ id: 'lock_z', type: 'checkbox', _checked: true })]),
|
div([label({ _: 'Z' }), input({ id: 'lock_z', type: 'checkbox', _checked: true })]),
|
||||||
|
|
@ -157,15 +173,16 @@ function content(actions) {
|
||||||
menuItem(actions, { id: 'context-mirror', lk: 'rc_mirr', text: 'mirror', iconClass: 'fas fa-arrows-left-right-to-line' }),
|
menuItem(actions, { id: 'context-mirror', lk: 'rc_mirr', text: 'mirror', iconClass: 'fas fa-arrows-left-right-to-line' }),
|
||||||
menuItem(actions, { id: 'context-duplicate', lk: 'rc_dupl', text: 'duplicate', iconClass: 'fas fa-copy' }),
|
menuItem(actions, { id: 'context-duplicate', lk: 'rc_dupl', text: 'duplicate', iconClass: 'fas fa-copy' }),
|
||||||
hr(),
|
hr(),
|
||||||
|
menuItem(actions, { id: 'context-rotate-panel', text: 'rotate', iconClass: 'fas fa-rotate-right' }),
|
||||||
|
menuItem(actions, { id: 'context-scale-panel', text: 'scale / size', iconClass: 'fas fa-expand' }),
|
||||||
|
hr(),
|
||||||
menuItem(actions, { id: 'mesh-merge', lk: 'rc_merg', text: 'merge meshes' }),
|
menuItem(actions, { id: 'mesh-merge', lk: 'rc_merg', text: 'merge meshes' }),
|
||||||
menuItem(actions, { id: 'mesh-split', lk: 'rc_splt', text: 'isolate meshes' }),
|
menuItem(actions, { id: 'mesh-split', lk: 'rc_splt', text: 'isolate meshes' }),
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
div({ class: 'menubar-separator' }),
|
div({ class: 'menubar-separator' }),
|
||||||
div({ class: 'f-row top-menu' }, [
|
div({ class: 'f-row top-menu' }, [
|
||||||
toolbarToolNozzle(),
|
toolbarToolNozzle()
|
||||||
toolbarToolRotate(actions),
|
|
||||||
toolbarToolScale(actions)
|
|
||||||
]),
|
]),
|
||||||
div({ class: 'grow' }),
|
div({ class: 'grow' }),
|
||||||
topMenu(actions, {
|
topMenu(actions, {
|
||||||
|
|
@ -209,7 +226,9 @@ function content(actions) {
|
||||||
menuItem(actions, { children: label({ id: 'lset-pt', _: 'português' }) })
|
menuItem(actions, { children: label({ id: 'lset-pt', _: 'português' }) })
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
])
|
]),
|
||||||
|
rotatePanel(actions),
|
||||||
|
scalePanel(actions)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1032,6 +1032,61 @@ details[open] summary::after {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* draggable selection transform panels */
|
||||||
|
.selection-panel {
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 220;
|
||||||
|
min-width: 230px;
|
||||||
|
border: 1px solid var(--border-gray);
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--menubar-back);
|
||||||
|
color: var(--menubar-fore);
|
||||||
|
box-shadow: 0 8px 22px rgba(0,0,0,0.25);
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
#panel-rotate {
|
||||||
|
top: 92px;
|
||||||
|
right: 26px;
|
||||||
|
}
|
||||||
|
#panel-scale {
|
||||||
|
top: 270px;
|
||||||
|
right: 26px;
|
||||||
|
}
|
||||||
|
.selection-panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 7px 10px;
|
||||||
|
border-bottom: 1px solid var(--border-gray);
|
||||||
|
cursor: move;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.selection-panel-head-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.selection-panel-head label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.selection-panel-close {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.selection-panel-close:hover {
|
||||||
|
background-color: var(--menu-hover-bg);
|
||||||
|
}
|
||||||
|
.selection-panel-body {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/** selection rotation */
|
/** selection rotation */
|
||||||
#ft-rotate {
|
#ft-rotate {
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue