/*
* Copyright 2016 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.nui.widgets;
import com.google.common.base.Function;
import org.terasology.input.MouseInput;
import org.terasology.math.geom.Rect2i;
import org.terasology.math.TeraMath;
import org.terasology.math.geom.Vector2i;
import org.terasology.rendering.nui.BaseInteractionListener;
import org.terasology.rendering.nui.Canvas;
import org.terasology.rendering.nui.CoreWidget;
import org.terasology.rendering.nui.InteractionListener;
import org.terasology.rendering.nui.LayoutConfig;
import org.terasology.rendering.nui.SubRegion;
import org.terasology.rendering.nui.databinding.Binding;
import org.terasology.rendering.nui.databinding.DefaultBinding;
import org.terasology.rendering.nui.events.NUIMouseClickEvent;
import org.terasology.rendering.nui.events.NUIMouseDragEvent;
import org.terasology.rendering.nui.events.NUIMouseReleaseEvent;
/**
* A simple value slider bar with one handle
*/
public class UISlider extends CoreWidget {
public static final String SLIDER = "slider";
public static final String TICKER = "ticker";
private InteractionListener tickerListener = new BaseInteractionListener() {
private Vector2i offset = new Vector2i();
@Override
public boolean onMouseClick(NUIMouseClickEvent event) {
if (event.getMouseButton() == MouseInput.MOUSE_LEFT) {
active = true;
offset.set(event.getRelativeMousePosition());
offset.x -= pixelOffsetFor(getValue(), sliderWidth);
return true;
}
return false;
}
@Override
public void onMouseRelease(NUIMouseReleaseEvent event) {
active = false;
}
@Override
public void onMouseDrag(NUIMouseDragEvent event) {
if (sliderWidth > 0) {
Vector2i pos = event.getRelativeMousePosition();
int maxSlot = TeraMath.floorToInt(getRange() / getIncrement());
int slotWidth = sliderWidth / maxSlot;
int nearestSlot = maxSlot * (pos.x - offset.x + slotWidth / 2) / sliderWidth;
nearestSlot = TeraMath.clamp(nearestSlot, 0, maxSlot);
float newValue = TeraMath.clamp(getIncrement() * nearestSlot, 0, getRange()) + getMinimum();
setValue(newValue);
}
}
};
@LayoutConfig
private Binding<Float> minimum = new DefaultBinding<>(0.0f);
@LayoutConfig
private Binding<Float> range = new DefaultBinding<>(1.0f);
@LayoutConfig
private Binding<Float> increment = new DefaultBinding<>(0.1f);
@LayoutConfig
private int precision = 1;
@LayoutConfig
private Binding<Float> value = new DefaultBinding<>(0.7f);
private int sliderWidth;
private boolean active;
private Function<? super Float, String> labelFunction;
public UISlider() {
}
public UISlider(String id) {
super(id);
}
private String getDisplayText() {
if (labelFunction != null) {
return labelFunction.apply(value.get());
} else {
return String.format("%." + precision + "f", value.get());
}
}
public void setLabelFunction(Function<? super Float, String> labelFunction) {
this.labelFunction = labelFunction;
}
@Override
public void onDraw(Canvas canvas) {
canvas.setPart(SLIDER);
canvas.drawBackground();
canvas.setPart(TICKER);
String display = getDisplayText();
int tickerWidth = canvas.getCurrentStyle().getFont().getWidth(display);
tickerWidth += canvas.getCurrentStyle().getMargin().getTotalWidth();
sliderWidth = canvas.size().x - tickerWidth;
int drawLocation = pixelOffsetFor(getValue(), sliderWidth);
Rect2i tickerRegion = Rect2i.createFromMinAndSize(drawLocation, 0, tickerWidth, canvas.size().y);
try (SubRegion ignored = canvas.subRegion(tickerRegion, false)) {
canvas.drawBackground();
canvas.drawText(display);
if (isEnabled()) {
canvas.addInteractionRegion(tickerListener);
}
}
}
@Override
public Vector2i getPreferredContentSize(Canvas canvas, Vector2i areaHint) {
Vector2i result = new Vector2i();
canvas.setPart(SLIDER);
result.x = canvas.getCurrentStyle().getFixedWidth();
if (result.x == 0) {
result.x = canvas.getCurrentStyle().getMinWidth();
}
result.y = canvas.getCurrentStyle().getFixedHeight();
if (result.y == 0) {
result.y = canvas.getCurrentStyle().getMinHeight();
}
canvas.setPart(TICKER);
int tickerWidth = canvas.getCurrentStyle().getFont().getWidth(getDisplayText());
tickerWidth += canvas.getCurrentStyle().getMargin().getTotalWidth();
result.x = Math.max(result.x, tickerWidth);
if (canvas.getCurrentStyle().getFixedWidth() != 0) {
result.x = Math.max(result.x, canvas.getCurrentStyle().getFixedWidth());
} else {
result.x = Math.max(result.x, canvas.getCurrentStyle().getMinWidth());
}
if (canvas.getCurrentStyle().getFixedHeight() != 0) {
result.y = Math.max(result.y, canvas.getCurrentStyle().getFixedHeight());
} else {
result.y = Math.max(result.y, canvas.getCurrentStyle().getMinHeight());
}
return result;
}
@Override
public String getMode() {
if (!isEnabled()) {
return DISABLED_MODE;
}
if (active) {
return ACTIVE_MODE;
} else if (tickerListener.isMouseOver()) {
return HOVER_MODE;
}
return DEFAULT_MODE;
}
public void bindMinimum(Binding<Float> binding) {
this.minimum = binding;
}
/**
* @return The minimum value possible.
*/
public float getMinimum() {
return minimum.get();
}
/**
* @param min The new minimum value
*/
public void setMinimum(float min) {
this.minimum.set(min);
}
public void bindRange(Binding<Float> binding) {
this.range = binding;
}
/**
* @return The maxiumum value possible.
*/
public float getRange() {
return range.get();
}
/**
* @param val The new maximum value.
*/
public void setRange(float val) {
range.set(val);
}
public void bindIncrement(Binding<Float> binding) {
increment = binding;
}
/**
* @return The smallest increment possible.
*/
public float getIncrement() {
return increment.get();
}
/**
* @param val The new smallest increment to set to.
*/
public void setIncrement(float val) {
increment.set(val);
}
public void bindValue(Binding<Float> binding) {
value = binding;
}
/**
* @return The current value.
*/
public float getValue() {
return value.get();
}
/**
* @param val The new current value.
*/
public void setValue(float val) {
value.set(val);
}
/**
* @return The number of decimal points used.
*/
public int getPrecision() {
return precision;
}
/**
* @param precision The number of decimal points.
*/
public void setPrecision(int precision) {
this.precision = precision;
}
private int pixelOffsetFor(float val, int width) {
return TeraMath.floorToInt(width * (val - getMinimum()) / getRange());
}
}