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(); } } }