sclass JTargetAndActualSlider extends MetaWithChangeListeners is Swingable { new TargetAndActual data; settableWithVar bool showSlider = true; settableWithVar bool sliderEnabled = true; settableWithVar double min; settableWithVar double max = 100; settableWithVar int decimalsToShow = 1; settableWithVar int widthOfNumbers = 50; settableWithVar S unit; settableWithVar S description; settableWithVar int preferredBarWidth = 50; // set to false to use ints settable bool scaling = true; transient JColorBar bar; transient JSlider slider; transient JLabel lblActual, lblTarget; *() {} *(TargetAndActual *data) {} double value() { ret unnull(data.value()); } double target() { ret unnull(data.target()); } cachedVisualize { bar = swing(-> new JColorBar().max(max)); jPreferWidth(bar, preferredBarWidth); if (showSlider) { slider = scaling ? jLiveValueSlider_double_bothWays(min, max, data.varTarget()) : jLiveValueSlider_double_noScaling(iround(min), iround(max), data.varTarget()); jPreferWidth(slider, preferredBarWidth); } lblActual = jlabel(); lblTarget = jlabel(); data.varActual().onChangeAndNow(-> { bar.setValue(value()); setText(lblActual, renderValue(value())); }); data.varTarget().onChangeAndNow(-> { setText(lblTarget, renderValue(target())); }); varSliderEnabled().onChangeAndNow(-> setEnabled(slider, sliderEnabled)); ret vstack/*WithSpacing*/( slider == null ?: westCenterAndEastWithMargin( jlabel(spaceCombine("Target", description)), slider, jMinWidth(widthOfNumbers, lblTarget)), westCenterAndEastWithMargin( jlabel(spaceCombine("Actual", description)), bar, jMinWidth(widthOfNumbers, lblActual))); } swappable S renderValue(double value) { ret spaceCombine(!scaling ? n2(iround(value)) : formatDoubleX(value, decimalsToShow), unit); } JSlider slider() { visualize(); ret slider; } selfType target(double target) { data.target(target); this; } selfType value(double value) { data.value(value); this; } }