GithubHelp home page GithubHelp logo

georgealways / lil-gui Goto Github PK

View Code? Open in Web Editor NEW
995.0 995.0 39.0 2.64 MB

Makes a floating panel for controllers on the web. Works as a drop-in replacement for dat.gui in most projects.

Home Page: https://lil-gui.georgealways.com/

License: MIT License

JavaScript 83.93% HTML 1.54% SCSS 8.37% CSS 6.16%
compact creative-coding dependency-free gui

lil-gui's People

Contributors

georgealways avatar jakelazaroff avatar jonobr1 avatar juliangarnier avatar leehax avatar methuselah96 avatar peque avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lil-gui's Issues

Feature request: allow decoupling the GUI label from an object

Hi,

thanks for the great library. It covers most of my cases OOTB!

However, in my project I would like to display a slider with a label U+03A9 which renders to Ω.
I believe I could in theory use Ω as a variable name, but that feels weird.

What do you think about this syntax:

gui.add(myObject, {myAttr: "\u03a9"}, 0, 1);

which would map the values to the myObject.myAttr but would show Ω as a label?

folders open and close events

Hey there! I'm trying to keep track of the folder fold state to persist it in localStorage- what would the best way to accomplish that at the moment?

I noticed issues like #57 and would like to +1 for folder events 🙏

Something like gui.folders.onChange() would be sweet!

Problem integrating in nw.js app

Nw.js is a development platform by Intel which allows application programmers to combine a nodejs backend with a HTML/CSS/JS frontend into a standalone app (similar to Electron):

https://nwjs.io/

In Nw.js, visible HTML interfaces don't usually use the default, global "window". Rather, the global window corresponds to an invisible, background object. This is an useful design choice for headless environments. However, it has 3 problems with lil-gui:

1 - By default, lil-gui will attempt to add itself to the global (invisible) document.
2 - By delfault, styles will be injected in the head of the global invisible document.
3 - Event listeners will be registered in the global invisible document as well.

The first couple of issues can be worked around by using lil-gui's already available constructor parameters:

1 - Setting a custom HTML node from a visible document in the Nw.js app works and lil-gui's frame shows up.
2 - Disabling default style injection and setting a custom CSS in the visible document works as well.

While these couple of workarounds show a perfectly visible lil-gui frame on my Nw.js app, it's non-functional due to the fact that its event listeners are still being registered in the default, global document.

Thus, I'd like to ask: would it be possible to provide an optional, custom window/document for lil-gui to use (for instance, as a constructor parameter), instead of the global one?

Thank you very much and kind regards,

~Sky

Help wanted debugging an error

Hello, I'm developing a userscript for a website, and I keep getting an error.

Uncaught TypeError: Cannot read properties of null (reading 'controllers')

The library is imported on line 9.

