/*
* Copyright (c) 2013. by Gerrit Grunwald
*
* 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 jfx8controls.ledregion;
import javafx.animation.AnimationTimer;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.LongProperty;
import javafx.beans.property.LongPropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.PseudoClass;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.InnerShadow;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
public class Led extends Region {
private static final double PREFERRED_SIZE = 16;
private static final double MINIMUM_SIZE = 8;
private static final double MAXIMUM_SIZE = 1024;
private static final PseudoClass ON_PSEUDO_CLASS = PseudoClass.getPseudoClass("on");
private static final long SHORTEST_INTERVAL = 50_000_000l;
private static final long LONGEST_INTERVAL = 5_000_000_000l;
// Model/Controller related
private ObjectProperty<Color> ledColor;
private BooleanProperty on;
private boolean _blinking = false;
private BooleanProperty blinking;
private boolean _frameVisible = true;
private BooleanProperty frameVisible;
private long lastTimerCall;
private long _interval = 500_000_000l;
private LongProperty interval;
private AnimationTimer timer;
// View related
private double size;
private Region frame;
private Region led;
private Region highlight;
private InnerShadow innerShadow;
private DropShadow glow;
// ******************** Constructors **************************************
public Led() {
getStylesheets().add(getClass().getResource("led.css").toExternalForm());
getStyleClass().add("led");
lastTimerCall = System.nanoTime();
timer = new AnimationTimer() {
@Override public void handle(final long NOW) {
if (NOW > lastTimerCall + getInterval()) {
setOn(!isOn());
lastTimerCall = NOW;
}
}
};
init();
initGraphics();
registerListeners();
}
// ******************** Initialization ************************************
private void init() {
if (Double.compare(getWidth(), 0) <= 0 || Double.compare(getHeight(), 0) <= 0 ||
Double.compare(getPrefWidth(), 0) <= 0 || Double.compare(getPrefHeight(), 0) <= 0) {
setPrefSize(PREFERRED_SIZE, PREFERRED_SIZE);
}
if (Double.compare(getMinWidth(), 0) <= 0 || Double.compare(getMinHeight(), 0) <= 0) {
setMinSize(MINIMUM_SIZE, MINIMUM_SIZE);
}
if (Double.compare(getMaxWidth(), 0) <= 0 || Double.compare(getMaxHeight(), 0) <= 0) {
setMaxSize(MAXIMUM_SIZE, MAXIMUM_SIZE);
}
}
private void initGraphics() {
frame = new Region();
frame.getStyleClass().setAll("frame");
frame.setOpacity(isFrameVisible() ? 1 : 0);
led = new Region();
led.getStyleClass().setAll("main");
led.setStyle("-led-color: " + (getLedColor()).toString().replace("0x", "#") + ";");
innerShadow = new InnerShadow(BlurType.TWO_PASS_BOX, Color.rgb(0, 0, 0, 0.65), 8, 0d, 0d, 0d);
glow = new DropShadow(BlurType.TWO_PASS_BOX, getLedColor(), 20, 0d, 0d, 0d);
glow.setInput(innerShadow);
highlight = new Region();
highlight.getStyleClass().setAll("highlight");
// Add all nodes
getChildren().addAll(frame, led, highlight);
}
private void registerListeners() {
widthProperty().addListener(observable -> resize());
heightProperty().addListener(observable -> resize());
frameVisibleProperty().addListener(observable -> frame.setOpacity(isFrameVisible() ? 1 : 0));
onProperty().addListener(observable -> led.setEffect(isOn() ? glow : innerShadow));
ledColorProperty().addListener(observable -> {
led.setStyle("-led-color: " + (getLedColor()).toString().replace("0x", "#") + ";");
resize();
});
}
// ******************** Methods *******************************************
public final boolean isOn() {
return null == on ? false : on.get();
}
public final void setOn(final boolean ON) {
onProperty().set(ON);
}
public final BooleanProperty onProperty() {
if (null == on) {
on = new BooleanPropertyBase(false) {
@Override protected void invalidated() { pseudoClassStateChanged(ON_PSEUDO_CLASS, get()); }
@Override public Object getBean() { return this; }
@Override public String getName() { return "on"; }
};
}
return on;
}
public final boolean isBlinking() {
return null == blinking ? _blinking : blinking.get();
}
public final void setBlinking(final boolean BLINKING) {
if (null == blinking) {
_blinking = BLINKING;
if (BLINKING) {
timer.start();
} else {
timer.stop();
setOn(false);
}
} else {
blinking.set(BLINKING);
}
}
public final BooleanProperty blinkingProperty() {
if (null == blinking) {
blinking = new BooleanPropertyBase() {
@Override public void set(final boolean BLINKING) {
super.set(BLINKING);
if (BLINKING) {
timer.start();
} else {
timer.stop();
setOn(false);
}
}
@Override public Object getBean() {
return Led.this;
}
@Override public String getName() {
return "blinking";
}
};
}
return blinking;
}
public final long getInterval() {
return null == interval ? _interval : interval.get();
}
public final void setInterval(final long INTERVAL) {
if (null == interval) {
_interval = clamp(SHORTEST_INTERVAL, LONGEST_INTERVAL, INTERVAL);
} else {
interval.set(INTERVAL);
}
}
public final LongProperty intervalProperty() {
if (null == interval) {
interval = new LongPropertyBase() {
@Override public void set(final long INTERVAL) {
super.set(clamp(SHORTEST_INTERVAL, LONGEST_INTERVAL, INTERVAL));
}
@Override public Object getBean() {
return Led.this;
}
@Override public String getName() {
return "interval";
}
};
}
return interval;
}
public final boolean isFrameVisible() {
return null == frameVisible ? _frameVisible : frameVisible.get();
}
public final void setFrameVisible(final boolean FRAME_VISIBLE) {
if (null == frameVisible) {
_frameVisible = FRAME_VISIBLE;
} else {
frameVisible.set(FRAME_VISIBLE);
}
}
public final BooleanProperty frameVisibleProperty() {
if (null == frameVisible) {
frameVisible = new SimpleBooleanProperty(this, "frameVisible", _frameVisible);
}
return frameVisible;
}
public final Color getLedColor() {
return null == ledColor ? Color.RED : ledColor.get();
}
public final void setLedColor(final Color LED_COLOR) {
ledColorProperty().set(LED_COLOR);
}
public final ObjectProperty<Color> ledColorProperty() {
if (null == ledColor) {
ledColor = new SimpleObjectProperty<>(this, "ledColor", Color.RED);
}
return ledColor;
}
// ******************** Utility Methods ***********************************
public static long clamp(final long MIN, final long MAX, final long VALUE) {
if (VALUE < MIN) return MIN;
if (VALUE > MAX) return MAX;
return VALUE;
}
// ******************** Resizing ******************************************
private void resize() {
size = getWidth() < getHeight() ? getWidth() : getHeight();
if (size > 0) {
if (getWidth() > getHeight()) {
setTranslateX(0.5 * (getWidth() - size));
} else if (getHeight() > getWidth()) {
setTranslateY(0.5 * (getHeight() - size));
}
innerShadow.setRadius(0.07 * size);
glow.setRadius(0.36 * size);
glow.setColor(getLedColor());
frame.setPrefSize(size, size);
led.setPrefSize(0.72 * size, 0.72 * size);
led.relocate(0.14 * size, 0.14 * size);
led.setEffect(isOn() ? glow : innerShadow);
highlight.setPrefSize(0.58 * size, 0.58 * size);
highlight.relocate(0.21 * size, 0.21 * size);
}
}
}