Java nixie clock

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

Download ZIP

Back