extract keyboard handling from init
This commit is contained in:
parent
667a7fad78
commit
41edcd408b
5 changed files with 433 additions and 301 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -7,12 +7,13 @@ app.json
|
|||
build
|
||||
data
|
||||
dist
|
||||
jszip-esm.js
|
||||
logs
|
||||
mod
|
||||
node_modules
|
||||
pack
|
||||
package-lock.json
|
||||
three.js
|
||||
src/ext/jszip-esm.js
|
||||
src/ext/quickjs.js
|
||||
src/ext/three.js
|
||||
tmp
|
||||
web/boot/bundle*
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { api } from './api.js';
|
|||
import { base } from '../../geo/base.js';
|
||||
import { local as sdb } from '../../data/local.js';
|
||||
import { slider } from './slider.js';
|
||||
import { keyboard } from './keyboard.js';
|
||||
import { space } from '../../moto/space.js';
|
||||
import { LOCAL, SETUP } from './main.js';
|
||||
import { VIEWS, MODES, SEED } from './consts.js';
|
||||
|
|
@ -237,296 +238,6 @@ function onButtonClick(ev) {
|
|||
|
||||
api.event.on('click.button', onButtonClick);
|
||||
|
||||
function inputHasFocus() {
|
||||
let active = DOC.activeElement;
|
||||
return active && (active.nodeName === "INPUT" || active.nodeName === "TEXTAREA");
|
||||
}
|
||||
|
||||
function cca(c) {
|
||||
return c.charCodeAt(0);
|
||||
}
|
||||
|
||||
function keyUpHandler(evt) {
|
||||
if (api.feature.on_key) {
|
||||
if (api.feature.on_key({up:evt})) return;
|
||||
}
|
||||
for (let handler of api.feature.on_key2) {
|
||||
if (handler({up:evt})) return;
|
||||
}
|
||||
switch (evt.keyCode) {
|
||||
// escape
|
||||
case 27:
|
||||
// blur text input focus
|
||||
DOC.activeElement.blur();
|
||||
// dismiss modals
|
||||
api.modal.hide();
|
||||
// deselect widgets
|
||||
platform.deselect();
|
||||
// hide all dialogs
|
||||
api.dialog.hide();
|
||||
// cancel slicing
|
||||
api.function.cancel();
|
||||
// and send an event (used by FDM client)
|
||||
api.event.emit("key.esc");
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function keyDownHandler(evt) {
|
||||
if (api.modal.visible()) {
|
||||
return false;
|
||||
}
|
||||
if (api.feature.on_key) {
|
||||
if (api.feature.on_key({down:evt})) return;
|
||||
}
|
||||
for (let handler of api.feature.on_key2) {
|
||||
if (handler({down:evt})) return;
|
||||
}
|
||||
let move = evt.altKey ? 5 : 0,
|
||||
deg = move ? 0 : -Math.PI / (evt.shiftKey ? 36 : 2);
|
||||
switch (evt.keyCode) {
|
||||
case 8: // apple: delete/backspace
|
||||
case 46: // others: delete
|
||||
if (inputHasFocus()) return false;
|
||||
platform.delete(selection.meshes());
|
||||
evt.preventDefault();
|
||||
break;
|
||||
case 37: // left arrow
|
||||
if (inputHasFocus()) return false;
|
||||
if (deg) selection.rotate(0, 0, -deg);
|
||||
if (move > 0) selection.move(-move, 0, 0);
|
||||
evt.preventDefault();
|
||||
break;
|
||||
case 39: // right arrow
|
||||
if (inputHasFocus()) return false;
|
||||
if (deg) selection.rotate(0, 0, deg);
|
||||
if (move > 0) selection.move(move, 0, 0);
|
||||
evt.preventDefault();
|
||||
break;
|
||||
case 38: // up arrow
|
||||
if (inputHasFocus()) return false;
|
||||
if (evt.metaKey) {
|
||||
const { hi } = slider.getRange();
|
||||
return api.show.layer(hi + 1);
|
||||
}
|
||||
if (deg) selection.rotate(deg, 0, 0);
|
||||
if (move > 0) selection.move(0, move, 0);
|
||||
evt.preventDefault();
|
||||
break;
|
||||
case 40: // down arrow
|
||||
if (inputHasFocus()) return false;
|
||||
if (evt.metaKey) {
|
||||
const { hi } = slider.getRange();
|
||||
return api.show.layer(hi - 1);
|
||||
}
|
||||
if (deg) selection.rotate(-deg, 0, 0);
|
||||
if (move > 0) selection.move(0, -move, 0);
|
||||
evt.preventDefault();
|
||||
break;
|
||||
case 65: // 'a' for select all
|
||||
if (evt.metaKey || evt.ctrlKey) {
|
||||
if (inputHasFocus()) return false;
|
||||
evt.preventDefault();
|
||||
platform.deselect();
|
||||
platform.select_all();
|
||||
}
|
||||
break;
|
||||
case 83: // 's' for save workspace
|
||||
if (evt.ctrlKey) {
|
||||
evt.preventDefault();
|
||||
api.conf.save();
|
||||
console.log("settings saved");
|
||||
} else
|
||||
if (evt.metaKey) {
|
||||
evt.preventDefault();
|
||||
api.space.save();
|
||||
set_ctrl.sync.put();
|
||||
}
|
||||
break;
|
||||
case 76: // 'l' for restore workspace
|
||||
if (evt.metaKey) {
|
||||
evt.preventDefault();
|
||||
api.space.restore();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function keyHandler(evt) {
|
||||
let handled = true;
|
||||
if (api.modal.visible() || inputHasFocus()) {
|
||||
return false;
|
||||
}
|
||||
if (api.feature.on_key) {
|
||||
if (api.feature.on_key({key:evt})) return;
|
||||
}
|
||||
for (let handler of api.feature.on_key2) {
|
||||
if (handler({key:evt})) return;
|
||||
}
|
||||
if (evt.ctrlKey) {
|
||||
switch (evt.key) {
|
||||
case 'g': return api.group.merge();
|
||||
case 'u': return api.group.split();
|
||||
}
|
||||
}
|
||||
switch (evt.charCode) {
|
||||
case cca('`'): api.show.slices(0); break;
|
||||
case cca('0'): {
|
||||
const { max } = slider.getRange();
|
||||
api.show.slices(max);
|
||||
break;
|
||||
}
|
||||
case cca('1'): {
|
||||
const { max } = slider.getRange();
|
||||
api.show.slices(max/10);
|
||||
break;
|
||||
}
|
||||
case cca('2'): {
|
||||
const { max } = slider.getRange();
|
||||
api.show.slices(max*2/10);
|
||||
break;
|
||||
}
|
||||
case cca('3'): {
|
||||
const { max } = slider.getRange();
|
||||
api.show.slices(max*3/10);
|
||||
break;
|
||||
}
|
||||
case cca('4'): {
|
||||
const { max } = slider.getRange();
|
||||
api.show.slices(max*4/10);
|
||||
break;
|
||||
}
|
||||
case cca('5'): {
|
||||
const { max } = slider.getRange();
|
||||
api.show.slices(max*5/10);
|
||||
break;
|
||||
}
|
||||
case cca('6'): {
|
||||
const { max } = slider.getRange();
|
||||
api.show.slices(max*6/10);
|
||||
break;
|
||||
}
|
||||
case cca('7'): {
|
||||
const { max } = slider.getRange();
|
||||
api.show.slices(max*7/10);
|
||||
break;
|
||||
}
|
||||
case cca('8'): {
|
||||
const { max } = slider.getRange();
|
||||
api.show.slices(max*8/10);
|
||||
break;
|
||||
}
|
||||
case cca('9'): {
|
||||
const { max } = slider.getRange();
|
||||
api.show.slices(max*9/10);
|
||||
break;
|
||||
}
|
||||
case cca('?'):
|
||||
api.help.show();
|
||||
break;
|
||||
case cca('Z'): // reset stored state
|
||||
uc.confirm('clear all settings and preferences?').then(yes => {
|
||||
if (yes) {
|
||||
sdb.clear();
|
||||
WIN.location.reload();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case cca('C'): // refresh catalog
|
||||
catalog.refresh();
|
||||
break;
|
||||
case cca('i'): // file import
|
||||
api.event.import();
|
||||
break;
|
||||
case cca('S'): // slice
|
||||
case cca('s'): // slice
|
||||
api.function.slice();
|
||||
break;
|
||||
case cca('P'): // prepare
|
||||
case cca('p'): // prepare
|
||||
if (api.mode.get() !== 'SLA') {
|
||||
// hidden in SLA mode
|
||||
api.function.prepare();
|
||||
}
|
||||
break;
|
||||
case cca('X'): // export
|
||||
case cca('x'): // export
|
||||
api.function.export();
|
||||
break;
|
||||
case cca('g'): // CAM animate
|
||||
api.function.animate();
|
||||
break;
|
||||
case cca('O'): // manual rotation
|
||||
rotateInputSelection();
|
||||
break;
|
||||
case cca('r'): // recent files
|
||||
api.modal.show('files');
|
||||
break;
|
||||
case cca('q'): // preferences
|
||||
api.modal.show('prefs');
|
||||
break;
|
||||
case cca('l'): // device
|
||||
settingsLoad();
|
||||
break;
|
||||
case cca('e'): // device
|
||||
api.show.devices();
|
||||
break;
|
||||
case cca('o'): // tools
|
||||
api.show.tools();
|
||||
break;
|
||||
case cca('c'): // local devices
|
||||
api.show.local();
|
||||
break;
|
||||
case cca('v'): // toggle single slice view mode
|
||||
if (api.view.get() === VIEWS.ARRANGE) {
|
||||
api.space.set_focus(selection.widgets());
|
||||
}
|
||||
const { lo, hi } = slider.getRange();
|
||||
if (hi === lo) {
|
||||
slider.setRange(0, hi);
|
||||
} else {
|
||||
slider.setRange(hi, hi);
|
||||
}
|
||||
api.show.slices();
|
||||
break;
|
||||
case cca('d'): // duplicate object
|
||||
duplicateSelection();
|
||||
break;
|
||||
case cca('m'): // mirror object
|
||||
mirrorSelection();
|
||||
break;
|
||||
case cca('a'):
|
||||
if (api.view.get() === VIEWS.ARRANGE) {
|
||||
// auto arrange items on platform
|
||||
platform.layout();
|
||||
if (!api.conf.get().controller.spaceRandoX) {
|
||||
api.space.set_focus(selection.widgets());
|
||||
}
|
||||
} else {
|
||||
// go to arrange view
|
||||
api.view.set(VIEWS.ARRANGE);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
api.event.emit('keypress', evt);
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
if (handled) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function duplicateSelection() {
|
||||
selection.duplicate();
|
||||
}
|
||||
|
||||
function mirrorSelection() {
|
||||
selection.mirror();
|
||||
}
|
||||
|
||||
function keys(o) {
|
||||
let key, list = [];
|
||||
|
|
@ -1154,7 +865,8 @@ function init_one() {
|
|||
tracker: tracker,
|
||||
mobile: space.info.mob,
|
||||
onLayerChange: (hi, lo) => {
|
||||
api.show.layer(hi, lo);
|
||||
// Visualization is handled by the code that calls setRange()
|
||||
// Don't call api.show.layer here as it creates circular dependency
|
||||
},
|
||||
onStackUpdate: (lo, hi) => {
|
||||
STACKS.setRange(lo, hi);
|
||||
|
|
@ -1167,6 +879,27 @@ function init_one() {
|
|||
// expose slider to API
|
||||
api.slider = slider;
|
||||
|
||||
// initialize keyboard controller
|
||||
keyboard.init({
|
||||
api,
|
||||
platform,
|
||||
selection,
|
||||
slider,
|
||||
space,
|
||||
catalog,
|
||||
sdb,
|
||||
uc,
|
||||
setCtrl: set_ctrl,
|
||||
VIEWS,
|
||||
DOC,
|
||||
WIN,
|
||||
rotateInputSelection,
|
||||
settingsLoad
|
||||
});
|
||||
|
||||
// expose keyboard to API
|
||||
api.keyboard = keyboard;
|
||||
|
||||
// add mobile class to body if needed
|
||||
if (space.info.mob) {
|
||||
DOC.body.classList.add('mobile');
|
||||
|
|
@ -1225,9 +958,6 @@ function init_one() {
|
|||
};
|
||||
|
||||
space.event.addHandlers(self, [
|
||||
'keyup', keyUpHandler,
|
||||
'keydown', keyDownHandler,
|
||||
'keypress', keyHandler,
|
||||
'dragover', dragOverHandler,
|
||||
'dragleave', dragLeave,
|
||||
'drop', dropHandler
|
||||
|
|
@ -1704,8 +1434,8 @@ function init_two() {
|
|||
$('mesh-export-obj').onclick = () => { objectsExport('obj') };
|
||||
$('mesh-merge').onclick = selection.merge;
|
||||
$('mesh-split').onclick = selection.isolateBodies;
|
||||
$('context-duplicate').onclick = duplicateSelection;
|
||||
$('context-mirror').onclick = mirrorSelection;
|
||||
$('context-duplicate').onclick = selection.duplicate;
|
||||
$('context-mirror').onclick = selection.mirror;
|
||||
$('context-layflat').onclick = () => { api.event.emit("tool.mesh.lay-flat") };
|
||||
$('context-lefty').onclick = () => { api.event.emit("tool.mesh.lefty") };
|
||||
$('context-setfocus').onclick = () => {
|
||||
|
|
|
|||
398
src/kiri/core/keyboard.js
Normal file
398
src/kiri/core/keyboard.js
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
/**
|
||||
* Keyboard Controller (Singleton)
|
||||
* Manages keyboard event handling and shortcuts
|
||||
*/
|
||||
|
||||
class KeyboardControl {
|
||||
#api;
|
||||
#platform;
|
||||
#selection;
|
||||
#slider;
|
||||
#space;
|
||||
#catalog;
|
||||
#sdb;
|
||||
#uc;
|
||||
#setCtrl;
|
||||
#VIEWS;
|
||||
#DOC;
|
||||
#WIN;
|
||||
#rotateInputSelection;
|
||||
#settingsLoad;
|
||||
#initialized = false;
|
||||
|
||||
/**
|
||||
* Initialize the keyboard controller with dependencies
|
||||
* @param {Object} config
|
||||
* @param {Object} config.api - API object
|
||||
* @param {Object} config.platform - Platform object
|
||||
* @param {Object} config.selection - Selection object
|
||||
* @param {Object} config.slider - Slider controller
|
||||
* @param {Object} config.space - Space object
|
||||
* @param {Object} config.catalog - Catalog object
|
||||
* @param {Object} config.sdb - Local storage
|
||||
* @param {Object} config.uc - User confirmation utility
|
||||
* @param {Object} config.setCtrl - Settings controller
|
||||
* @param {Object} config.VIEWS - View constants
|
||||
* @param {Document} config.DOC - Document object
|
||||
* @param {Window} config.WIN - Window object
|
||||
* @param {Function} config.rotateInputSelection - Rotate input callback
|
||||
* @param {Function} config.settingsLoad - Settings load callback
|
||||
*/
|
||||
init({ api, platform, selection, slider, space, catalog, sdb, uc, setCtrl, VIEWS, DOC, WIN, rotateInputSelection, settingsLoad }) {
|
||||
if (this.#initialized) {
|
||||
throw new Error('Keyboard already initialized');
|
||||
}
|
||||
|
||||
this.#api = api;
|
||||
this.#platform = platform;
|
||||
this.#selection = selection;
|
||||
this.#slider = slider;
|
||||
this.#space = space;
|
||||
this.#catalog = catalog;
|
||||
this.#sdb = sdb;
|
||||
this.#uc = uc;
|
||||
this.#setCtrl = setCtrl;
|
||||
this.#VIEWS = VIEWS;
|
||||
this.#DOC = DOC;
|
||||
this.#WIN = WIN;
|
||||
this.#rotateInputSelection = rotateInputSelection;
|
||||
this.#settingsLoad = settingsLoad;
|
||||
|
||||
this.#bindEvents();
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an input element has focus
|
||||
* @returns {boolean}
|
||||
*/
|
||||
inputHasFocus() {
|
||||
let active = this.#DOC.activeElement;
|
||||
return active && (active.nodeName === "INPUT" || active.nodeName === "TEXTAREA");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get character code at position 0
|
||||
* @param {string} c - Character
|
||||
* @returns {number}
|
||||
*/
|
||||
#cca(c) {
|
||||
return c.charCodeAt(0);
|
||||
}
|
||||
|
||||
#bindEvents() {
|
||||
this.#space.event.addHandlers(self, [
|
||||
'keyup', this.#handleKeyUp.bind(this),
|
||||
'keydown', this.#handleKeyDown.bind(this),
|
||||
'keypress', this.#handleKeyPress.bind(this)
|
||||
]);
|
||||
}
|
||||
|
||||
#handleKeyUp(evt) {
|
||||
// Allow feature hooks to intercept
|
||||
if (this.#api.feature.on_key) {
|
||||
if (this.#api.feature.on_key({up:evt})) return;
|
||||
}
|
||||
for (let handler of this.#api.feature.on_key2) {
|
||||
if (handler({up:evt})) return;
|
||||
}
|
||||
|
||||
switch (evt.keyCode) {
|
||||
// escape
|
||||
case 27:
|
||||
// blur text input focus
|
||||
this.#DOC.activeElement.blur();
|
||||
// dismiss modals
|
||||
this.#api.modal.hide();
|
||||
// deselect widgets
|
||||
this.#platform.deselect();
|
||||
// hide all dialogs
|
||||
this.#api.dialog.hide();
|
||||
// cancel slicing
|
||||
this.#api.function.cancel();
|
||||
// trigger escape handlers (used by FDM mode for support editing)
|
||||
this.#onEscape();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#handleKeyDown(evt) {
|
||||
if (this.#api.modal.visible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow feature hooks to intercept
|
||||
if (this.#api.feature.on_key) {
|
||||
if (this.#api.feature.on_key({down:evt})) return;
|
||||
}
|
||||
for (let handler of this.#api.feature.on_key2) {
|
||||
if (handler({down:evt})) return;
|
||||
}
|
||||
|
||||
let move = evt.altKey ? 5 : 0,
|
||||
deg = move ? 0 : -Math.PI / (evt.shiftKey ? 36 : 2);
|
||||
|
||||
switch (evt.keyCode) {
|
||||
case 8: // apple: delete/backspace
|
||||
case 46: // others: delete
|
||||
if (this.inputHasFocus()) return false;
|
||||
this.#platform.delete(this.#selection.meshes());
|
||||
evt.preventDefault();
|
||||
break;
|
||||
case 37: // left arrow
|
||||
if (this.inputHasFocus()) return false;
|
||||
if (deg) this.#selection.rotate(0, 0, -deg);
|
||||
if (move > 0) this.#selection.move(-move, 0, 0);
|
||||
evt.preventDefault();
|
||||
break;
|
||||
case 39: // right arrow
|
||||
if (this.inputHasFocus()) return false;
|
||||
if (deg) this.#selection.rotate(0, 0, deg);
|
||||
if (move > 0) this.#selection.move(move, 0, 0);
|
||||
evt.preventDefault();
|
||||
break;
|
||||
case 38: // up arrow
|
||||
if (this.inputHasFocus()) return false;
|
||||
if (evt.metaKey) {
|
||||
const { hi } = this.#slider.getRange();
|
||||
return this.#api.show.layer(hi + 1);
|
||||
}
|
||||
if (deg) this.#selection.rotate(deg, 0, 0);
|
||||
if (move > 0) this.#selection.move(0, move, 0);
|
||||
evt.preventDefault();
|
||||
break;
|
||||
case 40: // down arrow
|
||||
if (this.inputHasFocus()) return false;
|
||||
if (evt.metaKey) {
|
||||
const { hi } = this.#slider.getRange();
|
||||
return this.#api.show.layer(hi - 1);
|
||||
}
|
||||
if (deg) this.#selection.rotate(-deg, 0, 0);
|
||||
if (move > 0) this.#selection.move(0, -move, 0);
|
||||
evt.preventDefault();
|
||||
break;
|
||||
case 65: // 'a' for select all
|
||||
if (evt.metaKey || evt.ctrlKey) {
|
||||
if (this.inputHasFocus()) return false;
|
||||
evt.preventDefault();
|
||||
this.#platform.deselect();
|
||||
this.#platform.select_all();
|
||||
}
|
||||
break;
|
||||
case 83: // 's' for save workspace
|
||||
if (evt.ctrlKey) {
|
||||
evt.preventDefault();
|
||||
this.#api.conf.save();
|
||||
console.log("settings saved");
|
||||
} else
|
||||
if (evt.metaKey) {
|
||||
evt.preventDefault();
|
||||
this.#api.space.save();
|
||||
this.#setCtrl.sync.put();
|
||||
}
|
||||
break;
|
||||
case 76: // 'l' for restore workspace
|
||||
if (evt.metaKey) {
|
||||
evt.preventDefault();
|
||||
this.#api.space.restore();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#handleKeyPress(evt) {
|
||||
let handled = true;
|
||||
if (this.#api.modal.visible() || this.inputHasFocus()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow feature hooks to intercept
|
||||
if (this.#api.feature.on_key) {
|
||||
if (this.#api.feature.on_key({key:evt})) return;
|
||||
}
|
||||
for (let handler of this.#api.feature.on_key2) {
|
||||
if (handler({key:evt})) return;
|
||||
}
|
||||
|
||||
// Handle Ctrl key combinations
|
||||
if (evt.ctrlKey) {
|
||||
switch (evt.key) {
|
||||
case 'g': return this.#api.group.merge();
|
||||
case 'u': return this.#api.group.split();
|
||||
}
|
||||
}
|
||||
|
||||
switch (evt.charCode) {
|
||||
case this.#cca('`'): this.#api.show.slices(0); break;
|
||||
case this.#cca('0'): {
|
||||
const { max } = this.#slider.getRange();
|
||||
this.#api.show.slices(max);
|
||||
break;
|
||||
}
|
||||
case this.#cca('1'): {
|
||||
const { max } = this.#slider.getRange();
|
||||
this.#api.show.slices(max/10);
|
||||
break;
|
||||
}
|
||||
case this.#cca('2'): {
|
||||
const { max } = this.#slider.getRange();
|
||||
this.#api.show.slices(max*2/10);
|
||||
break;
|
||||
}
|
||||
case this.#cca('3'): {
|
||||
const { max } = this.#slider.getRange();
|
||||
this.#api.show.slices(max*3/10);
|
||||
break;
|
||||
}
|
||||
case this.#cca('4'): {
|
||||
const { max } = this.#slider.getRange();
|
||||
this.#api.show.slices(max*4/10);
|
||||
break;
|
||||
}
|
||||
case this.#cca('5'): {
|
||||
const { max } = this.#slider.getRange();
|
||||
this.#api.show.slices(max*5/10);
|
||||
break;
|
||||
}
|
||||
case this.#cca('6'): {
|
||||
const { max } = this.#slider.getRange();
|
||||
this.#api.show.slices(max*6/10);
|
||||
break;
|
||||
}
|
||||
case this.#cca('7'): {
|
||||
const { max } = this.#slider.getRange();
|
||||
this.#api.show.slices(max*7/10);
|
||||
break;
|
||||
}
|
||||
case this.#cca('8'): {
|
||||
const { max } = this.#slider.getRange();
|
||||
this.#api.show.slices(max*8/10);
|
||||
break;
|
||||
}
|
||||
case this.#cca('9'): {
|
||||
const { max } = this.#slider.getRange();
|
||||
this.#api.show.slices(max*9/10);
|
||||
break;
|
||||
}
|
||||
case this.#cca('?'):
|
||||
this.#api.help.show();
|
||||
break;
|
||||
case this.#cca('Z'): // reset stored state
|
||||
this.#uc.confirm('clear all settings and preferences?').then(yes => {
|
||||
if (yes) {
|
||||
this.#sdb.clear();
|
||||
this.#WIN.location.reload();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case this.#cca('C'): // refresh catalog
|
||||
this.#catalog.refresh();
|
||||
break;
|
||||
case this.#cca('i'): // file import
|
||||
this.#api.event.import();
|
||||
break;
|
||||
case this.#cca('S'): // slice
|
||||
case this.#cca('s'): // slice
|
||||
this.#api.function.slice();
|
||||
break;
|
||||
case this.#cca('P'): // prepare
|
||||
case this.#cca('p'): // prepare
|
||||
if (this.#api.mode.get() !== 'SLA') {
|
||||
// hidden in SLA mode
|
||||
this.#api.function.prepare();
|
||||
}
|
||||
break;
|
||||
case this.#cca('X'): // export
|
||||
case this.#cca('x'): // export
|
||||
this.#api.function.export();
|
||||
break;
|
||||
case this.#cca('g'): // CAM animate
|
||||
this.#api.function.animate();
|
||||
break;
|
||||
case this.#cca('O'): // manual rotation
|
||||
this.#rotateInputSelection();
|
||||
break;
|
||||
case this.#cca('r'): // recent files
|
||||
this.#api.modal.show('files');
|
||||
break;
|
||||
case this.#cca('q'): // preferences
|
||||
this.#api.modal.show('prefs');
|
||||
break;
|
||||
case this.#cca('l'): // device
|
||||
this.#settingsLoad();
|
||||
break;
|
||||
case this.#cca('e'): // device
|
||||
this.#api.show.devices();
|
||||
break;
|
||||
case this.#cca('o'): // tools
|
||||
this.#api.show.tools();
|
||||
break;
|
||||
case this.#cca('c'): // local devices
|
||||
this.#api.show.local();
|
||||
break;
|
||||
case this.#cca('v'): // toggle single slice view mode
|
||||
if (this.#api.view.get() === this.#VIEWS.ARRANGE) {
|
||||
this.#api.space.set_focus(this.#selection.widgets());
|
||||
}
|
||||
const { lo, hi } = this.#slider.getRange();
|
||||
if (hi === lo) {
|
||||
this.#slider.setRange(0, hi);
|
||||
} else {
|
||||
this.#slider.setRange(hi, hi);
|
||||
}
|
||||
this.#api.show.slices();
|
||||
break;
|
||||
case this.#cca('d'): // duplicate object
|
||||
this.#selection.duplicate();
|
||||
break;
|
||||
case this.#cca('m'): // mirror object
|
||||
this.#selection.mirror();
|
||||
break;
|
||||
case this.#cca('a'):
|
||||
if (this.#api.view.get() === this.#VIEWS.ARRANGE) {
|
||||
// auto arrange items on platform
|
||||
this.#platform.layout();
|
||||
if (!this.#api.conf.get().controller.spaceRandoX) {
|
||||
this.#api.space.set_focus(this.#selection.widgets());
|
||||
}
|
||||
} else {
|
||||
// go to arrange view
|
||||
this.#api.view.set(this.#VIEWS.ARRANGE);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.#onUnhandledKey(evt);
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
if (handled) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when escape key is pressed
|
||||
* Replaces api.event.emit('key.esc')
|
||||
*/
|
||||
#onEscape() {
|
||||
// Notify listeners (currently used by FDM mode for support editing)
|
||||
this.#api.event.emit("key.esc");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an unhandled key is pressed
|
||||
* Replaces api.event.emit('keypress', evt)
|
||||
*/
|
||||
#onUnhandledKey(evt) {
|
||||
// Notify listeners for custom key handling
|
||||
this.#api.event.emit('keypress', evt);
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const keyboard = new KeyboardControl();
|
||||
|
|
@ -327,7 +327,7 @@ function setVisibleLayer(h, l) {
|
|||
l = l >= 0 ? l : lo;
|
||||
const newHi = bound(h, 0, max);
|
||||
const newLo = bound(l, 0, newHi);
|
||||
api.slider.setRange(newLo, newHi);
|
||||
api.slider.setRange(newLo, newHi); // Notify callbacks (STACKS, scene updates)
|
||||
api.slider.showLabels();
|
||||
showSlices();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,13 +68,16 @@ class SliderControl {
|
|||
* Set layer range (bounds are enforced)
|
||||
* @param {number} lo - Lower layer
|
||||
* @param {number} hi - Upper layer
|
||||
* @param {boolean} notify - Whether to trigger callbacks (default: true)
|
||||
*/
|
||||
setRange(lo, hi) {
|
||||
setRange(lo, hi, notify = true) {
|
||||
this.#layerLo = Math.max(0, Math.min(hi, lo));
|
||||
this.#layerHi = Math.max(this.#layerLo, Math.min(this.#layerMax, hi));
|
||||
this.#updateVisuals();
|
||||
this.showLabels();
|
||||
this.#notifyChange();
|
||||
if (notify) {
|
||||
this.#notifyChange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue