-
-
Save jewelsea/2658491 to your computer and use it in GitHub Desktop.
/** clock.css (place input same directory as Clock.java and ensure build script copies the file to the same location as Clock.class) */ | |
.root { | |
-fx-background-color: linear-gradient(to bottom, cornsilk, wheat); | |
-fx-padding: 12; | |
-fx-background-insets: 5; | |
-fx-effect: dropshadow(three-pass-box, wheat, 10, 0, 0, 0); | |
} | |
#face { | |
-fx-fill: radial-gradient(radius 180%, burlywood, derive(burlywood, -30%), derive(burlywood, 30%)); | |
-fx-stroke: derive(burlywood, -45%); | |
-fx-stroke-width: 5; | |
-fx-effect: dropshadow(three-pass-box, grey, 10, 0, 4, 4); | |
} | |
#brand { | |
-fx-font-size: 14px; | |
} | |
#hourHand { | |
-fx-stroke: darkslategray; | |
-fx-stroke-width: 4; | |
-fx-stroke-line-cap: round; | |
} | |
#minuteHand { | |
-fx-stroke: derive(darkslategray, -5%); | |
-fx-stroke-width: 3; | |
-fx-stroke-line-cap: round; | |
} | |
#secondHand { | |
-fx-stroke: derive(firebrick, -15%); | |
-fx-stroke-width: 2; | |
-fx-stroke-line-cap: round; | |
} | |
#spindle { | |
-fx-fill: derive(darkslategray, +5%); | |
} | |
#digitalClock { | |
-fx-font-size: 14px; | |
-fx-font-family: 'Courier New'; | |
} | |
.tick { | |
-fx-stroke: derive(darkgoldenrod, -15%); | |
-fx-stroke-width: 3; | |
-fx-stroke-line-cap: round; | |
} |
import javafx.animation.*; | |
import javafx.application.Application; | |
import javafx.event.*; | |
import javafx.geometry.Pos; | |
import javafx.scene.*; | |
import javafx.scene.control.Label; | |
import javafx.scene.effect.Glow; | |
import javafx.scene.input.MouseEvent; | |
import javafx.scene.layout.VBox; | |
import javafx.scene.paint.Color; | |
import javafx.scene.shape.*; | |
import javafx.scene.transform.Rotate; | |
import javafx.stage.*; | |
import javafx.util.Duration; | |
import java.util.*; | |
/** Note that this clock does not keep perfect time, but is close. | |
It's main purpose is to demonstrate various features of JavaFX. */ | |
public class Clock extends Application { | |
public static void main(String[] args) throws Exception { launch(args); } | |
public void start(final Stage stage) throws Exception { | |
// construct the analogueClock pieces. | |
final Circle face = new Circle(100, 100, 100); | |
face.setId("face"); | |
final Label brand = new Label("Splotch"); | |
brand.setId("brand"); | |
brand.layoutXProperty().bind(face.centerXProperty().subtract(brand.widthProperty().divide(2))); | |
brand.layoutYProperty().bind(face.centerYProperty().add(face.radiusProperty().divide(2))); | |
final Line hourHand = new Line(0, 0, 0, -50); | |
hourHand.setTranslateX(100); hourHand.setTranslateY(100); | |
hourHand.setId("hourHand"); | |
final Line minuteHand = new Line(0, 0, 0, -75); | |
minuteHand.setTranslateX(100); minuteHand.setTranslateY(100); | |
minuteHand.setId("minuteHand"); | |
final Line secondHand = new Line(0, 15, 0, -88); | |
secondHand.setTranslateX(100); secondHand.setTranslateY(100); | |
secondHand.setId("secondHand"); | |
final Circle spindle = new Circle(100, 100, 5); | |
spindle.setId("spindle"); | |
Group ticks = new Group(); | |
for (int i = 0; i < 12; i++) { | |
Line tick = new Line(0, -83, 0, -93); | |
tick.setTranslateX(100); tick.setTranslateY(100); | |
tick.getStyleClass().add("tick"); | |
tick.getTransforms().add(new Rotate(i * (360 / 12))); | |
ticks.getChildren().add(tick); | |
} | |
final Group analogueClock = new Group(face, brand, ticks, spindle, hourHand, minuteHand, secondHand); | |
// construct the digitalClock pieces. | |
final Label digitalClock = new Label(); | |
digitalClock.setId("digitalClock"); | |
// determine the starting time. | |
Calendar calendar = GregorianCalendar.getInstance(); | |
final double seedSecondDegrees = calendar.get(Calendar.SECOND) * (360 / 60); | |
final double seedMinuteDegrees = (calendar.get(Calendar.MINUTE) + seedSecondDegrees / 360.0) * (360 / 60); | |
final double seedHourDegrees = (calendar.get(Calendar.HOUR) + seedMinuteDegrees / 360.0) * (360 / 12) ; | |
// define rotations to map the analogueClock to the current time. | |
final Rotate hourRotate = new Rotate(seedHourDegrees); | |
final Rotate minuteRotate = new Rotate(seedMinuteDegrees); | |
final Rotate secondRotate = new Rotate(seedSecondDegrees); | |
hourHand.getTransforms().add(hourRotate); | |
minuteHand.getTransforms().add(minuteRotate); | |
secondHand.getTransforms().add(secondRotate); | |
// the hour hand rotates twice a day. | |
final Timeline hourTime = new Timeline( | |
new KeyFrame( | |
Duration.hours(12), | |
new KeyValue( | |
hourRotate.angleProperty(), | |
360 + seedHourDegrees, | |
Interpolator.LINEAR | |
) | |
) | |
); | |
// the minute hand rotates once an hour. | |
final Timeline minuteTime = new Timeline( | |
new KeyFrame( | |
Duration.minutes(60), | |
new KeyValue( | |
minuteRotate.angleProperty(), | |
360 + seedMinuteDegrees, | |
Interpolator.LINEAR | |
) | |
) | |
); | |
// move second hand rotates once a minute. | |
final Timeline secondTime = new Timeline( | |
new KeyFrame( | |
Duration.seconds(60), | |
new KeyValue( | |
secondRotate.angleProperty(), | |
360 + seedSecondDegrees, | |
Interpolator.LINEAR | |
) | |
) | |
); | |
// the digital clock updates once a second. | |
final Timeline digitalTime = new Timeline( | |
new KeyFrame(Duration.seconds(0), | |
new EventHandler<ActionEvent>() { | |
@Override public void handle(ActionEvent actionEvent) { | |
Calendar calendar = GregorianCalendar.getInstance(); | |
String hourString = pad(2, '0', calendar.get(Calendar.HOUR) == 0 ? "12" : calendar.get(Calendar.HOUR) + ""); | |
String minuteString = pad(2, '0', calendar.get(Calendar.MINUTE) + ""); | |
String secondString = pad(2, '0', calendar.get(Calendar.SECOND) + ""); | |
String ampmString = calendar.get(Calendar.AM_PM) == Calendar.AM ? "AM" : "PM"; | |
digitalClock.setText(hourString + ":" + minuteString + ":" + secondString + " " + ampmString); | |
} | |
} | |
), | |
new KeyFrame(Duration.seconds(1)) | |
); | |
// time never ends. | |
hourTime.setCycleCount(Animation.INDEFINITE); | |
minuteTime.setCycleCount(Animation.INDEFINITE); | |
secondTime.setCycleCount(Animation.INDEFINITE); | |
digitalTime.setCycleCount(Animation.INDEFINITE); | |
// start the analogueClock. | |
digitalTime.play(); | |
secondTime.play(); | |
minuteTime.play(); | |
hourTime.play(); | |
stage.initStyle(StageStyle.TRANSPARENT); | |
// add a glow effect whenever the mouse is positioned over the clock. | |
final Glow glow = new Glow(); | |
analogueClock.setOnMouseEntered(new EventHandler<MouseEvent>() { | |
@Override public void handle(MouseEvent mouseEvent) { | |
analogueClock.setEffect(glow); | |
} | |
}); | |
analogueClock.setOnMouseExited(new EventHandler<MouseEvent>() { | |
@Override public void handle(MouseEvent mouseEvent) { | |
analogueClock.setEffect(null); | |
} | |
}); | |
// fade out the scene and shut it down when the mouse is clicked on the clock. | |
analogueClock.setOnMouseClicked(new EventHandler<MouseEvent>() { | |
@Override public void handle(MouseEvent mouseEvent) { | |
analogueClock.setMouseTransparent(true); | |
FadeTransition fade = new FadeTransition(Duration.seconds(1.2), analogueClock); | |
fade.setOnFinished(new EventHandler<ActionEvent>() { | |
@Override public void handle(ActionEvent actionEvent) { | |
stage.close(); | |
} | |
}); | |
fade.setFromValue(1); | |
fade.setToValue(0); | |
fade.play(); | |
} | |
}); | |
// layout the scene. | |
final VBox layout = new VBox(); | |
layout.getChildren().addAll(analogueClock, digitalClock); | |
layout.setAlignment(Pos.CENTER); | |
final Scene scene = new Scene(layout, Color.TRANSPARENT); | |
scene.getStylesheets().add(getResource("clock.css")); | |
stage.setScene(scene); | |
// allow the clock background to be used to drag the clock around. | |
final Delta dragDelta = new Delta(); | |
layout.setOnMousePressed(new EventHandler<MouseEvent>() { | |
@Override public void handle(MouseEvent mouseEvent) { | |
// record a delta distance for the drag and drop operation. | |
dragDelta.x = stage.getX() - mouseEvent.getScreenX(); | |
dragDelta.y = stage.getY() - mouseEvent.getScreenY(); | |
scene.setCursor(Cursor.MOVE); | |
} | |
}); | |
layout.setOnMouseReleased(new EventHandler<MouseEvent>() { | |
@Override public void handle(MouseEvent mouseEvent) { | |
scene.setCursor(Cursor.HAND); | |
} | |
}); | |
layout.setOnMouseDragged(new EventHandler<MouseEvent>() { | |
@Override public void handle(MouseEvent mouseEvent) { | |
stage.setX(mouseEvent.getScreenX() + dragDelta.x); | |
stage.setY(mouseEvent.getScreenY() + dragDelta.y); | |
} | |
}); | |
layout.setOnMouseEntered(new EventHandler<MouseEvent>() { | |
@Override public void handle(MouseEvent mouseEvent) { | |
if (!mouseEvent.isPrimaryButtonDown()) { | |
scene.setCursor(Cursor.HAND); | |
} | |
} | |
}); | |
layout.setOnMouseExited(new EventHandler<MouseEvent>() { | |
@Override public void handle(MouseEvent mouseEvent) { | |
if (!mouseEvent.isPrimaryButtonDown()) { | |
scene.setCursor(Cursor.DEFAULT); | |
} | |
} | |
}); | |
// show the scene. | |
stage.show(); | |
} | |
private String pad(int fieldWidth, char padChar, String s) { | |
StringBuilder sb = new StringBuilder(); | |
for (int i = s.length(); i < fieldWidth; i++) { | |
sb.append(padChar); | |
} | |
sb.append(s); | |
return sb.toString(); | |
} | |
static String getResource(String path) { | |
return Clock.class.getResource(path).toExternalForm(); | |
} | |
// records relative x and y co-ordinates. | |
class Delta { double x, y; } | |
} |
In response to Per Lundholm's criticism of the coding style used in this example, I also created a refactored version of this Clock demonstration code which follows the principles espoused in Per's blog entry on JavaFX coding style. Should you have time, I'd appreciate it if you can view both this code version and the refactored version and comment on Per's blogs as to which coding style you prefer and why.
Hello,
I just want to thank you very much. This example really helped me to understand the structure of a clock and the basics of rotation.
I mean it's now nearly six years ago you posted this, but hey thank you so much!!
VERY GOOD. WORTH REVIEWING.
I was searching Google for JavaFX clock implementations and yours is the best I found.
I forked this Gist and added the following features to the clock:
- Time zone location
- Latitude & longitude geo coordinates
- Day of the week
- Latest weather reports
It is available here: JavaFXClock
That is a nice feature set @antic-ml. The refactored code, may have been a better starting point for the fork as that code is better organized.
This is amazing though, good Job
Super!!! Thank you!
"How to draw a clock with JavaFX 2?"
http://stackoverflow.com/questions/10541738/how-to-draw-a-clock-with-javafx-2