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() {
|
||||
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
|
||||
ui.acct.help.onclick = (ev) => { ev.stopPropagation(); api.help.show() };
|
||||
ui.acct.don8.onclick = (ev) => { ev.stopPropagation(); api.modal.show('don8') };
|
||||
|
|
@ -296,6 +407,12 @@ function setup_keybd_nav() {
|
|||
$('mesh-split').onclick = selection.isolateBodies;
|
||||
$('context-duplicate').onclick = selection.duplicate;
|
||||
$('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-lefty').onclick = view_tools.startLeftAlign;
|
||||
$('context-setfocus').onclick = () => {
|
||||
|
|
@ -305,6 +422,9 @@ function setup_keybd_nav() {
|
|||
$('view-fit').onclick = api.const.SPACE.view.fit;
|
||||
$('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
|
||||
$('mod-x').onclick = api.modal.hide;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,10 +58,18 @@ function toolbarToolNozzle() {
|
|||
]);
|
||||
}
|
||||
|
||||
function toolbarToolRotate(actions) {
|
||||
return span({ id: 'tool-rotate' }, [
|
||||
i({ class: 'fas fa-rotate-right', title: 'rotate selection' }),
|
||||
div({ id: 'ft-rotate', class: 'grid pop' }, [
|
||||
function rotatePanel(actions) {
|
||||
return div({ id: 'panel-rotate', class: 'selection-panel hide' }, [
|
||||
div({ id: 'panel-rotate-head', class: 'selection-panel-head' }, [
|
||||
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')),
|
||||
label({ _: 'X' }),
|
||||
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) {
|
||||
return span({ id: 'tool-scale' }, [
|
||||
i({ class: 'fas fa-expand', title: 'scale or resize selection' }),
|
||||
div({ id: 'ft-scale', class: 'grid pop' }, [
|
||||
function scalePanel(actions) {
|
||||
return div({ id: 'panel-scale', class: 'selection-panel hide' }, [
|
||||
div({ id: 'panel-scale-head', class: 'selection-panel-head' }, [
|
||||
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({ _: 'Y' }), input({ id: 'lock_y', 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-duplicate', lk: 'rc_dupl', text: 'duplicate', iconClass: 'fas fa-copy' }),
|
||||
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-split', lk: 'rc_splt', text: 'isolate meshes' }),
|
||||
]
|
||||
}),
|
||||
div({ class: 'menubar-separator' }),
|
||||
div({ class: 'f-row top-menu' }, [
|
||||
toolbarToolNozzle(),
|
||||
toolbarToolRotate(actions),
|
||||
toolbarToolScale(actions)
|
||||
toolbarToolNozzle()
|
||||
]),
|
||||
div({ class: 'grow' }),
|
||||
topMenu(actions, {
|
||||
|
|
@ -209,7 +226,9 @@ function content(actions) {
|
|||
menuItem(actions, { children: label({ id: 'lset-pt', _: 'português' }) })
|
||||
]
|
||||
})
|
||||
])
|
||||
]),
|
||||
rotatePanel(actions),
|
||||
scalePanel(actions)
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1032,6 +1032,61 @@ details[open] summary::after {
|
|||
flex-direction: row;
|
||||
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 */
|
||||
#ft-rotate {
|
||||
gap: 2px;
|
||||
|
|
|
|||
Loading…
Reference in a new issue