/* * 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 eu.hansolo.enzo.roundlcdclock;/** * Created by * User: hansolo * Date: 11.07.13 * Time: 06:17 */ import javafx.animation.AnimationTimer; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.geometry.VPos; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.Pane; import javafx.scene.layout.Region; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.ArcType; import javafx.scene.shape.StrokeLineCap; import javafx.scene.text.Font; import javafx.scene.text.TextAlignment; import java.time.LocalDate; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; public class RoundLcdClock extends Region { private static final double PREFERRED_WIDTH = 200; private static final double PREFERRED_HEIGHT = 200; private static final double MINIMUM_WIDTH = 25; private static final double MINIMUM_HEIGHT = 25; private static final double MAXIMUM_WIDTH = 1024; private static final double MAXIMUM_HEIGHT = 1024; private double size; private double width; private double height; private ObjectProperty<Color> hColor; private ObjectProperty<Color> mColor; private ObjectProperty<Color> m5Color; private ObjectProperty<Color> sColor; private ObjectProperty<Color> timeColor; private ObjectProperty<Color> dateColor; private BooleanProperty alarmOn; private ObjectProperty<LocalTime> alarm; private BooleanProperty dateVisible; private BooleanProperty alarmVisible; private Pane pane; private Canvas canvasBkg; private GraphicsContext ctxBkg; private Canvas canvasFg; private GraphicsContext ctxFg; private Font font; private int hours; private int minutes; private int seconds; private boolean pm; private StringBuilder time; private long lastTimerCall; private AnimationTimer timer; // ******************** Constructors ************************************** public RoundLcdClock() { hColor = new SimpleObjectProperty<>(this, "hourColor", Color.BLACK); mColor = new SimpleObjectProperty<>(this, "minuteColor", Color.rgb(0, 0, 0, 0.5)); m5Color = new SimpleObjectProperty<>(this, "5MinuteColor", Color.BLACK); sColor = new SimpleObjectProperty<>(this, "secondColor", Color.BLACK); timeColor = new SimpleObjectProperty<>(this, "timeColor", Color.BLACK); dateColor = new SimpleObjectProperty<>(this, "dateColor", Color.BLACK); alarmOn = new SimpleBooleanProperty(this, "alarmOn", false); alarm = new SimpleObjectProperty<>(LocalTime.now().minusMinutes(1)); dateVisible = new SimpleBooleanProperty(this, "dateVisible", false); alarmVisible = new SimpleBooleanProperty(this, "alarmVisible", false); time = new StringBuilder(); lastTimerCall = System.nanoTime(); timer = new AnimationTimer() { @Override public void handle(long now) { if (now > lastTimerCall + 100_000_000l) { time.setLength(0); pm = LocalTime.now().get(ChronoField.AMPM_OF_DAY) == 1; hours = LocalTime.now().getHour(); String hourString = Integer.toString(hours); if (hours < 10) { time.append("0"); time.append(hourString.substring(0, 1)); } else { time.append(hourString.substring(0, 1)); time.append(hourString.substring(1)); } time.append(":"); minutes = LocalTime.now().getMinute(); String minutesString = Integer.toString(minutes); if (minutes < 10) { time.append("0"); time.append(minutesString.substring(0, 1)); } else { time.append(minutesString.substring(0, 1)); time.append(minutesString.substring(1)); } seconds = LocalTime.now().getSecond(); if (isAlarmOn() && LocalTime.now().isAfter(getAlarm()) && LocalTime.now().isBefore(getAlarm().plusNanos(105_000_000))) { fireAlarmEvent(); } drawForeground(); lastTimerCall = now; } } }; init(); initGraphics(); registerListeners(); timer.start(); } // ******************** Initialization ************************************ private void init() { if (Double.compare(getPrefWidth(), 0.0) <= 0 || Double.compare(getPrefHeight(), 0.0) <= 0 || Double.compare(getWidth(), 0.0) <= 0 || Double.compare(getHeight(), 0.0) <= 0) { if (getPrefWidth() > 0 && getPrefHeight() > 0) { setPrefSize(getPrefWidth(), getPrefHeight()); } else { setPrefSize(PREFERRED_WIDTH, PREFERRED_HEIGHT); } } if (Double.compare(getMinWidth(), 0.0) <= 0 || Double.compare(getMinHeight(), 0.0) <= 0) { setMinSize(MINIMUM_WIDTH, MINIMUM_HEIGHT); } if (Double.compare(getMaxWidth(), 0.0) <= 0 || Double.compare(getMaxHeight(), 0.0) <= 0) { setMaxSize(MAXIMUM_WIDTH, MAXIMUM_HEIGHT); } } private void initGraphics() { Font.loadFont(getClass().getResourceAsStream("/eu/hansolo/enzo/fonts/digital.ttf"), (0.5833333333 * PREFERRED_HEIGHT)); // "Digital-7" //Font.loadFont(getClass().getResourceAsStream("/eu/hansolo/enzo/fonts/digitalreadout.ttf"), (0.5833333333 * PREFERRED_HEIGHT)); // "Digital Readout Upright" canvasBkg = new Canvas(PREFERRED_WIDTH, PREFERRED_HEIGHT); ctxBkg = canvasBkg.getGraphicsContext2D(); canvasFg = new Canvas(PREFERRED_WIDTH, PREFERRED_HEIGHT); ctxFg = canvasFg.getGraphicsContext2D(); pane = new Pane(); pane.getChildren().setAll(canvasBkg, canvasFg); getChildren().setAll(pane); resize(); } private void registerListeners() { widthProperty().addListener(observable -> handleControlPropertyChanged("RESIZE")); heightProperty().addListener(observable -> handleControlPropertyChanged("RESIZE")); hColor.addListener(observable -> handleControlPropertyChanged("REDRAW")); mColor.addListener(observable -> handleControlPropertyChanged("REDRAW")); m5Color.addListener(observable -> handleControlPropertyChanged("REDRAW")); sColor.addListener(observable -> handleControlPropertyChanged("REDRAW")); timeColor.addListener(observable -> handleControlPropertyChanged("REDRAW")); dateColor.addListener(observable -> handleControlPropertyChanged("REDRAW")); alarmOn.addListener(observable -> handleControlPropertyChanged("REDRAW")); dateVisible.addListener(observable -> handleControlPropertyChanged("REDRAW")); alarmVisible.addListener(observable -> handleControlPropertyChanged("REDRAW")); } // ******************** Methods ******************************************* protected void handleControlPropertyChanged(final String PROPERTY) { if ("RESIZE".equals(PROPERTY)) { resize(); } else if ("REDRAW".equals(PROPERTY)) { timer.stop(); drawBackground(); drawForeground(); timer.start(); } } @Override protected double computePrefWidth(final double PREF_HEIGHT) { double prefHeight = PREFERRED_HEIGHT; if (PREF_HEIGHT != -1) { prefHeight = Math.max(0, PREF_HEIGHT - getInsets().getTop() - getInsets().getBottom()); } return super.computePrefWidth(prefHeight); } @Override protected double computePrefHeight(final double PREF_WIDTH) { double prefWidth = PREFERRED_WIDTH; if (PREF_WIDTH != -1) { prefWidth = Math.max(0, PREF_WIDTH - getInsets().getLeft() - getInsets().getRight()); } return super.computePrefWidth(prefWidth); } @Override protected double computeMinWidth(final double MIN_HEIGHT) { return super.computeMinWidth(Math.max(MINIMUM_HEIGHT, MIN_HEIGHT - getInsets().getTop() - getInsets().getBottom())); } @Override protected double computeMinHeight(final double MIN_WIDTH) { return super.computeMinHeight(Math.max(MINIMUM_WIDTH, MIN_WIDTH - getInsets().getLeft() - getInsets().getRight())); } @Override protected double computeMaxWidth(final double MAX_HEIGHT) { return super.computeMaxWidth(Math.min(MAXIMUM_HEIGHT, MAX_HEIGHT - getInsets().getTop() - getInsets().getBottom())); } @Override protected double computeMaxHeight(final double MAX_WIDTH) { return super.computeMaxHeight(Math.min(MAXIMUM_WIDTH, MAX_WIDTH - getInsets().getLeft() - getInsets().getRight())); } public final Color getHourColor() { return hColor.get(); } public final void setHourColor(final Color HOUR_COLOR) { hColor.set(HOUR_COLOR); } public final ObjectProperty<Color> hourColorProperty() { return hColor; } public final Color getMinuteColor() { return mColor.get(); } public final void setMinuteColor(final Color MINUTE_COLOR) { mColor.set(MINUTE_COLOR); } public final ObjectProperty<Color> minuteColorProperty() { return mColor; } public final Color getMinute5Color() { return m5Color.get(); } public final void setMinute5Color(final Color MINUTE_5_COLOR) { m5Color.set(MINUTE_5_COLOR); } public final ObjectProperty<Color> minute5ColorProperty() { return m5Color; } public final Color getSecondColor() { return sColor.get(); } public final void setSecondColor(final Color SECOND_COLOR) { sColor.set(SECOND_COLOR); } public final ObjectProperty<Color> secondColorProperty() { return sColor; } public final Color getTimeColor() { return timeColor.get(); } public final void setTimeColor(final Color TIME_COLOR) { timeColor.set(TIME_COLOR); } public final ObjectProperty<Color> timeColorProperty() { return timeColor; } public final Color getDateColor() { return dateColor.get(); } public final void setDateColor(final Color DATE_COLOR) { dateColor.set(DATE_COLOR); } public final ObjectProperty<Color> dateColorProperty() { return dateColor; } public final boolean isAlarmOn() { return alarmOn.get(); } public final void setAlarmOn(final boolean ALARM_ON) { alarmOn.set(ALARM_ON); } public final BooleanProperty alarmOnProperty() { return alarmOn; } public final LocalTime getAlarm() { return alarm.get(); } public final void setAlarm(final LocalTime ALARM) { alarm.set(ALARM); } public final ObjectProperty<LocalTime> alarmProperty() { return alarm; } public final boolean isDateVisible() { return dateVisible.get(); } public final void setDateVisible(final boolean DATE_VISIBLE) { dateVisible.set(DATE_VISIBLE); } public final BooleanProperty dateVisibleProperty() { return dateVisible; } public final boolean isAlarmVisible() { return alarmVisible.get(); } public final void setAlarmVisible(final boolean ALARM_VISIBLE) { alarmVisible.set(ALARM_VISIBLE); } public final BooleanProperty alarmVisibleProperty() { return alarmVisible; } public final void setColor(final Color COLOR) { setHourColor(COLOR); setMinuteColor(Color.color(COLOR.getRed(), COLOR.getGreen(), COLOR.getBlue(), 0.6)); setMinute5Color(COLOR); setSecondColor(COLOR); setTimeColor(COLOR); setDateColor(COLOR); } private void fireAlarmEvent() { fireEvent(new AlarmEvent(this, this, AlarmEvent.ALARM)); } private void drawForeground() { int hourCounter = 1; int minCounter = 1; int secCounter = 1; double strokeWidth = size * 0.06; ctxFg.setLineCap(StrokeLineCap.BUTT); ctxFg.clearRect(0, 0, size, size); for (int i = 450 ; i >= 90 ; i--) { ctxFg.save(); if (i % 6 == 0) { // draw minutes if (minCounter <= minutes) { ctxFg.setStroke(minCounter % 5 == 0 ? getMinute5Color() : getMinuteColor()); ctxFg.setLineWidth(strokeWidth); ctxFg.strokeArc(strokeWidth * 0.5 + strokeWidth * 1.1, strokeWidth * 0.5 + strokeWidth * 1.1, size - strokeWidth - strokeWidth * 2.2, size - strokeWidth - strokeWidth * 2.2, i + 1 - 6, 4, ArcType.OPEN); minCounter++; } // draw seconds if (secCounter <= seconds + 1) { ctxFg.setStroke(getSecondColor()); ctxFg.setLineWidth(strokeWidth * 0.25); ctxFg.strokeArc(strokeWidth * 0.5 + strokeWidth * 1.8, strokeWidth * 0.5 + strokeWidth * 1.8, size - strokeWidth - strokeWidth * 3.6, size - strokeWidth - strokeWidth * 3.6, i + 1 - 6, 4, ArcType.OPEN); secCounter++; } } if (i % 30 == 0) { //draw hours ctxFg.setStroke(getHourColor()); ctxFg.setLineWidth(strokeWidth); if (hours == 0 || hours == 12) { ctxFg.strokeArc(strokeWidth * 0.5, strokeWidth * 0.5, size - strokeWidth, size - strokeWidth, i + 1 - 30, 28, ArcType.OPEN); } else if (hourCounter <= (pm ? hours - 12 : hours)) { ctxFg.strokeArc(strokeWidth * 0.5, strokeWidth * 0.5, size - strokeWidth, size - strokeWidth, i + 1 - 30, 28, ArcType.OPEN); hourCounter++; } } ctxFg.restore(); } // draw the time ctxFg.setFill(getTimeColor()); font = Font.font("Digital-7", (0.2 * size)); //font = Font.font("Digital Readout Upright", (0.2 * size)); //font = Font.font("Digital Readout Thick Upright", (0.2 * size)); ctxFg.setTextBaseline(VPos.CENTER); ctxFg.setTextAlign(TextAlignment.CENTER); ctxFg.setFont(font); ctxFg.fillText(time.toString(), size * 0.5, size * 0.5); // draw the date if (isDateVisible()) { ctxFg.setFill(getDateColor()); font = Font.font("Digital-7", (0.09 * size)); ctxFg.setFont(font); ctxFg.fillText(LocalDate.now().format(DateTimeFormatter.ISO_DATE), size * 0.5, size * 0.65); } // draw the alarmOn icon if (isAlarmVisible() &&isAlarmOn()) drawAlarmIcon(ctxFg, ctxFg.getFill()); } private void drawBackground() { double strokeWidth = size * 0.06; ctxBkg.setLineCap(StrokeLineCap.BUTT); ctxBkg.clearRect(0, 0, size, size); // draw translucent background ctxBkg.setStroke(Color.rgb(0, 12, 6, 0.1)); for (int i = 0 ; i < 360 ; i++) { ctxBkg.save(); if (i % 6 == 0) { // draw minutes ctxBkg.setStroke(Color.color(mColor.get().getRed(), mColor.get().getGreen(), mColor.get().getBlue(), 0.1)); ctxBkg.setLineWidth(strokeWidth); ctxBkg.strokeArc(strokeWidth * 0.5 + strokeWidth * 1.1, strokeWidth * 0.5 + strokeWidth * 1.1, size - strokeWidth - strokeWidth * 2.2, size - strokeWidth - strokeWidth * 2.2, i + 1, 4, ArcType.OPEN); // draw seconds ctxBkg.setStroke(Color.color(sColor.get().getRed(), sColor.get().getGreen(), sColor.get().getBlue(), 0.1)); ctxBkg.setLineWidth(strokeWidth * 0.25); ctxBkg.strokeArc(strokeWidth * 0.5 + strokeWidth * 1.8, strokeWidth * 0.5 + strokeWidth * 1.8, size - strokeWidth - strokeWidth * 3.6, size - strokeWidth - strokeWidth * 3.6, i + 1, 4, ArcType.OPEN); } if (i % 30 == 0) { //draw hours ctxBkg.setStroke(Color.color(hColor.get().getRed(), hColor.get().getGreen(), hColor.get().getBlue(), 0.1)); ctxBkg.setLineWidth(strokeWidth); ctxBkg.strokeArc(strokeWidth * 0.5, strokeWidth * 0.5, size - strokeWidth, size - strokeWidth, i + 1, 28, ArcType.OPEN); } ctxBkg.restore(); } // draw the time ctxBkg.setFill(Color.color(timeColor.get().getRed(), timeColor.get().getGreen(), timeColor.get().getBlue(), 0.1)); font = Font.font("Digital-7", (0.2 * size)); //font = Font.font("Digital Readout Upright", (0.2 * size)); //font = Font.font("Digital Readout Thick Upright", (0.2 * size)); ctxBkg.setTextBaseline(VPos.CENTER); ctxBkg.setTextAlign(TextAlignment.CENTER); ctxBkg.setFont(font); ctxBkg.fillText("88:88", size * 0.5, size * 0.5); // draw the date if (isDateVisible()) { ctxBkg.setFill(Color.color(dateColor.get().getRed(), dateColor.get().getGreen(), dateColor.get().getBlue(), 0.1)); font = Font.font("Digital-7", (0.09 * size)); ctxBkg.setFont(font); ctxBkg.fillText("8888-88-88", size * 0.5, size * 0.65); } // draw the alarmOn icon if (isAlarmVisible() && !isAlarmOn()) drawAlarmIcon(ctxBkg, ctxBkg.getFill()); } private void drawAlarmIcon(final GraphicsContext CTX, final Paint COLOR) { double iconSize = 0.1 * size; CTX.save(); CTX.translate((size - iconSize) * 0.5, size * 0.25); CTX.beginPath(); CTX.moveTo(0.6875 * iconSize, 0.875 * iconSize); CTX.bezierCurveTo(0.625 * iconSize, 0.9375 * iconSize, 0.5625 * iconSize, iconSize, 0.5 * iconSize, iconSize); CTX.bezierCurveTo(0.4375 * iconSize, iconSize, 0.375 * iconSize, 0.9375 * iconSize, 0.375 * iconSize, 0.875 * iconSize); CTX.bezierCurveTo(0.375 * iconSize, 0.875 * iconSize, 0.6875 * iconSize, 0.875 * iconSize, 0.6875 * iconSize, 0.875 * iconSize); CTX.closePath(); CTX.moveTo(iconSize, 0.8125 * iconSize); CTX.bezierCurveTo(0.6875 * iconSize, 0.5625 * iconSize, 0.9375 * iconSize, 0.0, 0.5 * iconSize, 0.0); CTX.bezierCurveTo(0.5 * iconSize, 0.0, 0.5 * iconSize, 0.0, 0.5 * iconSize, 0.0); CTX.bezierCurveTo(0.5 * iconSize, 0.0, 0.5 * iconSize, 0.0, 0.5 * iconSize, 0.0); CTX.bezierCurveTo(0.125 * iconSize, 0.0, 0.375 * iconSize, 0.5625 * iconSize, 0.0, 0.8125 * iconSize); CTX.bezierCurveTo(0.0, 0.8125 * iconSize, 0.0, 0.8125 * iconSize, 0.0, 0.8125 * iconSize); CTX.bezierCurveTo(0.0, 0.8125 * iconSize, 0.0, 0.8125 * iconSize, 0.0, 0.8125 * iconSize); CTX.bezierCurveTo(0.0, 0.8125 * iconSize, 0.0, 0.8125 * iconSize, 0.0625 * iconSize, 0.8125 * iconSize); CTX.bezierCurveTo(0.0625 * iconSize, 0.8125 * iconSize, 0.5 * iconSize, 0.8125 * iconSize, 0.5 * iconSize, 0.8125 * iconSize); CTX.bezierCurveTo(0.5 * iconSize, 0.8125 * iconSize, iconSize, 0.8125 * iconSize, iconSize, 0.8125 * iconSize); CTX.bezierCurveTo(iconSize, 0.8125 * iconSize, iconSize, 0.8125 * iconSize, iconSize, 0.8125 * iconSize); CTX.bezierCurveTo(iconSize, 0.8125 * iconSize, iconSize, 0.8125 * iconSize, iconSize, 0.8125 * iconSize); CTX.bezierCurveTo(iconSize, 0.8125 * iconSize, iconSize, 0.8125 * iconSize, iconSize, 0.8125 * iconSize); CTX.closePath(); CTX.setFill(COLOR); CTX.fill(); CTX.restore(); } // ******************** Resizing ****************************************** private void resize() { size = getWidth() < getHeight() ? getWidth() : getHeight(); width = getWidth(); height = getHeight(); if (width > 0 && height > 0) { canvasBkg.setWidth(size); canvasBkg.setHeight(size); canvasFg.setWidth(size); canvasFg.setHeight(size); drawBackground(); drawForeground(); } } }