// Copyright (c) 2026 Dominic Masters // // This software is released under the MIT License. // https://opensource.org/licenses/MIT // Name label + value label + a track/fill Rectangle pair. Unlike Button, // a Slider doesn't fire on ACCEPT -- it consumes LEFT/RIGHT itself via // directionInput(), stepping value with wraparound at min/max, which is // how a Menu gives the currently-highlighted item first refusal on // direction input before falling back to moving the cursor. class Slider extends UIElement { init() { this.nameLabel = new Label(); this.add(this.nameLabel); this.valueLabel = new Label(); this.valueLabel.x = 100; this.add(this.valueLabel); this.track = new Rectangle(); this.track.y = 16; this.track.width = 100; this.track.height = 4; this.track.color = Slider.COLOR_TRACK; this.add(this.track); this.fill = new Rectangle(); this.fill.y = 16; this.fill.height = 4; this.fill.color = Slider.COLOR_FILL; this.add(this.fill); this._min = 0; this._max = 1; this.step = 0.1; this._value = 0; this.highlighted = false; this.onChange = null; this._refresh(); } get text() { return this.nameLabel.text; } set text(value) { this.nameLabel.text = value; } get min() { return this._min; } set min(value) { this._min = value; this._refresh(); } get max() { return this._max; } set max(value) { this._max = value; this._refresh(); } get value() { return this._value; } set value(v) { this._value = Math.min(this._max, Math.max(this._min, v)); this._refresh(); if(this.onChange) this.onChange(this._value); } setHighlighted(highlighted) { this.highlighted = highlighted; this.nameLabel.color = highlighted ? Slider.COLOR_HIGHLIGHTED : Slider.COLOR_NORMAL; } directionInput(dx, dy) { if(dx === 0) return false; let next = this._value + dx * this.step; const range = this._max - this._min; if(range > 0) { // Wrap within [min, max) rather than clamp, matching the old // slider's step-with-wraparound behavior. next = this._min + (((next - this._min) % range) + range) % range; } this.value = next; return true; } _refresh() { this.valueLabel.text = String(Math.round(this._value * 100) / 100); const ratio = this._max > this._min ? (this._value - this._min) / (this._max - this._min) : 0; this.fill.width = this.track.width * ratio; } } Slider.COLOR_NORMAL = { r: 255, g: 255, b: 255, a: 255 }; Slider.COLOR_HIGHLIGHTED = { r: 255, g: 0, b: 0, a: 255 }; Slider.COLOR_TRACK = { r: 80, g: 80, b: 80, a: 255 }; Slider.COLOR_FILL = { r: 0, g: 160, b: 220, a: 255 }; module.exports = Slider;