package nl.utwente.viskell.ui.components;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import com.google.common.collect.ImmutableMap;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.scene.control.Slider;
import javafx.scene.input.TouchEvent;
import nl.utwente.viskell.ui.ToplevelPane;
import nl.utwente.viskell.ui.serialize.Bundleable;
/**
* An extension of ValueBlock.
* The value of this Block can be changed by dragging a slider.
*/
public class SliderBlock extends ValueBlock implements Bundleable {
@FXML protected Slider slider;
/** Whether this slider represent an integral value. */
public final boolean isIntegral;
/** The value as of the latest finished modification. */
private double baseValue;
/**
* Constructs a new SliderBlock
* @param pane The parent pane this Block resides on.
* @param isIntegral wWhether this slider represent an integral value.
*/
public SliderBlock(ToplevelPane pane, boolean isIntegral) {
super("SliderBlock", pane, pane.getEnvInstance().buildType(isIntegral ? "Num a => a" : "Fractional a => a"));
this.isIntegral = isIntegral;
this.baseValue = 0;
this.slider.setValue(0);
this.updateValue();
slider.getStyleClass().add("inactive");
slider.setOnMousePressed(e -> slider.getStyleClass().removeAll("inactive"));
slider.setOnMouseReleased(e -> slider.getStyleClass().add("inactive"));
slider.setOnTouchPressed(e -> slider.getStyleClass().removeAll("inactive"));
slider.setOnTouchReleased(e -> slider.getStyleClass().add("inactive"));
slider.valueProperty().addListener(ev -> this.updateValue());
slider.valueChangingProperty().addListener(ev -> this.toggleSliding());
slider.addEventHandler(TouchEvent.TOUCH_MOVED, Event::consume);
}
@Override
protected ImmutableMap<String, Object> toBundleFragment() {
return ImmutableMap.of(
"isIntegral", isIntegral,
"value", getValue());
}
public static SliderBlock fromBundleFragment(ToplevelPane pane, Map<String,Object> bundleFragment) {
boolean isIntegral = (Boolean)bundleFragment.get("isIntegral");
SliderBlock sliderBlock = new SliderBlock(pane, isIntegral);
sliderBlock.setValue((String)bundleFragment.get("value"));
return sliderBlock;
}
private double computeCurrentValue() {
double offset = this.slider.getValue();
if (this.isIntegral) {
return Math.rint(this.baseValue + Math.signum(offset) * 4 * Math.expm1(Math.abs(offset) * 4));
}
else {
return this.baseValue + Math.signum(offset) * Math.expm1(Math.abs(offset) * 5);
}
}
private void updateValue() {
if (this.isIntegral) {
setValue("" + Math.round(this.computeCurrentValue()));
} else {
setValue(String.format(Locale.US, "%.5f", this.computeCurrentValue()));
}
this.initiateConnectionChanges();
}
private void toggleSliding() {
if (!slider.isValueChanging()) {
// on finish reset slider and use new base value
this.baseValue = this.computeCurrentValue();
this.slider.setValue(0.0);
}
}
public void resetSlider() {
this.baseValue = 0;
this.slider.setValue(0);
this.updateValue();
}
@Override
public Optional<Block> getNewCopy() {
SliderBlock block = new SliderBlock(this.getToplevel(), this.isIntegral);
block.slider.setValue(this.slider.getValue());
return Optional.of(block);
}
}