View full code
window["__f__l8wddwcq.0f"] = function(){with (this.s) {(async (u, { p, r, s }) => {try {r(u, s, [undefined,undefined,undefined,p.GM_info,p.GM]);} catch (e) {if (e.message && e.stack) {console.error("ERROR: Execution of script 'paper2hack' failed! " + e.message);console.log(e.stack);} else {console.error(e);}}})(async function(define,module,exports,GM_info,GM) {
/**
 * lil-gui
 * https://lil-gui.georgealways.com
 * @version 0.17.0
 * @author George Michael Brower
 * @license MIT
 */
!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((t=t||self).lil={})}(this,(function(t){"use strict";class i{constructor(t,e,s,n,l="div"){this.parent=t,this.object=e,this.property=s,this._disabled=!1,this._hidden=!1,this.initialValue=this.getValue(),this.domElement=document.createElement("div"),this.domElement.classList.add("controller"),this.domElement.classList.add(n),this.$name=document.createElement("div"),this.$name.classList.add("name"),i.nextNameID=i.nextNameID||0,this.$name.id="lil-gui-name-"+ ++i.nextNameID,this.$widget=document.createElement(l),this.$widget.classList.add("widget"),this.$disable=this.$widget,this.domElement.appendChild(this.$name),this.domElement.appendChild(this.$widget),this.parent.children.push(this),this.parent.controllers.push(this),this.parent.$children.appendChild(this.domElement),this._listenCallback=this._listenCallback.bind(this),this.name(s)}name(t){return this._name=t,this.$name.innerHTML=t,this}onChange(t){return this._onChange=t,this}_callOnChange(){this.parent._callOnChange(this),void 0!==this._onChange&&this._onChange.call(this,this.getValue()),this._changed=!0}onFinishChange(t){return this._onFinishChange=t,this}_callOnFinishChange(){this._changed&&(this.parent._callOnFinishChange(this),void 0!==this._onFinishChange&&this._onFinishChange.call(this,this.getValue())),this._changed=!1}reset(){return this.setValue(this.initialValue),this._callOnFinishChange(),this}enable(t=!0){return this.disable(!t)}disable(t=!0){return t===this._disabled||(this._disabled=t,this.domElement.classList.toggle("disabled",t),this.$disable.toggleAttribute("disabled",t)),this}show(t=!0){return this._hidden=!t,this.domElement.style.display=this._hidden?"none":"",this}hide(){return this.show(!1)}options(t){const i=this.parent.add(this.object,this.property,t);return i.name(this._name),this.destroy(),i}min(t){return this}max(t){return this}step(t){return this}decimals(t){return this}listen(t=!0){return this._listening=t,void 0!==this._listenCallbackID&&(cancelAnimationFrame(this._listenCallbackID),this._listenCallbackID=void 0),this._listening&&this._listenCallback(),this}_listenCallback(){this._listenCallbackID=requestAnimationFrame(this._listenCallback);const t=this.save();t!==this._listenPrevValue&&this.updateDisplay(),this._listenPrevValue=t}getValue(){return this.object[this.property]}setValue(t){return this.object[this.property]=t,this._callOnChange(),this.updateDisplay(),this}updateDisplay(){return this}load(t){return this.setValue(t),this._callOnFinishChange(),this}save(){return this.getValue()}destroy(){this.listen(!1),this.parent.children.splice(this.parent.children.indexOf(this),1),this.parent.controllers.splice(this.parent.controllers.indexOf(this),1),this.parent.$children.removeChild(this.domElement)}}class e extends i{constructor(t,i,e){super(t,i,e,"boolean","label"),this.$input=document.createElement("input"),this.$input.setAttribute("type","checkbox"),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$widget.appendChild(this.$input),this.$input.addEventListener("change",()=>{this.setValue(this.$input.checked),this._callOnFinishChange()}),this.$disable=this.$input,this.updateDisplay()}updateDisplay(){return this.$input.checked=this.getValue(),this}}function s(t){let i,e;return(i=t.match(/(#|0x)?([a-f0-9]{6})/i))?e=i[2]:(i=t.match(/rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/))?e=parseInt(i[1]).toString(16).padStart(2,0)+parseInt(i[2]).toString(16).padStart(2,0)+parseInt(i[3]).toString(16).padStart(2,0):(i=t.match(/^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i))&&(e=i[1]+i[1]+i[2]+i[2]+i[3]+i[3]),!!e&&"#"+e}const n={isPrimitive:!0,match:t=>"string"==typeof t,fromHexString:s,toHexString:s},l={isPrimitive:!0,match:t=>"number"==typeof t,fromHexString:t=>parseInt(t.substring(1),16),toHexString:t=>"#"+t.toString(16).padStart(6,0)},r={isPrimitive:!1,match:Array.isArray,fromHexString(t,i,e=1){const s=l.fromHexString(t);i[0]=(s>>16&255)/255*e,i[1]=(s>>8&255)/255*e,i[2]=(255&s)/255*e},toHexString:([t,i,e],s=1)=>l.toHexString(t*(s=255/s)<<16^i*s<<8^e*s<<0)},o={isPrimitive:!1,match:t=>Object(t)===t,fromHexString(t,i,e=1){const s=l.fromHexString(t);i.r=(s>>16&255)/255*e,i.g=(s>>8&255)/255*e,i.b=(255&s)/255*e},toHexString:({r:t,g:i,b:e},s=1)=>l.toHexString(t*(s=255/s)<<16^i*s<<8^e*s<<0)},a=[n,l,r,o];class h extends i{constructor(t,i,e,n){var l;super(t,i,e,"color"),this.$input=document.createElement("input"),this.$input.setAttribute("type","color"),this.$input.setAttribute("tabindex",-1),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$text=document.createElement("input"),this.$text.setAttribute("type","text"),this.$text.setAttribute("spellcheck","false"),this.$text.setAttribute("aria-labelledby",this.$name.id),this.$display=document.createElement("div"),this.$display.classList.add("display"),this.$display.appendChild(this.$input),this.$widget.appendChild(this.$display),this.$widget.appendChild(this.$text),this._format=(l=this.initialValue,a.find(t=>t.match(l))),this._rgbScale=n,this._initialValueHexString=this.save(),this._textFocused=!1,this.$input.addEventListener("input",()=>{this._setValueFromHexString(this.$input.value)}),this.$input.addEventListener("blur",()=>{this._callOnFinishChange()}),this.$text.addEventListener("input",()=>{const t=s(this.$text.value);t&&this._setValueFromHexString(t)}),this.$text.addEventListener("focus",()=>{this._textFocused=!0,this.$text.select()}),this.$text.addEventListener("blur",()=>{this._textFocused=!1,this.updateDisplay(),this._callOnFinishChange()}),this.$disable=this.$text,this.updateDisplay()}reset(){return this._setValueFromHexString(this._initialValueHexString),this}_setValueFromHexString(t){if(this._format.isPrimitive){const i=this._format.fromHexString(t);this.setValue(i)}else this._format.fromHexString(t,this.getValue(),this._rgbScale),this._callOnChange(),this.updateDisplay()}save(){return this._format.toHexString(this.getValue(),this._rgbScale)}load(t){return this._setValueFromHexString(t),this._callOnFinishChange(),this}updateDisplay(){return this.$input.value=this._format.toHexString(this.getValue(),this._rgbScale),this._textFocused||(this.$text.value=this.$input.value.substring(1)),this.$display.style.backgroundColor=this.$input.value,this}}class d extends i{constructor(t,i,e){super(t,i,e,"function"),this.$button=document.createElement("button"),this.$button.appendChild(this.$name),this.$widget.appendChild(this.$button),this.$button.addEventListener("click",t=>{t.preventDefault(),this.getValue().call(this.object)}),this.$button.addEventListener("touchstart",()=>{},{passive:!0}),this.$disable=this.$button}}class c extends i{constructor(t,i,e,s,n,l){super(t,i,e,"number"),this._initInput(),this.min(s),this.max(n);const r=void 0!==l;this.step(r?l:this._getImplicitStep(),r),this.updateDisplay()}decimals(t){return this._decimals=t,this.updateDisplay(),this}min(t){return this._min=t,this._onUpdateMinMax(),this}max(t){return this._max=t,this._onUpdateMinMax(),this}step(t,i=!0){return this._step=t,this._stepExplicit=i,this}updateDisplay(){const t=this.getValue();if(this._hasSlider){let i=(t-this._min)/(this._max-this._min);i=Math.max(0,Math.min(i,1)),this.$fill.style.width=100*i+"%"}return this._inputFocused||(this.$input.value=void 0===this._decimals?t:t.toFixed(this._decimals)),this}_initInput(){this.$input=document.createElement("input"),this.$input.setAttribute("type","number"),this.$input.setAttribute("step","any"),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$widget.appendChild(this.$input),this.$disable=this.$input;const t=t=>{const i=parseFloat(this.$input.value);isNaN(i)||(this._snapClampSetValue(i+t),this.$input.value=this.getValue())};let i,e,s,n,l,r=!1;const o=t=>{if(r){const s=t.clientX-i,n=t.clientY-e;Math.abs(n)>5?(t.preventDefault(),this.$input.blur(),r=!1,this._setDraggingStyle(!0,"vertical")):Math.abs(s)>5&&a()}if(!r){const i=t.clientY-s;l-=i*this._step*this._arrowKeyMultiplier(t),n+l>this._max?l=this._max-n:n+l<this._min&&(l=this._min-n),this._snapClampSetValue(n+l)}s=t.clientY},a=()=>{this._setDraggingStyle(!1,"vertical"),this._callOnFinishChange(),window.removeEventListener("mousemove",o),window.removeEventListener("mouseup",a)};this.$input.addEventListener("input",()=>{let t=parseFloat(this.$input.value);isNaN(t)||(this._stepExplicit&&(t=this._snap(t)),this.setValue(this._clamp(t)))}),this.$input.addEventListener("keydown",i=>{"Enter"===i.code&&this.$input.blur(),"ArrowUp"===i.code&&(i.preventDefault(),t(this._step*this._arrowKeyMultiplier(i))),"ArrowDown"===i.code&&(i.preventDefault(),t(this._step*this._arrowKeyMultiplier(i)*-1))}),this.$input.addEventListener("wheel",i=>{this._inputFocused&&(i.preventDefault(),t(this._step*this._normalizeMouseWheel(i)))},{passive:!1}),this.$input.addEventListener("mousedown",t=>{i=t.clientX,e=s=t.clientY,r=!0,n=this.getValue(),l=0,window.addEventListener("mousemove",o),window.addEventListener("mouseup",a)}),this.$input.addEventListener("focus",()=>{this._inputFocused=!0}),this.$input.addEventListener("blur",()=>{this._inputFocused=!1,this.updateDisplay(),this._callOnFinishChange()})}_initSlider(){this._hasSlider=!0,this.$slider=document.createElement("div"),this.$slider.classList.add("slider"),this.$fill=document.createElement("div"),this.$fill.classList.add("fill"),this.$slider.appendChild(this.$fill),this.$widget.insertBefore(this.$slider,this.$input),this.domElement.classList.add("hasSlider");const t=t=>{const i=this.$slider.getBoundingClientRect();let e=(s=t,n=i.left,l=i.right,r=this._min,o=this._max,(s-n)/(l-n)*(o-r)+r);var s,n,l,r,o;this._snapClampSetValue(e)},i=i=>{t(i.clientX)},e=()=>{this._callOnFinishChange(),this._setDraggingStyle(!1),window.removeEventListener("mousemove",i),window.removeEventListener("mouseup",e)};let s,n,l=!1;const r=i=>{i.preventDefault(),this._setDraggingStyle(!0),t(i.touches[0].clientX),l=!1},o=i=>{if(l){const t=i.touches[0].clientX-s,e=i.touches[0].clientY-n;Math.abs(t)>Math.abs(e)?r(i):(window.removeEventListener("touchmove",o),window.removeEventListener("touchend",a))}else i.preventDefault(),t(i.touches[0].clientX)},a=()=>{this._callOnFinishChange(),this._setDraggingStyle(!1),window.removeEventListener("touchmove",o),window.removeEventListener("touchend",a)},h=this._callOnFinishChange.bind(this);let d;this.$slider.addEventListener("mousedown",s=>{this._setDraggingStyle(!0),t(s.clientX),window.addEventListener("mousemove",i),window.addEventListener("mouseup",e)}),this.$slider.addEventListener("touchstart",t=>{t.touches.length>1||(this._hasScrollBar?(s=t.touches[0].clientX,n=t.touches[0].clientY,l=!0):r(t),window.addEventListener("touchmove",o,{passive:!1}),window.addEventListener("touchend",a))},{passive:!1}),this.$slider.addEventListener("wheel",t=>{if(Math.abs(t.deltaX)<Math.abs(t.deltaY)&&this._hasScrollBar)return;t.preventDefault();const i=this._normalizeMouseWheel(t)*this._step;this._snapClampSetValue(this.getValue()+i),this.$input.value=this.getValue(),clearTimeout(d),d=setTimeout(h,400)},{passive:!1})}_setDraggingStyle(t,i="horizontal"){this.$slider&&this.$slider.classList.toggle("active",t),document.body.classList.toggle("lil-gui-dragging",t),document.body.classList.toggle("lil-gui-"+i,t)}_getImplicitStep(){return this._hasMin&&this._hasMax?(this._max-this._min)/1e3:.1}_onUpdateMinMax(){!this._hasSlider&&this._hasMin&&this._hasMax&&(this._stepExplicit||this.step(this._getImplicitStep(),!1),this._initSlider(),this.updateDisplay())}_normalizeMouseWheel(t){let{deltaX:i,deltaY:e}=t;Math.floor(t.deltaY)!==t.deltaY&&t.wheelDelta&&(i=0,e=-t.wheelDelta/120,e*=this._stepExplicit?1:10);return i+-e}_arrowKeyMultiplier(t){let i=this._stepExplicit?1:10;return t.shiftKey?i*=10:t.altKey&&(i/=10),i}_snap(t){const i=Math.round(t/this._step)*this._step;return parseFloat(i.toPrecision(15))}_clamp(t){return t<this._min&&(t=this._min),t>this._max&&(t=this._max),t}_snapClampSetValue(t){this.setValue(this._clamp(this._snap(t)))}get _hasScrollBar(){const t=this.parent.root.$children;return t.scrollHeight>t.clientHeight}get _hasMin(){return void 0!==this._min}get _hasMax(){return void 0!==this._max}}class u extends i{constructor(t,i,e,s){super(t,i,e,"option"),this.$select=document.createElement("select"),this.$select.setAttribute("aria-labelledby",this.$name.id),this.$display=document.createElement("div"),this.$display.classList.add("display"),this._values=Array.isArray(s)?s:Object.values(s),this._names=Array.isArray(s)?s:Object.keys(s),this._names.forEach(t=>{const i=document.createElement("option");i.innerHTML=t,this.$select.appendChild(i)}),this.$select.addEventListener("change",()=>{this.setValue(this._values[this.$select.selectedIndex]),this._callOnFinishChange()}),this.$select.addEventListener("focus",()=>{this.$display.classList.add("focus")}),this.$select.addEventListener("blur",()=>{this.$display.classList.remove("focus")}),this.$widget.appendChild(this.$select),this.$widget.appendChild(this.$display),this.$disable=this.$select,this.updateDisplay()}updateDisplay(){const t=this.getValue(),i=this._values.indexOf(t);return this.$select.selectedIndex=i,this.$display.innerHTML=-1===i?t:this._names[i],this}}class p extends i{constructor(t,i,e){super(t,i,e,"string"),this.$input=document.createElement("input"),this.$input.setAttribute("type","text"),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$input.addEventListener("input",()=>{this.setValue(this.$input.value)}),this.$input.addEventListener("keydown",t=>{"Enter"===t.code&&this.$input.blur()}),this.$input.addEventListener("blur",()=>{this._callOnFinishChange()}),this.$widget.appendChild(this.$input),this.$disable=this.$input,this.updateDisplay()}updateDisplay(){return this.$input.value=this.getValue(),this}}let g=!1;class m{constructor({parent:t,autoPlace:i=void 0===t,container:e,width:s,title:n="Controls",injectStyles:l=!0,touchStyles:r=!0}={}){if(this.parent=t,this.root=t?t.root:this,this.children=[],this.controllers=[],this.folders=[],this._closed=!1,this._hidden=!1,this.domElement=document.createElement("div"),this.domElement.classList.add("lil-gui"),this.$title=document.createElement("div"),this.$title.classList.add("title"),this.$title.setAttribute("role","button"),this.$title.setAttribute("aria-expanded",!0),this.$title.setAttribute("tabindex",0),this.$title.addEventListener("click",()=>this.openAnimated(this._closed)),this.$title.addEventListener("keydown",t=>{"Enter"!==t.code&&"Space"!==t.code||(t.preventDefault(),this.$title.click())}),this.$title.addEventListener("touchstart",()=>{},{passive:!0}),this.$children=document.createElement("div"),this.$children.classList.add("children"),this.domElement.appendChild(this.$title),this.domElement.appendChild(this.$children),this.title(n),r&&this.domElement.classList.add("allow-touch-styles"),this.parent)return this.parent.children.push(this),this.parent.folders.push(this),void this.parent.$children.appendChild(this.domElement);this.domElement.classList.add("root"),!g&&l&&(!function(t){const i=document.createElement("style");i.innerHTML=t;const e=document.querySelector("head link[rel=stylesheet], head style");e?document.head.insertBefore(i,e):document.head.appendChild(i)}('.lil-gui{--background-color:#1f1f1f;--text-color:#ebebeb;--title-background-color:#111;--title-text-color:#ebebeb;--widget-color:#424242;--hover-color:#4f4f4f;--focus-color:#595959;--number-color:#2cc9ff;--string-color:#a2db3c;--font-size:11px;--input-font-size:11px;--font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;--font-family-mono:Menlo,Monaco,Consolas,"Droid Sans Mono",monospace;--padding:4px;--spacing:4px;--widget-height:20px;--name-width:45%;--slider-knob-width:2px;--slider-input-width:27%;--color-input-width:27%;--slider-input-min-width:45px;--color-input-min-width:45px;--folder-indent:7px;--widget-padding:0 0 0 3px;--widget-border-radius:2px;--checkbox-size:calc(var(--widget-height)*0.75);--scrollbar-width:5px;background-color:var(--background-color);color:var(--text-color);font-family:var(--font-family);font-size:var(--font-size);font-style:normal;font-weight:400;line-height:1;text-align:left;touch-action:manipulation;user-select:none;-webkit-user-select:none}.lil-gui,.lil-gui *{box-sizing:border-box;margin:0;padding:0}.lil-gui.root{display:flex;flex-direction:column;width:var(--width,245px)}.lil-gui.root>.title{background:var(--title-background-color);color:var(--title-text-color)}.lil-gui.root>.children{overflow-x:hidden;overflow-y:auto}.lil-gui.root>.children::-webkit-scrollbar{background:var(--background-color);height:var(--scrollbar-width);width:var(--scrollbar-width)}.lil-gui.root>.children::-webkit-scrollbar-thumb{background:var(--focus-color);border-radius:var(--scrollbar-width)}.lil-gui.force-touch-styles{--widget-height:28px;--padding:6px;--spacing:6px;--font-size:13px;--input-font-size:16px;--folder-indent:10px;--scrollbar-width:7px;--slider-input-min-width:50px;--color-input-min-width:65px}.lil-gui.autoPlace{max-height:100%;position:fixed;right:15px;top:0;z-index:1001}.lil-gui .controller{align-items:center;display:flex;margin:var(--spacing) 0;padding:0 var(--padding)}.lil-gui .controller.disabled{opacity:.5}.lil-gui .controller.disabled,.lil-gui .controller.disabled *{pointer-events:none!important}.lil-gui .controller>.name{flex-shrink:0;line-height:var(--widget-height);min-width:var(--name-width);padding-right:var(--spacing);white-space:pre}.lil-gui .controller .widget{align-items:center;display:flex;min-height:var(--widget-height);position:relative;width:100%}.lil-gui .controller.string input{color:var(--string-color)}.lil-gui .controller.boolean .widget{cursor:pointer}.lil-gui .controller.color .display{border-radius:var(--widget-border-radius);height:var(--widget-height);position:relative;width:100%}.lil-gui .controller.color input[type=color]{cursor:pointer;height:100%;opacity:0;width:100%}.lil-gui .controller.color input[type=text]{flex-shrink:0;font-family:var(--font-family-mono);margin-left:var(--spacing);min-width:var(--color-input-min-width);width:var(--color-input-width)}.lil-gui .controller.option select{max-width:100%;opacity:0;position:absolute;width:100%}.lil-gui .controller.option .display{background:var(--widget-color);border-radius:var(--widget-border-radius);height:var(--widget-height);line-height:var(--widget-height);max-width:100%;overflow:hidden;padding-left:.55em;padding-right:1.75em;pointer-events:none;position:relative;word-break:break-all}.lil-gui .controller.option .display.active{background:var(--focus-color)}.lil-gui .controller.option .display:after{bottom:0;content:"↕";font-family:lil-gui;padding-right:.375em;position:absolute;right:0;top:0}.lil-gui .controller.option .widget,.lil-gui .controller.option select{cursor:pointer}.lil-gui .controller.number input{color:var(--number-color)}.lil-gui .controller.number.hasSlider input{flex-shrink:0;margin-left:var(--spacing);min-width:var(--slider-input-min-width);width:var(--slider-input-width)}.lil-gui .controller.number .slider{background-color:var(--widget-color);border-radius:var(--widget-border-radius);cursor:ew-resize;height:var(--widget-height);overflow:hidden;padding-right:var(--slider-knob-width);touch-action:pan-y;width:100%}.lil-gui .controller.number .slider.active{background-color:var(--focus-color)}.lil-gui .controller.number .slider.active .fill{opacity:.95}.lil-gui .controller.number .fill{border-right:var(--slider-knob-width) solid var(--number-color);box-sizing:content-box;height:100%}.lil-gui-dragging .lil-gui{--hover-color:var(--widget-color)}.lil-gui-dragging *{cursor:ew-resize!important}.lil-gui-dragging.lil-gui-vertical *{cursor:ns-resize!important}.lil-gui .title{--title-height:calc(var(--widget-height) + var(--spacing)*1.25);-webkit-tap-highlight-color:transparent;text-decoration-skip:objects;cursor:pointer;font-weight:600;height:var(--title-height);line-height:calc(var(--title-height) - 4px);outline:none;padding:0 var(--padding)}.lil-gui .title:before{content:"▾";display:inline-block;font-family:lil-gui;padding-right:2px}.lil-gui .title:active{background:var(--title-background-color);opacity:.75}.lil-gui.root>.title:focus{text-decoration:none!important}.lil-gui.closed>.title:before{content:"▸"}.lil-gui.closed>.children{opacity:0;transform:translateY(-7px)}.lil-gui.closed:not(.transition)>.children{display:none}.lil-gui.transition>.children{overflow:hidden;pointer-events:none;transition-duration:.3s;transition-property:height,opacity,transform;transition-timing-function:cubic-bezier(.2,.6,.35,1)}.lil-gui .children:empty:before{content:"Empty";display:block;font-style:italic;height:var(--widget-height);line-height:var(--widget-height);margin:var(--spacing) 0;opacity:.5;padding:0 var(--padding)}.lil-gui.root>.children>.lil-gui>.title{border-width:0;border-bottom:1px solid var(--widget-color);border-left:0 solid var(--widget-color);border-right:0 solid var(--widget-color);border-top:1px solid var(--widget-color);transition:border-color .3s}.lil-gui.root>.children>.lil-gui.closed>.title{border-bottom-color:transparent}.lil-gui+.controller{border-top:1px solid var(--widget-color);margin-top:0;padding-top:var(--spacing)}.lil-gui .lil-gui .lil-gui>.title{border:none}.lil-gui .lil-gui .lil-gui>.children{border:none;border-left:2px solid var(--widget-color);margin-left:var(--folder-indent)}.lil-gui .lil-gui .controller{border:none}.lil-gui input{-webkit-tap-highlight-color:transparent;background:var(--widget-color);border:0;border-radius:var(--widget-border-radius);color:var(--text-color);font-family:var(--font-family);font-size:var(--input-font-size);height:var(--widget-height);outline:none;width:100%}.lil-gui input:disabled{opacity:1}.lil-gui input[type=number],.lil-gui input[type=text]{padding:var(--widget-padding)}.lil-gui input[type=number]:focus,.lil-gui input[type=text]:focus{background:var(--focus-color)}.lil-gui input::-webkit-inner-spin-button,.lil-gui input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.lil-gui input[type=number]{-moz-appearance:textfield}.lil-gui input[type=checkbox]{appearance:none;-webkit-appearance:none;border-radius:var(--widget-border-radius);cursor:pointer;height:var(--checkbox-size);text-align:center;width:var(--checkbox-size)}.lil-gui input[type=checkbox]:checked:before{content:"✓";font-family:lil-gui;font-size:var(--checkbox-size);line-height:var(--checkbox-size)}.lil-gui button{-webkit-tap-highlight-color:transparent;background:var(--widget-color);border:1px solid var(--widget-color);border-radius:var(--widget-border-radius);color:var(--text-color);cursor:pointer;font-family:var(--font-family);font-size:var(--font-size);height:var(--widget-height);line-height:calc(var(--widget-height) - 4px);outline:none;text-align:center;text-transform:none;width:100%}.lil-gui button:active{background:var(--focus-color)}@font-face{font-family:lil-gui;src:url("data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUsAAsAAAAACJwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAAH4AAADAImwmYE9TLzIAAAGIAAAAPwAAAGBKqH5SY21hcAAAAcgAAAD0AAACrukyyJBnbHlmAAACvAAAAF8AAACEIZpWH2hlYWQAAAMcAAAAJwAAADZfcj2zaGhlYQAAA0QAAAAYAAAAJAC5AHhobXR4AAADXAAAABAAAABMAZAAAGxvY2EAAANsAAAAFAAAACgCEgIybWF4cAAAA4AAAAAeAAAAIAEfABJuYW1lAAADoAAAASIAAAIK9SUU/XBvc3QAAATEAAAAZgAAAJCTcMc2eJxVjbEOgjAURU+hFRBK1dGRL+ALnAiToyMLEzFpnPz/eAshwSa97517c/MwwJmeB9kwPl+0cf5+uGPZXsqPu4nvZabcSZldZ6kfyWnomFY/eScKqZNWupKJO6kXN3K9uCVoL7iInPr1X5baXs3tjuMqCtzEuagm/AAlzQgPAAB4nGNgYRBlnMDAysDAYM/gBiT5oLQBAwuDJAMDEwMrMwNWEJDmmsJwgCFeXZghBcjlZMgFCzOiKOIFAB71Bb8AeJy1kjFuwkAQRZ+DwRAwBtNQRUGKQ8OdKCAWUhAgKLhIuAsVSpWz5Bbkj3dEgYiUIszqWdpZe+Z7/wB1oCYmIoboiwiLT2WjKl/jscrHfGg/pKdMkyklC5Zs2LEfHYpjcRoPzme9MWWmk3dWbK9ObkWkikOetJ554fWyoEsmdSlt+uR0pCJR34b6t/TVg1SY3sYvdf8vuiKrpyaDXDISiegp17p7579Gp3p++y7HPAiY9pmTibljrr85qSidtlg4+l25GLCaS8e6rRxNBmsnERunKbaOObRz7N72ju5vdAjYpBXHgJylOAVsMseDAPEP8LYoUHicY2BiAAEfhiAGJgZWBgZ7RnFRdnVJELCQlBSRlATJMoLV2DK4glSYs6ubq5vbKrJLSbGrgEmovDuDJVhe3VzcXFwNLCOILB/C4IuQ1xTn5FPilBTj5FPmBAB4WwoqAHicY2BkYGAA4sk1sR/j+W2+MnAzpDBgAyEMQUCSg4EJxAEAwUgFHgB4nGNgZGBgSGFggJMhDIwMqEAYAByHATJ4nGNgAIIUNEwmAABl3AGReJxjYAACIQYlBiMGJ3wQAEcQBEV4nGNgZGBgEGZgY2BiAAEQyQWEDAz/wXwGAAsPATIAAHicXdBNSsNAHAXwl35iA0UQXYnMShfS9GPZA7T7LgIu03SSpkwzYTIt1BN4Ak/gKTyAeCxfw39jZkjymzcvAwmAW/wgwHUEGDb36+jQQ3GXGot79L24jxCP4gHzF/EIr4jEIe7wxhOC3g2TMYy4Q7+Lu/SHuEd/ivt4wJd4wPxbPEKMX3GI5+DJFGaSn4qNzk8mcbKSR6xdXdhSzaOZJGtdapd4vVPbi6rP+cL7TGXOHtXKll4bY1Xl7EGnPtp7Xy2n00zyKLVHfkHBa4IcJ2oD3cgggWvt/V/FbDrUlEUJhTn/0azVWbNTNr0Ens8de1tceK9xZmfB1CPjOmPH4kitmvOubcNpmVTN3oFJyjzCvnmrwhJTzqzVj9jiSX911FjeAAB4nG3HMRKCMBBA0f0giiKi4DU8k0V2GWbIZDOh4PoWWvq6J5V8If9NVNQcaDhyouXMhY4rPTcG7jwYmXhKq8Wz+p762aNaeYXom2n3m2dLTVgsrCgFJ7OTmIkYbwIbC6vIB7WmFfAAAA==") format("woff")}@media (pointer:coarse){.lil-gui.allow-touch-styles{--widget-height:28px;--padding:6px;--spacing:6px;--font-size:13px;--input-font-size:16px;--folder-indent:10px;--scrollbar-width:7px;--slider-input-min-width:50px;--color-input-min-width:65px}}@media (hover:hover){.lil-gui .controller.color .display:hover:before{border:1px solid #fff9;border-radius:var(--widget-border-radius);bottom:0;content:" ";display:block;left:0;position:absolute;right:0;top:0}.lil-gui .controller.option .display.focus{background:var(--focus-color)}.lil-gui .controller.option .widget:hover .display{background:var(--hover-color)}.lil-gui .controller.number .slider:hover{background-color:var(--hover-color)}body:not(.lil-gui-dragging) .lil-gui .title:hover{background:var(--title-background-color);opacity:.85}.lil-gui .title:focus{text-decoration:underline var(--focus-color)}.lil-gui input:hover{background:var(--hover-color)}.lil-gui input:active{background:var(--focus-color)}.lil-gui input[type=checkbox]:focus{box-shadow:inset 0 0 0 1px var(--focus-color)}.lil-gui button:hover{background:var(--hover-color);border-color:var(--hover-color)}.lil-gui button:focus{border-color:var(--focus-color)}}'),g=!0),e?e.appendChild(this.domElement):i&&(this.domElement.classList.add("autoPlace"),document.body.appendChild(this.domElement)),s&&this.domElement.style.setProperty("--width",s+"px"),this.domElement.addEventListener("keydown",t=>t.stopPropagation()),this.domElement.addEventListener("keyup",t=>t.stopPropagation())}add(t,i,s,n,l){if(Object(s)===s)return new u(this,t,i,s);const r=t[i];switch(typeof r){case"number":return new c(this,t,i,s,n,l);case"boolean":return new e(this,t,i);case"string":return new p(this,t,i);case"function":return new d(this,t,i)}console.error("gui.add failed\n\tproperty:",i,"\n\tobject:",t,"\n\tvalue:",r)}addColor(t,i,e=1){return new h(this,t,i,e)}addFolder(t){return new m({parent:this,title:t})}load(t,i=!0){return t.controllers&&this.controllers.forEach(i=>{i instanceof d||i._name in t.controllers&&i.load(t.controllers[i._name])}),i&&t.folders&&this.folders.forEach(i=>{i._title in t.folders&&i.load(t.folders[i._title])}),this}save(t=!0){const i={controllers:{},folders:{}};return this.controllers.forEach(t=>{if(!(t instanceof d)){if(t._name in i.controllers)throw new Error(`Cannot save GUI with duplicate property "${t._name}"`);i.controllers[t._name]=t.save()}}),t&&this.folders.forEach(t=>{if(t._title in i.folders)throw new Error(`Cannot save GUI with duplicate folder "${t._title}"`);i.folders[t._title]=t.save()}),i}open(t=!0){return this._closed=!t,this.$title.setAttribute("aria-expanded",!this._closed),this.domElement.classList.toggle("closed",this._closed),this}close(){return this.open(!1)}show(t=!0){return this._hidden=!t,this.domElement.style.display=this._hidden?"none":"",this}hide(){return this.show(!1)}openAnimated(t=!0){return this._closed=!t,this.$title.setAttribute("aria-expanded",!this._closed),requestAnimationFrame(()=>{const i=this.$children.clientHeight;this.$children.style.height=i+"px",this.domElement.classList.add("transition");const e=t=>{t.target===this.$children&&(this.$children.style.height="",this.domElement.classList.remove("transition"),this.$children.removeEventListener("transitionend",e))};this.$children.addEventListener("transitionend",e);const s=t?this.$children.scrollHeight:0;this.domElement.classList.toggle("closed",!t),requestAnimationFrame(()=>{this.$children.style.height=s+"px"})}),this}title(t){return this._title=t,this.$title.innerHTML=t,this}reset(t=!0){return(t?this.controllersRecursive():this.controllers).forEach(t=>t.reset()),this}onChange(t){return this._onChange=t,this}_callOnChange(t){this.parent&&this.parent._callOnChange(t),void 0!==this._onChange&&this._onChange.call(this,{object:t.object,property:t.property,value:t.getValue(),controller:t})}onFinishChange(t){return this._onFinishChange=t,this}_callOnFinishChange(t){this.parent&&this.parent._callOnFinishChange(t),void 0!==this._onFinishChange&&this._onFinishChange.call(this,{object:t.object,property:t.property,value:t.getValue(),controller:t})}destroy(){this.parent&&(this.parent.children.splice(this.parent.children.indexOf(this),1),this.parent.folders.splice(this.parent.folders.indexOf(this),1)),this.domElement.parentElement&&this.domElement.parentElement.removeChild(this.domElement),Array.from(this.children).forEach(t=>t.destroy())}controllersRecursive(){let t=Array.from(this.controllers);return this.folders.forEach(i=>{t=t.concat(i.controllersRecursive())}),t}foldersRecursive(){let t=Array.from(this.folders);return this.folders.forEach(i=>{t=t.concat(i.foldersRecursive())}),t}}t.BooleanController=e,t.ColorController=h,t.Controller=i,t.FunctionController=d,t.GUI=m,t.NumberController=c,t.OptionController=u,t.StringController=p,t.default=m,Object.defineProperty(t,"__esModule",{value:!0})}));
// ==UserScript==
// @name         paper2hack
// @description  Modding utility/menu for paper.io
// @version      0.1.10
// @author       its-pablo
// @match        https://paper-io.com
// @match        https://paper-io.com/teams/
// @match        https://paperanimals.io
// @match        https://amogus.io
// @require      https://cdn.jsdelivr.net/npm/[email protected]
// @icon         https://paper-io.com/favicon.ico
// @grant        none
// ==/UserScript==
adblock = () => false //this detects if adblock is on, we make it always return false so that the impostor skin loads
window.addEventListener('load', function () {
  "use strict";
  window.api = {
      get config(){
          if(location.href === "https://paper-io.com/teams/" || location.href === "https://paperanimals.io/" || location.href === "https://amogus.io"){
              return paperio2api.config
          } else {
              return paper2.currentConfig
          }
      },
      get game(){
          if(location === ("https://paper-io.com/teams/" || "https://paperanimals.io" || "https://amogus.io")){
              return paperio2api.game
          } else {
              return paper2.game
          }
      }
  }
  let ETC = {
      "Reset": function(){gui.reset()},
      "Scroll to zoom": false,
      "Debug": api.game.debug && api.game.debugGraph,
      "Speed": api.config.unitSpeed,
      "Skin (requires refresh)": "",
      "_skins": [],
  }
  shop.btnsData.forEach(i => {
      if(i.useId === Cookies.get('skin')){
          ETC["Skin (requires refresh)"] = i.name
      }
  })
  shop.btnsData.forEach(i => {ETC._skins.push(i.name)})
  function scrollE(e) {
      if (e.deltaY > 0) {
          if (paper2.currentConfig.maxScale > 0.45) {
              paper2.currentConfig.maxScale -= 0.2
          }
      } else if (e.deltaY < 0) {
          if (paper2.currentConfig.maxScale < 4.5) {
              paper2.currentConfig.maxScale += 0.2
          }
      }
  }
  let GUI = lil.GUI
  let gui = new GUI({title: "paper2hack beta v0.1.9"})
  gui.add(ETC, "Speed", 1, 500, 5)
  gui.add(ETC, "Skin (requires refresh)", ETC._skins).onChange(v => {
      let id;
      shop.btnsData.forEach(i => {
          if(i.name === v){
              id = i.useId
          }
      })
      Cookies.set('skin', id)
      location.reload()
  })

  gui.add(ETC, "Scroll to zoom").onFinishChange(value => {
      if(value === true){
          window.addEventListener("wheel", scrollE)
      } else {
          window.removeEventListener("wheel", scrollE)
      }
  })
  gui.add(ETC, "Reset")
  /*Last things*/
  gui.load(localStorage.getItem("paper2hack"))
  gui.onFinishChange(e => {
      localStorage.setItem("paper2hack", gui.save())
  })
}, false);

}, this)}
//# sourceURL=chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?name=paper2hack.user.js&id=800778c0-e862-42e5-b04d-18a59c0c41f3
}

It happens around here:
Screen Shot 2022-10-05 at 3 24 49 PM

Is this a tampermonkey error or a lil-gui error?
thx!

Move --title-height custom property to .lil-gui?

Is there a reason that --title-height: calc(var(--widget-height) + var(--spacing) * 1.25); is a declaration in .lil-gui .title instead of with the rest of the custom props in .lil-gui?

If not I think it would be nice to keep all the custom props in the root .lil-gui class.

NumberController behaves weird when scrolling mousewheel

I define my NumberController like:

const folder = gui.addFolder("elements");
folder.add(state, 'a', 0.1).min(0).step(0.1);

Keyboard arrow-up and arrow-down work correctly. However, mouse scrolling event increases and decreases the values by 5.3 😕 the same behaviour is experienced with a dofferent controller (the sliders) where keyboards change the value by 0.015 but mouse wheel changes the value by 0.0795 (also 5.3 times larger).

I noticed these lines when searching for the problem:

_normalizeMouseWheel( e ) {
let { deltaX, deltaY } = e;
// Safari and Chrome report weird non-integral values for a notched wheel,
// but still expose actual lines scrolled via wheelDelta. Notched wheels
// should behave the same way as arrow keys.
if ( Math.floor( e.deltaY ) !== e.deltaY && e.wheelDelta ) {
deltaX = 0;
deltaY = -e.wheelDelta / 120;
deltaY *= this._stepExplicit ? 1 : 10;
}
const wheel = deltaX + -deltaY;
return wheel;
}

Maybe the magic 120 is wrongly hardcoded? But I can't even grasp my head around what the value would depend on...

I use Brave Browser:
Version 1.36.83 Chromium: 98.0.4758.87 (Official Build) beta (64-bit)

Make text in disabled controllers selectable

disable() can be used, but it greys out the DOM element, and we cannot copy the content of the text boxes. It would be nice to have a controller.readonly(true) method to make the controller read-only, and be still able to access the text values.

What do you think ?

Color alpha support

Is this somehow supported?
If not: how about just adding it by just using a rgb colorpocker, and entering the 'AA' value manually in the textbox (thats how dat.gui solved it).

Option to set 'Folders closed by default'

When we initialise the gui maybe we can pass 'defaultClosed:true' or something like that to set the folder's default open or close state

i'm migrating a three js project with lots of folders being created in different files and to prevent all the folder from getting opened ,at the end of all the folder creation , I loop/traverse/foldersRecursive through the folders and do close.() on them . works great but not ideal while developing

excellent work btw !

GitHub actions for publishing to NPM and creating "Releases"

There's two things in this repository I'd love to automate and I think GitHub Actions enable them:

  1. Publishing to NPM
  2. Creating GitHub "Releases"—how does this work when the built source is in .gitignore?

I'm hoping someone familiar with Actions could lend a hand.

Great job!!!

Drawing the user interface is not the main content of my work. I used datgui before.
But datgui is too old and really needs to be updated.

I noticed that you updated the color component and an unavailable feature(disable()). That's what I need.

Minor issue: cursor style over checkbox

It seems the cursor is displayed as a pointer when hovering the right section of the checkbox. That is fine since the whole section is used to activate/deactivate the checkbox.

However, when hovering right over the checkbox, the cursor is displayed with the default style.

Hope you can reproduce it. I can with both Firefox and Chromium. 😊

Getting the min / max values of a controller?

Hey there!

I need a function that randomizes all values (between min/max), but I'm having trouble getting access to each controller's min/max while iterating over them!

Is this currently possible?

Console warning : [Violation] Added non-passive event listener

Hi,

There is warnings in the console because of the touchstart and wheel events added to the inputs without the {passive: false} option.
This can be inconvenient when adding multiple properties because the console is full of warning and the other warnings are lost in the mass.

Console warning:
[Violation] Added non-passive event listener to a scroll-blocking 'touchstart' event. Consider marking event handler as 'passive' to make the page more responsive.

Sources of the warnings :

this.$input.addEventListener( 'wheel', onWheel );
this.$title.addEventListener( 'touchstart', () => { } );
this.$slider.addEventListener( 'touchstart', onTouchStart );
this.$slider.addEventListener( 'wheel', onWheel );

I'm using the latest version (0.16.0)

Typescript Support

Hi! I'd love to use lil-gui in my projects, but it's currently missing type definitions for TypeScript to fully work. I've not personally created type definitions for JavaScript projects before, but it might be something to consider to be able support TS projects.

image

Multiple controllers on same row ?

Hi,

I would like to know if this is implemented or planned to be implemented?
I checked the documentation I did not see anything about this topic.

If it's not implemented, let's say that I want to have 2 checkboxes and 2 buttons on the same row,
I have to create elements programmatically with the right CSS and then add them to the gui, is that correct ?

Thanks for your help.

onBeforeChange

hi, can we have a way to inject a cb before change operate plz ?

potential feature: multi-touch capability

I just implemented an iPad remote-control thing with this and notice that you can't drag multiple sliders simultaneously... I reckon it'd be more fun if you could.

Glancing at the code it seems like it might be a relatively small localised change in NumberController.js... doesn't seem like too much else would be implicated but maybe I didn't think it through. I might try to make a PR for this if there's interest.

listen() halts UI if used on many controllers

I've tried lil-GUI to replace dat.GUI, following threejs.
I've noticed it kill my framerate. this happens because I use the listen() on each field.
Note that in dat.GUI it doesn't happen.
For now I'm returning to dat. keep me updated.

/*
In this code I populate the GUI with a settings structure. 
*/
  private populateGui(node: GUI, key: string, settings: any) {
    if (typeof settings[key] == typeof String()) {
      node.add(settings, key).listen();
    } else if (typeof settings[key] == typeof Number()) {
      node.add(settings, key).listen();
    } else if (typeof settings[key] == typeof Boolean()) {
      node.add(settings, key, settings[key]).listen();
    } else if (typeof settings[key] == typeof Object()) {
      let folder = node.addFolder(key);
      Object.keys(settings[key]).forEach((k) => {
        this.populateGui(folder, k, settings[key]);
      });
    }
    node.close();
  }

  public open(settings: Settings) {
    this.close();
    this.gui = new GUI();
    this.gui.domElement.parentElement.style.zIndex = "10";
    Object.keys(settings).forEach((key) => {
      this.populateGui(this.gui, key, settings);
    });
  }

onFinishChange

It seems the onChange function has been implemented, but the .onFinishChange is just an alias, according to the documentation.

Are there any plans to implement this or is there a way to workaround it? 😊

Use case

I have an editor in which the user can edit parameters using click-and-drag on the slider or the value in the controllers to see the changes applied in real-time. These changes, however, end up being a lot when the user interacts with the controller even just one second.

Having onFinishChange allows the interface to call a "save state" function after the change has finished to make a snapshot of the state and allow undo operations with CTRL+Z afterwards.

Without this function, if I were to call the "save state" function on each .onChange, I would end up with thousands of states to undo if the user wanted to go back to the previous state.

Implement hide/show for Controller?

I am currently using this snippet:

Controller.prototype.hide = function() {                                                                              
    this.domElement.style.display = "none";
};
Controller.prototype.show = function() {
    this.domElement.style.display = "";
};

But, since hide/show is already implemented for GUI, maybe it would make sense to add it for Controller too? 😊

gui.hide/show

According to the documentation, gui.hide/show were removed.

Is there a way to hide/show the GUI instead, or is it a planned/supported feature?

Don't disappear in react

Hello, i try to use it in my nextjs website.

But i go int a problem.

When i navigate to another page and go back to the one with the lil-gui, it create another gui over the existing one.
And when you switch page, the gui don't disapear.
I think it a problem with document, like the gui is attached to the document but in react the document don't change so the gui alway stay.

I try to remove it but i didn't find a way.

Code look like this :

const [gui, setGrui] = useState<any>();
  const [state, setState] = useState({
    sunGrid: false,
    earthGrid: false,
    moonGrid: false,
  });

  const myObject = {
    sungrid: () => setState((prev) => ({ ...prev, sunGrid: !prev.sunGrid })),
    earthgrid: () =>
      setState((prev) => ({ ...prev, earthGrid: !prev.earthGrid })),
    moongrid: () => setState((prev) => ({ ...prev, moonGrid: !prev.moonGrid })),
  };

  useEffect(() => {
    if (!gui) setGrui(new GUI());
  }, []);

  useEffect(() => {
    if (gui) {
      gui.add(myObject, "sungrid");
      gui.add(myObject, "earthgrid");
      gui.add(myObject, "moongrid");
    }
  }, [gui]);

Thanks a lot

Feature Request: Auto close all folders

Would it be possible to add a function so that when you click on a folder all other folders in the Gui close automatically?

Just an idea for larger GUi's.

ALT multiplier may open Firefox's menu

I don't know if this is something unavoidable (i.e.: if it would require to change ALT key to CTRL key for example), or if it could be fixed in lil-gui. Feel free to close if it is not possible or you would rather not change the ALT key to CTRL. 😊

  • Open Firefox
  • Start a vertical drag on a number
  • Press ALT to slow down the change speed
  • After releasing ALT, Firefox's top menu pops

This does not happen if you press ALT first and then start the vertical drag. 🤷

PS: definitely not critical for me, but since I noticed... I thought I'd open another issue. 😇 😂

--folder-title-color

@WestLangley Hey! So I was thinking more about the idea for this CSS var and I forgot you can do the following:

.lil-gui .title { 
  color: #fff;
}

Does that accommodate the use case you have in mind?

HDR color

The model with emissive color that use HDR color space.

lil-gui could not identity HDR color.

e.stopPropagation() on $widget ? where is events ref ?

How can we access to listener binder to $widget ?
image

Let say i want add a DND for the GUI window width position relative.
But i dont want allow drag the GUI if the mouse is on a $widget.

Best solution will be add a e.stopPropagation() on all widget ?
But i cant found a ref of $widget events

So when i drag in a $widget, the GUI also drag.


EDIT:

this.$input.addEventListener( 'input', onInput );

Hum, this is not good ? what happen if we destroy the gui ? how do you clean listener ?
why not directly asign event to dom ?

// this.$input.addEventListener( 'input', onInput ); // this is usually for global usage , and make hard to manage.
this.$input.oninput = onInput;
// or if context needed
this.$input.oninput = (...arg)=> onInput(...arg);

Question: Should lil-gui stop propagation of key events when inputing values?

The case is well-described here for dat.gui and applies for lil-gui as well:

https://stackoverflow.com/questions/67769713/avoid-propagating-keydown-events-from-dat-gui-to-three-js-scene

When editing an input field, key events get propagated. There is an easy workaround as stated in the StackOverflow thread, but maybe it should be handled by lil-gui?

The example shown in the question does not make much sense, but a real use case is the following:

  • The user can select objects in the scene
  • The user can remove objects in the scene pressing DEL
  • The user can edit parameters of the object in lil-gui
  • If the user edits a parameter (i.e.: change a numerical number with the keyboard) and decides to delete the current value by pressing DEL instead of backspace, the object will be deleted from the scene, which is unexpected

slider handles disappear when initialized with value out of range

I'm not sure if you care about this but.... dat.gui shows a bar, lil.gui shows a line, the problem with the line is if the starting value is greater than the range, lil.gui shows nothing (because the line is effectively beyond the right edge of the control) but dat.gui shows a full bar which is arguably more useful or at least represents the situation better.

Here's an example

https://jsgist.org/?src=a2ca7508d6bd2b9ae7023517e70eccc0

Screen Shot 2021-11-26 at 00 29 08

Of course the value is out of range, but dropping in lil.gui suddenly things that used to show as full now show as empty.

Question: Should CSS class names be less generic?

I am integrating lil-gui inside a Vuetify application. Without altering the CSS in my application, this is the result:

Screenshot from 2021-12-07 02-16-44

It seems Vuetify is overriding the .title class style:

Screenshot from 2021-12-07 02-16-05

Since .title seems like a very generic class name, and in order to avoid other frameworks overriding the style, would it make sense to use more specific class names? Like .lil-gui-title for example.

Low priority, since I can very easily modify the styles in my application, but maybe it would be more user-friendly the other way? 😊

Feel free to close if you think it is not worth it. 👍

Folder close or open eats keystrokes

I have a super simple lil-gui demo that has one folder with one item. I also have a window event listener for 'keydown' events because I eventually want to enable shortcut keys for various behaviors. Upon startup, the folder is open and the event listener works as expected. But once the folder is closed, or even if reopened, keydown events never make it to my event listener until I click outside the lil-gui window. (This behavior happens with both Safari and Firefox on a Mac running macOS Monterey 12.4.)

Seems like a bug to me. Can this behavior be fixed? Thanks.

[idea] Shorter syntax

Hi!

I was thinking about a shorter syntax to bind (one-way) a gui checkbox to a property; do you think to another alternative?
When clicking on the gui checkbox I want to update obj.prop and obj.prop2 and make sure that the Gui is initied with right value.

Solution 0

const gui = new GUI();

const myObject = {
	Labl: obj.prop,
        get Labl2() { return obj.prop2}, set Labl2(v) { obj.prop2 = v}, //using setter and getter
};

gui.add( myObject, 'Labl' ).onChange(v => obj.prop = v)
gui.add( myObject, 'Labl2' )

Solution 1

export function guiAddAll(gui, params) {
    const paramsSmart = {}
    for (const param in params) {
        let [obj, prop] = params[param]
        paramsSmart[param] = obj[prop]
        gui.add(paramsSmart, param).onChange(v => obj[prop] = v)
    }
}
guiAddAll(new GUI(), {
  Labl: [obj, 'prop'],
  Labl2: [obj, 'prop2']
})

Solution 2

export function guiAddOne(gui, label, obj, f) {
    const objAndPropName = f.toString().replace(/[ |\(\)=>]/g,'') // "obj.prop"
    const propName = objAndPropName.substring(objAndPropName.indexOf('.') + 1) // "prop"
    gui.params = gui.params ?? {} //init
    gui.params[label] = obj[propName] //gui.params = {"Labl": obj.prop}
    gui.add(gui.params, label).onChange(v => obj[propName] = v)
}
guiAddOne(gui, 'Labl', obj, () => obj.prop)
guiAddOne(gui, 'Labl1', obj, () => obj.prop1)

Inspired from https://java2blog.com/get-variable-name-as-string-javascript/#Using_toString_method

Other Solution ?

Maybe using Reflect API? Generate setter and getter in a loop?

Step is ignored when typing a number

If you currently define a number controller with step 1 (i.e.: integer), then when moving the scroll bar the value gets adjusted to an actual integer. However, if you go to the number input field and enter 1.234, then after onFinishChange this value will be stored internally.

Would it make sense to "snap" the value to the given step always? In example, if you input 1.234 and the step is 1, the value would be rounded to 1.

This could apply to any step, not just integer steps (i.e.: with step 0.1 and value 1.234, it could be rounded to 1.2). But with integers it makes even more sense, since you may want to "ensure" the value is an actual integer (i.e.: imagine you used that number to access a position in an array).

How to change default theme?

This is probably due to my lack of JS experience, but I am unsure about how should I change the CSS style of the default theme. Note that I would like to change it once, not dynamically.

I tried to create a simple GUI with red background, but I am of course doing something wrong:

import { GUI } from 'https://cdn.skypack.dev/lil-gui';

const root = document.documentElement;
root.style.setProperty('--background-color', '#ff0000');
root.style.setProperty('--title-background-color', '#ff0000');

const gui = new GUI();
gui.add( document, 'title' );

I guess I am setting the wrong property. 😂

Is const style = Array.from( document.querySelectorAll( 'style' ) ).find( v => /lil-gui/.test( v.innerHTML ) ); and then replacing the innerHTML as in the kitchen sink example the only/best way to do it?

If there is a way to change styles without iterating (i.e.: before instantiating the GUI elements), that would be great.

Controller Extensions

Providing a standard entry point to provide custom controllers would be lovely.

Something on a GUI class instance would work. like:

const gui = new GUI();
gui.add_custom_controller( 'addDate', DateController );
...
const folder = gui.addFolder( 'Folder' );
folder.addDate( folderParams, 'date' );

Where DateController extends Controllers. Though perhaps you could come up with something you find more elegant.

However it's done, a method to provide custom controllers could harness community creativity. DateTime, RealTimeGraph, Point, 3DPoint, etc..

Edit: While keeping the core minimal

separator

need natvie way for add separator title

	addSeparator( f: GUI, title?: string ) {
		const separator = f.add( { l:100 }, 'l' );
		const el = separator.domElement;
		const style: Partial<CSSStyleDeclaration> = {
			borderTop:`1px solid #aaaaaa50`,
			margin: '6px 10%',
			justifyContent: 'center',
			fontSize: 'x-small',
			paddingBottom: '6px',
			opacity: '0.7',
		};
		Object.assign( el.style, style );
		while( el.firstChild ) el.removeChild( el.firstChild ); // remove all childs
		// add title in middle
		if( title ) {
			const titleDiv = document.createElement( 'div' );
			titleDiv.style.position = 'sticky';
			titleDiv.style.backgroundColor = 'inherit';
			titleDiv.innerText = title;
			separator.domElement.appendChild( titleDiv );
		}
	}

ex:

		const f0 = gui.addFolder( 'camera' );
		( this.addSeparator( f0, 'BASE' ) );
		f0.add( obj, 'sceneW' ).listen().disable();
		f0.add( obj, 'sceneH' ).listen().disable();

image

Function controllers call onChange on click

Lovely library you have going here!
Thought it'd be nice to have some sort of callback when a function button is clicked, similar to the onChange with other controllers.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.