extract slider control from init into slider.js core singleton
This commit is contained in:
parent
8349fcb65e
commit
667a7fad78
7 changed files with 440 additions and 188 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "grid-apps",
|
||||
"version": "4.5.0",
|
||||
"version": "4.6.0",
|
||||
"description": "grid.space 3d slicing & modeling tools",
|
||||
"author": "Stewart Allen <sa@grid.space>",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { $, $c } from '../../moto/webui.js';
|
|||
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 { space } from '../../moto/space.js';
|
||||
import { LOCAL, SETUP } from './main.js';
|
||||
import { VIEWS, MODES, SEED } from './consts.js';
|
||||
|
|
@ -20,6 +21,7 @@ import { menu as menuCAM } from '../mode/cam/init-menu.js';
|
|||
import { menu as menuFDM } from '../mode/fdm/init-menu.js';
|
||||
import { menu as menuLaser } from '../mode/laser/init-menu.js';
|
||||
import { menu as menuSLA } from '../mode/sla/init-menu.js';
|
||||
import STACKS from './stacks.js';
|
||||
import './tools.js';
|
||||
|
||||
let { CAM, SLA, FDM, LASER, DRAG, WJET, WEDM } = MODES,
|
||||
|
|
@ -304,14 +306,20 @@ function keyDownHandler(evt) {
|
|||
break;
|
||||
case 38: // up arrow
|
||||
if (inputHasFocus()) return false;
|
||||
if (evt.metaKey) return api.show.layer(api.var.layer_at+1);
|
||||
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) return api.show.layer(api.var.layer_at-1);
|
||||
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();
|
||||
|
|
@ -364,16 +372,56 @@ function keyHandler(evt) {
|
|||
}
|
||||
switch (evt.charCode) {
|
||||
case cca('`'): api.show.slices(0); break;
|
||||
case cca('0'): api.show.slices(api.var.layer_max); break;
|
||||
case cca('1'): api.show.slices(api.var.layer_max/10); break;
|
||||
case cca('2'): api.show.slices(api.var.layer_max*2/10); break;
|
||||
case cca('3'): api.show.slices(api.var.layer_max*3/10); break;
|
||||
case cca('4'): api.show.slices(api.var.layer_max*4/10); break;
|
||||
case cca('5'): api.show.slices(api.var.layer_max*5/10); break;
|
||||
case cca('6'): api.show.slices(api.var.layer_max*6/10); break;
|
||||
case cca('7'): api.show.slices(api.var.layer_max*7/10); break;
|
||||
case cca('8'): api.show.slices(api.var.layer_max*8/10); break;
|
||||
case cca('9'): api.show.slices(api.var.layer_max*9/10); 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;
|
||||
|
|
@ -434,10 +482,11 @@ function keyHandler(evt) {
|
|||
if (api.view.get() === VIEWS.ARRANGE) {
|
||||
api.space.set_focus(selection.widgets());
|
||||
}
|
||||
if (api.var.layer_hi == api.var.layer_lo) {
|
||||
api.var.layer_lo = 0;
|
||||
const { lo, hi } = slider.getRange();
|
||||
if (hi === lo) {
|
||||
slider.setRange(0, hi);
|
||||
} else {
|
||||
api.var.layer_lo = api.var.layer_hi;
|
||||
slider.setRange(hi, hi);
|
||||
}
|
||||
api.show.slices();
|
||||
break;
|
||||
|
|
@ -804,25 +853,28 @@ function init_one() {
|
|||
space.sky.setColor(controller.dark ? 0 : 0xffffff);
|
||||
space.setAntiAlias(controller.antiAlias);
|
||||
space.init(container, function (delta) {
|
||||
let vars = api.var;
|
||||
if (vars.layer_max === 0 || !delta) return;
|
||||
const { lo, hi, max } = slider.getRange();
|
||||
if (max === 0 || !delta) return;
|
||||
if (controller.reverseZoom) delta = -delta;
|
||||
let same = vars.layer_hi === vars.layer_lo;
|
||||
let track = vars.layer_lo > 0;
|
||||
let same = hi === lo;
|
||||
let track = lo > 0;
|
||||
let newHi = hi;
|
||||
let newLo = lo;
|
||||
if (delta > 0) {
|
||||
vars.layer_hi = Math.max(same ? 0 : vars.layer_lo, vars.layer_hi - 1);
|
||||
newHi = Math.max(same ? 0 : lo, hi - 1);
|
||||
if (track) {
|
||||
vars.layer_lo = Math.max(0, vars.layer_lo - 1);
|
||||
newLo = Math.max(0, lo - 1);
|
||||
}
|
||||
} else if (delta < 0) {
|
||||
vars.layer_hi = Math.min(vars.layer_max, vars.layer_hi + 1);
|
||||
newHi = Math.min(max, hi + 1);
|
||||
if (track) {
|
||||
vars.layer_lo = Math.min(vars.layer_hi, vars.layer_lo + 1);
|
||||
newLo = Math.min(newHi, lo + 1);
|
||||
}
|
||||
}
|
||||
if (same) {
|
||||
vars.layer_lo = vars.layer_hi;
|
||||
newLo = newHi;
|
||||
}
|
||||
slider.setRange(newLo, newHi);
|
||||
view.update_slider();
|
||||
show.slices();
|
||||
}, controller.ortho);
|
||||
|
|
@ -1086,148 +1138,45 @@ function init_one() {
|
|||
};
|
||||
|
||||
// slider setup
|
||||
const mobile = space.info.mob;
|
||||
const slbar = mobile ? 80 : 30;
|
||||
const slbar2 = slbar * 2;
|
||||
const slider = ui.sliderRange;
|
||||
const drag = { };
|
||||
slider.init({
|
||||
ui: {
|
||||
layers: ui.layers,
|
||||
slider: ui.slider,
|
||||
sliderRange: ui.sliderRange,
|
||||
sliderHold: ui.sliderHold,
|
||||
sliderMid: ui.sliderMid,
|
||||
sliderLo: ui.sliderLo,
|
||||
sliderHi: ui.sliderHi,
|
||||
sliderMin: ui.sliderMin,
|
||||
sliderMax: ui.sliderMax,
|
||||
sliderZero: $('slider-zero'),
|
||||
},
|
||||
tracker: tracker,
|
||||
mobile: space.info.mob,
|
||||
onLayerChange: (hi, lo) => {
|
||||
api.show.layer(hi, lo);
|
||||
},
|
||||
onStackUpdate: (lo, hi) => {
|
||||
STACKS.setRange(lo, hi);
|
||||
},
|
||||
onSceneUpdate: () => {
|
||||
space.scene.active();
|
||||
}
|
||||
});
|
||||
|
||||
if (mobile) {
|
||||
ui.slider.classList.add('slider-mobile');
|
||||
ui.sliderLo.classList.add('slider-mobile');
|
||||
ui.sliderHi.classList.add('slider-mobile');
|
||||
// add css style for mobile devices
|
||||
// expose slider to API
|
||||
api.slider = slider;
|
||||
|
||||
// add mobile class to body if needed
|
||||
if (space.info.mob) {
|
||||
DOC.body.classList.add('mobile');
|
||||
}
|
||||
|
||||
function pxToInt(txt) {
|
||||
return txt ? parseInt(txt.substring(0,txt.length-2)) : 0;
|
||||
}
|
||||
|
||||
function sliderUpdate() {
|
||||
let start = drag.low / drag.maxval;
|
||||
let end = (drag.low + drag.mid - slbar) / drag.maxval;
|
||||
api.event.emit('slider.pos', { start, end });
|
||||
api.var.layer_lo = Math.round(start * api.var.layer_max);
|
||||
api.var.layer_hi = Math.round(end * api.var.layer_max);
|
||||
api.show.layer();
|
||||
space.scene.active();
|
||||
}
|
||||
|
||||
function dragit(el, delta) {
|
||||
el.ontouchstart = el.onmousedown = (ev) => {
|
||||
// el.classList.add('sli-drag-el');
|
||||
tracker.style.display = 'block';
|
||||
ev.stopPropagation();
|
||||
let obj = (ev.touches ? ev.touches[0] : ev);
|
||||
drag.width = slider.clientWidth;
|
||||
drag.maxval = drag.width - slbar2;
|
||||
drag.start = obj.screenX;
|
||||
drag.loat = drag.low = pxToInt(ui.sliderHold.style.marginLeft);
|
||||
drag.mdat = drag.mid = ui.sliderMid.clientWidth;
|
||||
drag.hiat = pxToInt(ui.sliderHold.style.marginRight);
|
||||
drag.mdmax = drag.width - slbar - drag.loat;
|
||||
drag.himax = drag.width - slbar - drag.mdat;
|
||||
let cancel_drag = tracker.ontouchend = tracker.onmouseup = (ev) => {
|
||||
// el.classList.remove('sli-drag-el');
|
||||
if (ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
slider.onmousemove = undefined;
|
||||
tracker.style.display = 'none';
|
||||
};
|
||||
el.ontouchend = cancel_drag;
|
||||
el.ontouchmove = tracker.ontouchmove = tracker.onmousemove = (ev) => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
if (ev.buttons === 0) {
|
||||
return cancel_drag();
|
||||
}
|
||||
if (delta) {
|
||||
let obj = (ev.touches ? ev.touches[0] : ev);
|
||||
delta(obj.screenX - drag.start);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
dragit(ui.sliderLo, (delta) => {
|
||||
let midval = drag.mdat - delta;
|
||||
let lowval = drag.loat + delta;
|
||||
if (midval < slbar || lowval < 0) {
|
||||
return;
|
||||
}
|
||||
ui.sliderHold.style.marginLeft = `${lowval}px`;
|
||||
ui.sliderMid.style.width = `${midval}px`;
|
||||
drag.low = lowval;
|
||||
drag.mid = midval;
|
||||
sliderUpdate();
|
||||
});
|
||||
dragit(ui.sliderMid, (delta) => {
|
||||
let loval = drag.loat + delta;
|
||||
let hival = drag.hiat - delta;
|
||||
if (loval < 0 || hival < 0) return;
|
||||
ui.sliderHold.style.marginLeft = `${loval}px`;
|
||||
ui.sliderHold.style.marginRight = `${hival}px`;
|
||||
drag.low = loval;
|
||||
sliderUpdate();
|
||||
});
|
||||
dragit(ui.sliderHi, (delta) => {
|
||||
let midval = drag.mdat + delta;
|
||||
let hival = drag.hiat - delta;
|
||||
if (midval < slbar || midval > drag.mdmax || hival < 0) return;
|
||||
ui.sliderMid.style.width = `${midval}px`;
|
||||
ui.sliderHold.style.marginRight = `${hival}px`;
|
||||
drag.mid = midval;
|
||||
sliderUpdate();
|
||||
});
|
||||
|
||||
ui.load.onchange = function(event) {
|
||||
api.platform.load_files(event.target.files);
|
||||
ui.load.value = ''; // reset so you can re-import the same filee
|
||||
};
|
||||
|
||||
ui.sliderMin.onclick = () => {
|
||||
api.show.layer(0,0);
|
||||
}
|
||||
|
||||
ui.sliderMax.onclick = () => {
|
||||
api.show.layer(api.var.layer_max,0);
|
||||
}
|
||||
|
||||
ui.slider.onmouseover = (ev) => {
|
||||
api.event.emit('slider.label');
|
||||
};
|
||||
|
||||
ui.slider.onmouseleave = (ev) => {
|
||||
if (!ev.buttons) api.event.emit('slider.unlabel');
|
||||
};
|
||||
|
||||
api.event.on('slider.unlabel', (values) => {
|
||||
});
|
||||
|
||||
api.event.on('slider.label', (values) => {
|
||||
let digits = api.var.layer_max.toString().length;
|
||||
$('slider-zero').style.width = `${digits}em`;
|
||||
$('slider-max').style.width = `${digits}em`;
|
||||
$('slider-zero').innerText = api.var.layer_lo;
|
||||
$('slider-max').innerText = api.var.layer_hi;
|
||||
});
|
||||
|
||||
api.event.on('slider.set', (values) => {
|
||||
let width = slider.clientWidth;
|
||||
let maxval = width - slbar2;
|
||||
let start = Math.max(0, Math.min(1, values.start));
|
||||
let end = Math.max(start, Math.min(1, values.end));
|
||||
let lowval = start * maxval;
|
||||
let midval = ((end - start) * maxval) + slbar;
|
||||
let hival = maxval - end * maxval;
|
||||
ui.sliderHold.style.marginLeft = `${lowval}px`;
|
||||
ui.sliderMid.style.width = `${midval}px`;
|
||||
ui.sliderHold.style.marginRight = `${hival}px`;
|
||||
});
|
||||
|
||||
// store layer preferences
|
||||
api.event.on('stack.show', label => {
|
||||
let mode = api.mode.get();
|
||||
|
|
|
|||
|
|
@ -314,20 +314,21 @@ function updateSpeeds(maxSpeed, minSpeed) {
|
|||
}
|
||||
|
||||
function updateSlider() {
|
||||
api.event.emit("slider.set", {
|
||||
start: (api.var.layer_lo / api.var.layer_max),
|
||||
end: (api.var.layer_hi / api.var.layer_max)
|
||||
});
|
||||
const { lo, hi, max } = api.slider.getRange();
|
||||
if (max > 0) {
|
||||
api.slider.updatePosition(lo / max, hi / max);
|
||||
}
|
||||
api.conf.update_fields_from_range();
|
||||
}
|
||||
|
||||
function setVisibleLayer(h, l) {
|
||||
h = h >= 0 ? h : api.var.layer_hi;
|
||||
l = l >= 0 ? l : api.var.layer_lo;
|
||||
api.var.layer_hi = bound(h, 0, api.var.layer_max);
|
||||
api.var.layer_lo = bound(l, 0, h);
|
||||
api.event.emit("slider.label");
|
||||
updateSlider();
|
||||
const { lo, hi, max } = api.slider.getRange();
|
||||
h = h >= 0 ? h : hi;
|
||||
l = l >= 0 ? l : lo;
|
||||
const newHi = bound(h, 0, max);
|
||||
const newLo = bound(l, 0, newHi);
|
||||
api.slider.setRange(newLo, newHi);
|
||||
api.slider.showLabels();
|
||||
showSlices();
|
||||
}
|
||||
|
||||
|
|
@ -349,11 +350,11 @@ function setEdges(bool) {
|
|||
|
||||
function updateSliderMax(set) {
|
||||
let max = STACKS.getRange().tallest - 1;
|
||||
api.var.layer_max = api.ui.sliderMax.innerText = max;
|
||||
if (set || max < api.var.layer_hi) {
|
||||
api.var.layer_hi = api.var.layer_max;
|
||||
api.event.emit("slider.label");
|
||||
updateSlider();
|
||||
api.slider.setMax(max);
|
||||
const { hi } = api.slider.getRange();
|
||||
if (set || max < hi) {
|
||||
api.slider.setRange(max, max);
|
||||
api.slider.showLabels();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -387,31 +388,31 @@ function showSlices(layer) {
|
|||
|
||||
showSlider();
|
||||
|
||||
const { lo, hi, max } = api.slider.getRange();
|
||||
|
||||
if (typeof(layer) === 'string' || typeof(layer) === 'number') {
|
||||
layer = parseInt(layer);
|
||||
} else {
|
||||
layer = api.var.layer_hi;
|
||||
layer = hi;
|
||||
}
|
||||
|
||||
layer = bound(layer, 0, api.var.layer_max);
|
||||
if (layer < api.var.layer_lo) api.var.layer_lo = layer;
|
||||
api.var.layer_hi = layer;
|
||||
api.event.emit("slider.label");
|
||||
layer = bound(layer, 0, max);
|
||||
const newLo = layer < lo ? layer : lo;
|
||||
api.slider.setRange(newLo, layer);
|
||||
api.slider.showLabels();
|
||||
|
||||
updateSlider();
|
||||
STACKS.setRange(api.var.layer_lo, api.var.layer_hi);
|
||||
// STACKS.setRange is called by slider callback
|
||||
|
||||
SPACE.update();
|
||||
}
|
||||
|
||||
function showSlider() {
|
||||
api.ui.layers.style.display = 'flex';
|
||||
api.ui.slider.style.display = 'flex';
|
||||
api.slider.show();
|
||||
}
|
||||
|
||||
function hideSlider() {
|
||||
api.ui.layers.style.display = 'none';
|
||||
api.ui.slider.style.display = 'none';
|
||||
api.slider.hide();
|
||||
api.ui.speeds.style.display = 'none';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ function updateSettings(opt = {}) {
|
|||
if (settings.mode === 'FDM' && api.view.is_slice()) {
|
||||
let changes = {};
|
||||
let values = process;
|
||||
let { layer_lo, layer_hi, layer_max } = api.var;
|
||||
const { lo: layer_lo, hi: layer_hi, max: layer_max } = api.slider.getRange();
|
||||
let range = { lo: layer_lo, hi: layer_hi };
|
||||
let add = false;
|
||||
if (layer_lo > 0 || layer_hi < layer_max) {
|
||||
|
|
@ -450,8 +450,8 @@ function updateFieldsFromRange() {
|
|||
let match = 0;
|
||||
let values = {};
|
||||
let restores = Object.clone(overrides);
|
||||
let { layer_lo, layer_hi } = api.var;
|
||||
for (let range of getOverlappingRanges(api.var.layer_lo, api.var.layer_hi)) {
|
||||
const { lo: layer_lo, hi: layer_hi } = api.slider.getRange();
|
||||
for (let range of getOverlappingRanges(layer_lo, layer_hi)) {
|
||||
for (let key of Object.keys(range.fields)) {
|
||||
values[key] = range.fields[key];
|
||||
overrides[key] = settings.process[key];
|
||||
|
|
|
|||
302
src/kiri/core/slider.js
Normal file
302
src/kiri/core/slider.js
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
/**
|
||||
* Layer Slider Controller (Singleton)
|
||||
* Manages layer range selection and visualization for slice/preview views
|
||||
*/
|
||||
|
||||
class SliderControl {
|
||||
#layerLo = 0;
|
||||
#layerHi = 0;
|
||||
#layerMax = 0;
|
||||
#ui;
|
||||
#tracker;
|
||||
#mobile;
|
||||
#sliderBar;
|
||||
#sliderBar2;
|
||||
#drag = {};
|
||||
|
||||
// Callbacks to replace event emissions
|
||||
#onLayerChange;
|
||||
#onStackUpdate;
|
||||
#onSceneUpdate;
|
||||
|
||||
#initialized = false;
|
||||
|
||||
/**
|
||||
* Initialize the slider with configuration
|
||||
* @param {Object} config
|
||||
* @param {Object} config.ui - UI element references
|
||||
* @param {HTMLElement} config.tracker - Drag tracker element
|
||||
* @param {boolean} config.mobile - Mobile device flag
|
||||
* @param {Function} config.onLayerChange - Called when layer range changes
|
||||
* @param {Function} config.onStackUpdate - Called to update stack visibility
|
||||
* @param {Function} config.onSceneUpdate - Called to refresh scene
|
||||
*/
|
||||
init({ ui, tracker, mobile, onLayerChange, onStackUpdate, onSceneUpdate }) {
|
||||
if (this.#initialized) {
|
||||
throw new Error('Slider already initialized');
|
||||
}
|
||||
|
||||
this.#ui = ui;
|
||||
this.#tracker = tracker;
|
||||
this.#mobile = mobile;
|
||||
this.#sliderBar = mobile ? 80 : 30;
|
||||
this.#sliderBar2 = this.#sliderBar * 2;
|
||||
this.#onLayerChange = onLayerChange;
|
||||
this.#onStackUpdate = onStackUpdate;
|
||||
this.#onSceneUpdate = onSceneUpdate;
|
||||
|
||||
this.#setupUI();
|
||||
this.#bindEvents();
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current layer range
|
||||
* @returns {{lo: number, hi: number, max: number}}
|
||||
*/
|
||||
getRange() {
|
||||
return {
|
||||
lo: this.#layerLo,
|
||||
hi: this.#layerHi,
|
||||
max: this.#layerMax
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set layer range (bounds are enforced)
|
||||
* @param {number} lo - Lower layer
|
||||
* @param {number} hi - Upper layer
|
||||
*/
|
||||
setRange(lo, hi) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set maximum layer count
|
||||
* @param {number} max - Maximum layer value
|
||||
*/
|
||||
setMax(max) {
|
||||
this.#layerMax = max;
|
||||
this.#ui.sliderMax.innerText = max;
|
||||
if (max < this.#layerHi) {
|
||||
this.#layerHi = max;
|
||||
this.showLabels();
|
||||
this.#updateVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update slider visual position from normalized values
|
||||
* @param {number} startPercent - Start position (0-1)
|
||||
* @param {number} endPercent - End position (0-1)
|
||||
*/
|
||||
updatePosition(startPercent, endPercent) {
|
||||
const width = this.#ui.sliderRange.clientWidth;
|
||||
const maxval = width - this.#sliderBar2;
|
||||
const start = Math.max(0, Math.min(1, startPercent));
|
||||
const end = Math.max(start, Math.min(1, endPercent));
|
||||
|
||||
const lowval = start * maxval;
|
||||
const midval = ((end - start) * maxval) + this.#sliderBar;
|
||||
const hival = maxval - end * maxval;
|
||||
|
||||
this.#ui.sliderHold.style.marginLeft = `${lowval}px`;
|
||||
this.#ui.sliderMid.style.width = `${midval}px`;
|
||||
this.#ui.sliderHold.style.marginRight = `${hival}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show layer number labels
|
||||
*/
|
||||
showLabels() {
|
||||
const digits = this.#layerMax.toString().length;
|
||||
this.#ui.sliderZero.style.width = `${digits}em`;
|
||||
this.#ui.sliderMax.style.width = `${digits}em`;
|
||||
this.#ui.sliderZero.innerText = this.#layerLo;
|
||||
this.#ui.sliderMax.innerText = this.#layerHi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show slider UI
|
||||
*/
|
||||
show() {
|
||||
this.#ui.layers.style.display = 'flex';
|
||||
this.#ui.slider.style.display = 'flex';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide slider UI
|
||||
*/
|
||||
hide() {
|
||||
this.#ui.layers.style.display = 'none';
|
||||
this.#ui.slider.style.display = 'none';
|
||||
}
|
||||
|
||||
#setupUI() {
|
||||
if (this.#mobile) {
|
||||
this.#ui.slider.classList.add('slider-mobile');
|
||||
this.#ui.sliderLo.classList.add('slider-mobile');
|
||||
this.#ui.sliderHi.classList.add('slider-mobile');
|
||||
}
|
||||
}
|
||||
|
||||
#bindEvents() {
|
||||
// Min/Max buttons
|
||||
this.#ui.sliderMin.onclick = () => {
|
||||
this.#layerLo = 0;
|
||||
this.#layerHi = 0;
|
||||
this.showLabels();
|
||||
this.#updateVisuals();
|
||||
this.#notifyChange();
|
||||
};
|
||||
|
||||
this.#ui.sliderMax.onclick = () => {
|
||||
this.#layerLo = this.#layerMax;
|
||||
this.#layerHi = this.#layerMax;
|
||||
this.showLabels();
|
||||
this.#updateVisuals();
|
||||
this.#notifyChange();
|
||||
};
|
||||
|
||||
// Mouse hover for labels
|
||||
this.#ui.slider.onmouseover = () => {
|
||||
this.showLabels();
|
||||
};
|
||||
|
||||
this.#ui.slider.onmouseleave = (ev) => {
|
||||
if (!ev.buttons) {
|
||||
this.hideLabels();
|
||||
}
|
||||
};
|
||||
|
||||
// Drag handlers for the three slider sections
|
||||
this.#bindDrag(this.#ui.sliderLo, this.#handleLoDrag.bind(this));
|
||||
this.#bindDrag(this.#ui.sliderMid, this.#handleMidDrag.bind(this));
|
||||
this.#bindDrag(this.#ui.sliderHi, this.#handleHiDrag.bind(this));
|
||||
}
|
||||
|
||||
#bindDrag(el, deltaHandler) {
|
||||
const slider = this.#ui.sliderRange;
|
||||
|
||||
el.ontouchstart = el.onmousedown = (ev) => {
|
||||
this.#tracker.style.display = 'block';
|
||||
ev.stopPropagation();
|
||||
|
||||
let obj = (ev.touches ? ev.touches[0] : ev);
|
||||
this.#drag.width = slider.clientWidth;
|
||||
this.#drag.maxval = this.#drag.width - this.#sliderBar2;
|
||||
this.#drag.start = obj.screenX;
|
||||
this.#drag.loat = this.#drag.low = this.#pxToInt(this.#ui.sliderHold.style.marginLeft);
|
||||
this.#drag.mdat = this.#drag.mid = this.#ui.sliderMid.clientWidth;
|
||||
this.#drag.hiat = this.#pxToInt(this.#ui.sliderHold.style.marginRight);
|
||||
this.#drag.mdmax = this.#drag.width - this.#sliderBar - this.#drag.loat;
|
||||
this.#drag.himax = this.#drag.width - this.#sliderBar - this.#drag.mdat;
|
||||
|
||||
const cancelDrag = this.#tracker.ontouchend = this.#tracker.onmouseup = (ev) => {
|
||||
if (ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
slider.onmousemove = undefined;
|
||||
this.#tracker.style.display = 'none';
|
||||
};
|
||||
|
||||
el.ontouchend = cancelDrag;
|
||||
el.ontouchmove = this.#tracker.ontouchmove = this.#tracker.onmousemove = (ev) => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
if (ev.buttons === 0) {
|
||||
return cancelDrag();
|
||||
}
|
||||
if (deltaHandler) {
|
||||
let obj = (ev.touches ? ev.touches[0] : ev);
|
||||
deltaHandler(obj.screenX - this.#drag.start);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
#handleLoDrag(delta) {
|
||||
let midval = this.#drag.mdat - delta;
|
||||
let lowval = this.#drag.loat + delta;
|
||||
|
||||
if (midval < this.#sliderBar || lowval < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#ui.sliderHold.style.marginLeft = `${lowval}px`;
|
||||
this.#ui.sliderMid.style.width = `${midval}px`;
|
||||
this.#drag.low = lowval;
|
||||
this.#drag.mid = midval;
|
||||
this.#updateFromDrag();
|
||||
}
|
||||
|
||||
#handleMidDrag(delta) {
|
||||
let loval = this.#drag.loat + delta;
|
||||
let hival = this.#drag.hiat - delta;
|
||||
|
||||
if (loval < 0 || hival < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#ui.sliderHold.style.marginLeft = `${loval}px`;
|
||||
this.#ui.sliderHold.style.marginRight = `${hival}px`;
|
||||
this.#drag.low = loval;
|
||||
this.#updateFromDrag();
|
||||
}
|
||||
|
||||
#handleHiDrag(delta) {
|
||||
let midval = this.#drag.mdat + delta;
|
||||
let hival = this.#drag.hiat - delta;
|
||||
|
||||
if (midval < this.#sliderBar || midval > this.#drag.mdmax || hival < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#ui.sliderMid.style.width = `${midval}px`;
|
||||
this.#ui.sliderHold.style.marginRight = `${hival}px`;
|
||||
this.#drag.mid = midval;
|
||||
this.#updateFromDrag();
|
||||
}
|
||||
|
||||
#updateFromDrag() {
|
||||
const start = this.#drag.low / this.#drag.maxval;
|
||||
const end = (this.#drag.low + this.#drag.mid - this.#sliderBar) / this.#drag.maxval;
|
||||
|
||||
this.#layerLo = Math.round(start * this.#layerMax);
|
||||
this.#layerHi = Math.round(end * this.#layerMax);
|
||||
|
||||
this.#notifyChange();
|
||||
}
|
||||
|
||||
#updateVisuals() {
|
||||
if (this.#layerMax === 0) return;
|
||||
const start = this.#layerLo / this.#layerMax;
|
||||
const end = this.#layerHi / this.#layerMax;
|
||||
this.updatePosition(start, end);
|
||||
}
|
||||
|
||||
#notifyChange() {
|
||||
// Notify callbacks of layer change
|
||||
this.#onLayerChange?.(this.#layerHi, this.#layerLo);
|
||||
this.#onStackUpdate?.(this.#layerLo, this.#layerHi);
|
||||
this.#onSceneUpdate?.();
|
||||
}
|
||||
|
||||
#pxToInt(txt) {
|
||||
return txt ? parseInt(txt.substring(0, txt.length - 2)) : 0;
|
||||
}
|
||||
|
||||
hideLabels() {
|
||||
// Labels hide themselves on mouse leave, but can be called explicitly
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const slider = new SliderControl();
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
const terms = {
|
||||
COPYRIGHT: "Copyright (C) Stewart Allen <sa@grid.space> - All Rights Reserved",
|
||||
LICENSE: "See the license.md file included with the source distribution",
|
||||
VERSION: "4.5.0"
|
||||
VERSION: "4.6.0"
|
||||
};
|
||||
|
||||
export const beta = 0;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "Kiri:Moto 4.5.0",
|
||||
"name": "Kiri:Moto 4.6.0",
|
||||
"short_name": "Kiri:Moto",
|
||||
"description": "Slicer for 3D printers, CNC mills, laser cutters and more",
|
||||
"start_url": "/boot/index.html",
|
||||
|
|
|
|||
Loading…
Reference in a new issue