Java nixie clock
-------------- NixieClock.java -----------------
import java.util.*;
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.canvas.*;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import javafx.beans.property.*;
import javafx.geometry.*;
import javafx.animation.*;
import javafx.util.*;
public class NixieClock extends Application {
Nixie[] digits = new Nixie[8];
Calendar calendar = Calendar.getInstance();
int sl = -1;
@Override
public void start(Stage stage) {
BorderPane layout = new BorderPane();
stage.setScene(new Scene(layout, 300, 300));
HBox hBox = new HBox();
hBox.setAlignment(Pos.CENTER);
for (int i = 0; i < digits.length; i++) {
digits[i] = new Nixie();
digits[i].sizeProperty().bind(layout.widthProperty().divide(digits.length));
hBox.getChildren().add(digits[i]);
}
ColorPicker colorPicker = new ColorPicker();
colorPicker.setValue(digits[0].getColor());
colorPicker.setOnAction(ae -> {
for (Nixie digit : digits) digit.setColor(colorPicker.getValue());
});
layout.setBackground(new Background(new BackgroundFill(Color.MIDNIGHTBLUE, null, null)));
layout.setCenter(hBox);
layout.setBottom(colorPicker);
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0), e -> refresh()),
new KeyFrame(Duration.seconds(.5))
);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
stage.show();
}
void refresh() {
calendar.setTimeInMillis(System.currentTimeMillis());
int h = calendar.get(Calendar.HOUR_OF_DAY);
int m = calendar.get(Calendar.MINUTE);
int s = calendar.get(Calendar.SECOND);
char sep = (sl == s ? ':' : ' ');
sl = s;
digits[0].set(h / 10);
digits[1].set(h % 10);
digits[2].set(sep);
digits[3].set(m / 10);
digits[4].set(m % 10);
digits[5].set(sep);
digits[6].set(s / 10);
digits[7].set(s % 10);
}
class Nixie extends Canvas {
private char ch = ' ';
private GraphicsContext gc = getGraphicsContext2D();
private ObjectProperty<Color> colorProperty = new SimpleObjectProperty<>();
private DoubleProperty sizeProperty = new SimpleDoubleProperty();
private Paint pBorder = Color.BLACK;
private Paint pBackground = Color.web("505050");
private Paint pClamp = Color.web("858585");
private Paint pGrid = Color.web("A9A9A9");
private Paint pBody = new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE,
new Stop[] { new Stop(0, Color.web("E6E6FF")), new Stop(1, Color.web("8F8FBA"))}
);
public Nixie() {
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineJoin(StrokeLineJoin.ROUND);
gc.setFillRule(FillRule.EVEN_ODD);
colorProperty.addListener(obs -> draw());
colorProperty.set(Color.web("F8F595"));
sizeProperty().addListener(obs -> {
setWidth(sizeProperty.get());
setHeight(sizeProperty.get() * 75 / 50);
draw();
});
}
public void set(int n) { set(n >= 0 && n <= 9 ? (char)('0' + n) : '\0'); }
public void set(char ch) {
if (this.ch != ch) {
this.ch = ch;
draw();
}
}
private void draw() {
double scale = Math.min(getWidth() / 50, getHeight() / 75);
gc.setTransform(scale, 0, 0, scale, 0, 0);
gc.clearRect(0, 0, 50, 75);
gc.setFill(pBackground);
gc.fillRect(6, 6, 36, 62);
if (ch != ' ') {
boolean fill = false;
String p;
switch (ch) {
case '0': p = "M24,16 A12,21 0 1,0 24,58 A12,21 0 1,0 24,16"; break;
case '1': p = "M24,16 V58"; break;
case '2': p = "M12,28 A12,12 0 0,1 24,16 A12,12 0 0,1 36,28 C36,44 12,39 12,58 H36"; break;
case '3': p = "M12,16 H36 L24,32 A13,13 0 0,1 24,58 A13,13 0 0,1 12,52"; break;
case '4': p = "M36,46 H12 L30,16 V58"; break;
case '5': p = "M36,16 H14 L12,35 A14,14 0 1,1 24,58 A14,14 0 0,1 12,52"; break;
case '6': p = "M24,34 A12,12 0 1,0 24,58 A12,12 0 1,0 24,34 M14.3,39 L29,16"; break;
case '7': p = "M12,16 H36 L22,58"; break;
case '8': p = "M24,34 A12,12 0 1,0 24,58 A12,12 0 1,0 24,34 A9,9 0 1,0 24,16 A9,9 0 1,0 24,34"; break;
case '9': p = "M24,16 A12,12 0 1,0 24,40 A12,12 0 1,0 24,16 M33.7,35 L19,58"; break;
case ':': p = "M24,24 A1,1 0 1,0 24,28 A1,1 0 1,0 24,24 M24,46 A1,1 0 1,0 24,50 A1,1 0 1,0 24,46"; fill = true; break;
default: p = "M12,16 H36 V58 H12 V16";
}
gc.setLineWidth(5);
gc.beginPath();
gc.appendSVGPath(p);
gc.setStroke(colorProperty.get());
if (fill) { gc.setFill(colorProperty.get()); gc.fill(); }
gc.stroke();
}
gc.setStroke(pGrid);
gc.setLineWidth(0.3);
for (int i = 11; i < 67; i += 4) gc.strokeLine(6, i, 42, i); //14
for (int i = 10; i < 42; i += 4) gc.strokeLine(i, 6, i, 68); //8
gc.beginPath();
gc.appendSVGPath("M1,17 Q1,1 24,1 Q47,1 47,17 V57 Q47,73 24,73 Q1,73 1,57 z");
gc.appendSVGPath("M6,17 Q6,6 24,6 Q42,6 42,17 V57 Q42,68 24,68 Q6,68 6,57 z");
gc.setFill(pBody);
gc.setStroke(pBorder);
gc.setLineWidth(.3);
gc.fill();
gc.stroke();
gc.setFill(pClamp);
gc.fillOval(20, 6, 8, 8);
gc.fillOval(20, 60, 8, 8);
gc.setFill(pBorder);
gc.fillOval(22, 8, 4, 4);
gc.fillOval(22, 62, 4, 4);
}
public Color getColor() { return colorProperty.get(); }
public void setColor(Color color) { colorProperty.set(color); }
public ObjectProperty<Color> colorProperty() { return colorProperty; }
public double getSize() { return sizeProperty.get(); }
public void setSize(double size) { sizeProperty.set(size); }
public DoubleProperty sizeProperty() { return sizeProperty; }
@Override public boolean isResizable() { return true; }
@Override public double prefWidth(double height) { return getWidth(); }
@Override public double prefHeight(double width) { return getHeight(); }
}
}