UIForSlider.java
package swingtree;
import sprouts.Action;
import sprouts.From;
import sprouts.Val;
import sprouts.Var;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.util.Objects;
import java.util.function.Consumer;
/**
* A SwingTree builder node designed for configuring {@link JSlider} instances.
* <p>
* <b>Please take a look at the <a href="https://globaltcad.github.io/swing-tree/">living swing-tree documentation</a>
* where you can browse a large collection of examples demonstrating how to use the API of this class.</b>
*/
public final class UIForSlider<S extends JSlider> extends UIForAnySwing<UIForSlider<S>, S>
{
private final BuilderState<S> _state;
UIForSlider( BuilderState<S> state ) {
Objects.requireNonNull(state);
_state = state;
}
@Override
protected BuilderState<S> _state() {
return _state;
}
@Override
protected UIForSlider<S> _newBuilderWithState(BuilderState<S> newState ) {
return new UIForSlider<>(newState);
}
/**
* Sets the orientation of the slider.
* @param align The orientation of the slider.
* @return This builder node.
*/
public final UIForSlider<S> withOrientation( UI.Align align ) {
NullUtil.nullArgCheck( align, "align", UI.Align.class );
return _with( thisComponent -> {
_setOrientation( thisComponent, align );
})
._this();
}
private void _setOrientation( S thisComponent, UI.Align align ) {
_doWithoutListeners(thisComponent,
() -> thisComponent.setOrientation(align.forSlider())
);
}
/**
* Dynamically sets the orientation of the slider.
* @param align The orientation of the slider.
* @return This builder node.
*/
public final UIForSlider<S> withOrientation( Val<UI.Align> align ) {
NullUtil.nullArgCheck( align, "align", Val.class );
NullUtil.nullPropertyCheck( align, "align", "Null is not a valid alignment" );
return _withOnShow( align, (thisComponent,v) -> {
_setOrientation(thisComponent, align.orElseThrow());
})
._with( thisComponent -> {
_setOrientation(thisComponent, align.orElseThrow());
})
._this();
}
/**
* Adds an {@link Action} to the underlying {@link JSlider}
* through an {@link javax.swing.event.ChangeListener},
* which will be called when the state of the slider changes.
* For more information see {@link JSlider#addChangeListener(javax.swing.event.ChangeListener)}.
*
* @param action The {@link Action} that will be called through the underlying change event.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException if {@code action} is {@code null}.
*/
public final UIForSlider<S> onChange( Action<ComponentDelegate<JSlider, ChangeEvent>> action ) {
NullUtil.nullArgCheck( action, "action", Action.class );
return _with( thisComponent -> {
_onChange(thisComponent,
e -> _runInApp(()->action.accept(new ComponentDelegate<>(thisComponent, e)))
);
})
._this();
}
private void _onChange( S thisComponent, Consumer<ChangeEvent> action ) {
thisComponent.addChangeListener(action::accept);
}
/**
* Sets the minimum value of the slider.
* For more information see {@link JSlider#setMinimum(int)}.
*
* @param min The minimum value of the slider.
* @return This very instance, which enables builder-style method chaining.
*/
public final UIForSlider<S> withMin( int min ) {
return _with( thisComponent -> {
_setMin( thisComponent, min );
})
._this();
}
private void _setMin( S thisComponent, int min ) {
_doWithoutListeners(thisComponent, ()->thisComponent.setMinimum( min ));
}
/**
* Binds the supplied {@link Val} property to the min value of the slider
* so that when the value of the property changes, the min value of the slider will be updated accordingly.
* For more information about the underlying value in the component, see {@link JSlider#setMinimum(int)}.
*
* @param min The min property used to dynamically update the min value of the slider.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException if {@code min} is {@code null}.
*/
public final UIForSlider<S> withMin( Val<Integer> min ) {
NullUtil.nullArgCheck( min, "min", Val.class );
return _withOnShow( min, (thisComponent,v) -> {
_setMin(thisComponent, min.orElseThrow());
})
._with( thisComponent -> {
_setMin(thisComponent, min.orElseThrow());
})
._this();
}
/**
* Sets the maximum value of the slider.
* For more information see {@link JSlider#setMaximum(int)} (int)}.
*
* @param max The maximum value of the slider.
* @return This very instance, which enables builder-style method chaining.
*/
public final UIForSlider<S> withMax( int max ) {
return _with( thisComponent -> {
_setMax( thisComponent, max );
})
._this();
}
private void _setMax( S thisComponent, int max ) {
_doWithoutListeners(thisComponent, ()->thisComponent.setMaximum( max ));
}
/**
* Binds the supplied {@link Val} property to the max value of the slider.
* When the value of the property changes, the max value of the slider will be updated accordingly.
* For more information about the underlying value in the component, see {@link JSlider#setMaximum(int)}.
*
* @param max An integer property used to dynamically update the max value of the slider.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException if {@code max} is {@code null}.
*/
public final UIForSlider<S> withMax( Val<Integer> max ) {
NullUtil.nullArgCheck( max, "max", Val.class );
return _withOnShow( max, (thisComponent,v) -> {
_setMax(thisComponent, max.orElseThrow());
})
._with( thisComponent -> {
_setMax(thisComponent, max.orElseThrow());
})
._this();
}
/**
* Sets the current value of the slider.
* For more information see {@link JSlider#setValue(int)}.
*
* @param value The current value of the slider.
* @return This very instance, which enables builder-style method chaining.
*/
public final UIForSlider<S> withValue( int value ) {
return _with( thisComponent -> {
_setValue( thisComponent, value );
})
._this();
}
private void _setValue( S thisComponent, int value ) {
_doWithoutListeners(thisComponent, ()->thisComponent.setValue( value ));
}
/**
* Binds the supplied {@link Val} property to the value of the slider,
* which causes the knob of the slider to move when the value of the property changes.
* But note that the supplied property is a read only, so when the user updates
* the value of the slider, the property will not be updated.
* Use {@link #withValue(Var)} if you want to bind a property bidirectionally.
* For more information about the underlying value in the component, see {@link JSlider#setValue(int)}.
*
* @param val An integer property used to dynamically update the value of the slider.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException if {@code value} is {@code null}.
*/
public final UIForSlider<S> withValue( Val<Integer> val ) {
NullUtil.nullArgCheck( val, "val", Val.class );
return _withOnShow( val, (thisComponent,v) -> {
_setValue(thisComponent, val.orElseThrow());
})
._with( thisComponent -> {
_setValue(thisComponent, val.orElseThrow());
})
._this();
}
/**
* Use this to bind the supplied {@link Var} property to the value of the slider.
* When the value of the slider changes, the value of the {@link Var} will be updated
* and when the item of the {@link Var} is changed as part of the application logic,
* the value of the slider will be updated accordingly.
*
* @param var An integer property used to dynamically update the value of the slider.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException if {@code value} is {@code null}.
*/
public final UIForSlider<S> withValue( Var<Integer> var ) {
NullUtil.nullArgCheck( var, "var", Var.class );
return _withOnShow( var, (thisComponent,v) -> {
_setValue(thisComponent, v);
})
._with( thisComponent -> {
_onChange(thisComponent,
e -> _runInApp(thisComponent.getValue(), newItem -> var.set(From.VIEW, newItem) )
);
_setValue(thisComponent, var.orElseThrow());
})
._this();
}
/**
* Sets the major tick spacing of the slider.
* For more information see {@link JSlider#setMajorTickSpacing(int)}.
*
* @param spacing The major tick spacing of the slider.
* @return This very instance, which enables builder-style method chaining.
*/
public final UIForSlider<S> withMajorTickSpacing( int spacing ) {
return _with( thisComponent -> {
thisComponent.setMajorTickSpacing( spacing );
})
._this();
}
/**
* Sets the minor tick spacing of the slider.
* For more information see {@link JSlider#setMinorTickSpacing(int)}.
*
* @param spacing The minor tick spacing of the slider.
* @return This very instance, which enables builder-style method chaining.
*/
public final UIForSlider<S> withMinorTickSpacing( int spacing ) {
return _with( thisComponent -> {
thisComponent.setMinorTickSpacing( spacing );
})
._this();
}
/**
* Dynamically sets the major tick spacing of the slider.
* For more information see {@link JSlider#setMajorTickSpacing(int)}.
* @param spacing The major tick spacing of the slider.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException if {@code spacing} is {@code null}.
*/
public final UIForSlider<S> withMajorTickSpacing( Val<Integer> spacing ) {
NullUtil.nullArgCheck( spacing, "spacing", Val.class );
NullUtil.nullPropertyCheck( spacing, "spacing" );
return _withOnShow( spacing, (thisComponent,v) -> {
thisComponent.setMajorTickSpacing(v);
})
._with( thisComponent -> {
thisComponent.setMajorTickSpacing( spacing.orElseThrow() );
})
._this();
}
/**
* Dynamically sets the minor tick spacing of the slider.
* For more information see {@link JSlider#setMinorTickSpacing(int)}.
* @param spacing The minor tick spacing of the slider.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException if {@code spacing} is {@code null}.
*/
public final UIForSlider<S> withMinorTickSpacing( Val<Integer> spacing ) {
NullUtil.nullArgCheck( spacing, "spacing", Val.class );
NullUtil.nullPropertyCheck( spacing, "spacing" );
return _withOnShow( spacing, (thisComponent,v) -> {
thisComponent.setMinorTickSpacing(v);
})
._with( thisComponent -> {
thisComponent.setMinorTickSpacing( spacing.orElseThrow() );
})
._this();
}
private void _doWithoutListeners( S thisComponent, Runnable someTask ) {
// We need to first remove the change listener, otherwise we might trigger unwanted events.
ChangeListener[] listeners = thisComponent.getChangeListeners();
for ( ChangeListener listener : listeners )
thisComponent.removeChangeListener( listener );
someTask.run();
// Now we can add the listeners back.
for ( ChangeListener listener : listeners )
thisComponent.addChangeListener( listener );
}
}