diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b931840..e7acc9d 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - java: [11, 15, 16-ea] + java: [15, 16-ea] fail-fast: false max-parallel: 4 name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} diff --git a/README.md b/README.md index 72a33c7..dfe473e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # World Clock - A JavaFX World Clock -Welcome to the World Clock application! This project is for a series of blogs at foojay.io -https://foojay.io/today/creating-a-javafx-world-clock-from-scratch-part-1/ +Welcome to the World Clock application! This project is for a series of blog entries at https://foojay.io +https://foojay.io/today/creating-a-javafx-world-clock-from-scratch-part-1 This is a standard Maven JavaFX modular (JPMS) application. ## Requirements: -- Maven -- Java 15 w/JavaFX (ZuluFX) https://www.azul.com/downloads/zulu-community/?package=jdk-fx +- Maven 3.6.3 or greater +- Java 15+ ## Run World Clock diff --git a/pom.xml b/pom.xml index f7e4f0f..8c21d22 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 1.0-SNAPSHOT UTF-8 - 11 + 15 15 @@ -27,9 +27,14 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 - - ${maven.compiler.release} - + + + default-compile + + ${maven.compiler.release} + + + org.openjfx diff --git a/src/main/java/com/carlfx/worldclock/App.java b/src/main/java/com/carlfx/worldclock/App.java index 44b7a57..9512a29 100644 --- a/src/main/java/com/carlfx/worldclock/App.java +++ b/src/main/java/com/carlfx/worldclock/App.java @@ -7,28 +7,35 @@ import javafx.application.Application; import javafx.application.Platform; import javafx.beans.property.LongProperty; import javafx.beans.property.SimpleLongProperty; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.fxml.FXMLLoader; import javafx.geometry.Point2D; +import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; - import javafx.scene.text.Font; import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.util.Duration; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; /** * JavaFX World Clock + * + * Creative commons attribution: + * Font Awesome was used for buttons https://fontawesome.com/license */ public class App extends Application { @@ -41,6 +48,7 @@ public class App extends Application { "Roboto-Medium.ttf", "RobotoMono-Medium.ttf" }; + @Override public void init() throws Exception { super.init(); @@ -53,18 +61,23 @@ public class App extends Application { T childNode = (T) node.lookup("#"+id); return childNode; } - Location[] locations = new Location[] { - new USLocation("GMT-5", "Pasadena, MD", "US", 32.0f, Location.TEMP_STD.FAHRENHEIT), - new USLocation("GMT-8", "Sunnyvale, CA", "US", 60.0f, Location.TEMP_STD.FAHRENHEIT), - new Location("GMT+1", "Amsterdam", "NL", 4.0f, Location.TEMP_STD.CELSIUS), - new Location("GMT+1", "Münster", "DE",5.0f, Location.TEMP_STD.CELSIUS) - }; + static ObservableList locations = FXCollections.observableArrayList(); + + @Override public void start(Stage stage) throws IOException { stage.initStyle(StageStyle.TRANSPARENT); stage.setOpacity(.75); this.stage = stage; + // fake data + locations.addAll( + new USLocation("GMT-5", "Pasadena, MD", "US", 32.0f, Location.TEMP_STD.FAHRENHEIT), + new USLocation("GMT-8", "Sunnyvale, CA", "US", 60.0f, Location.TEMP_STD.FAHRENHEIT), + new Location("GMT+1", "Amsterdam", "NL", 4.0f, Location.TEMP_STD.CELSIUS), + new Location("GMT+1", "Münster", "DE",5.0f, Location.TEMP_STD.CELSIUS) + ); + SimpleLongProperty epochTime = new SimpleLongProperty(new Date().getTime()); // each tick update epoch property Timeline timeline = new Timeline( @@ -79,9 +92,14 @@ public class App extends Application { // each controller will attach a listener when epochTime changes. clocks.add(loadClockFXML(location, epochTime)); } - + BorderPane windowContainer = new BorderPane(); + windowContainer.getStyleClass().add("window-container"); VBox clockList = new VBox(); - + Parent windowBar = loadWindowControlsFXML(locations); + BorderPane.setAlignment(windowBar, Pos.CENTER_RIGHT); + windowContainer.setTop(windowBar); + windowContainer.setCenter(clockList); + makeDraggable(windowContainer); // fake map ImageView mapImage = new ImageView(new Image(App.class.getResourceAsStream("Mapimage.png"))); @@ -90,9 +108,8 @@ public class App extends Application { .addAll(clocks); clockList.getChildren() .add(mapImage); - makeDraggable(clockList); - scene = new Scene(clockList); + scene = new Scene(windowContainer); scene.getStylesheets() .add(getClass() .getResource("styles.css") @@ -140,6 +157,19 @@ public class App extends Application { controller.init(location, epochTime); return parent; } + private static Parent loadWindowControlsFXML(ObservableList locations) throws IOException { + FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource("window-controls.fxml")); + Parent parent = fxmlLoader.load(); + WindowController controller = fxmlLoader.getController(); + controller.init(locations); + return parent; + } + + @Override + public void stop() { + Platform.exit(); + System.exit(0); + } public static void main(String[] args) { System.setProperty("prism.lcdtext", "false"); diff --git a/src/main/java/com/carlfx/worldclock/Location.java b/src/main/java/com/carlfx/worldclock/Location.java index 7eabf91..9465f15 100644 --- a/src/main/java/com/carlfx/worldclock/Location.java +++ b/src/main/java/com/carlfx/worldclock/Location.java @@ -48,10 +48,20 @@ public class Location { CELSIUS, FAHRENHEIT } - public Location(String timezone, String city, String countryCode, float temp, TEMP_STD tempType) { + + public Location(String timezone, String city, String countryCode) { this.timezone = timezone; this.city = city; this.countryCode = countryCode; + } + + public Location(String timezone, String city, String countryCode, double [] latLong) { + this(timezone, city, countryCode); + this.latLong = latLong; + } + + public Location(String timezone, String city, String countryCode, float temp, TEMP_STD tempType) { + this(timezone, city, countryCode); this.temperature = temp; this.tempType = tempType; } @@ -101,9 +111,19 @@ public class Location { } public void setLatLong(double[] latLong) { + if (latLong == null || latLong.length != 2) { + throw new IllegalArgumentException("Latitude and Longitude is a two element array of type double."); + } this.latLong = latLong; } + public double getLatitude() { + return this.latLong[0]; + } + public double getLongitude() { + return this.latLong[1]; + } + public Image getWeatherImage() { return weatherImage; } diff --git a/src/main/java/com/carlfx/worldclock/WindowController.java b/src/main/java/com/carlfx/worldclock/WindowController.java new file mode 100644 index 0000000..1e7af4d --- /dev/null +++ b/src/main/java/com/carlfx/worldclock/WindowController.java @@ -0,0 +1,49 @@ +package com.carlfx.worldclock; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.input.MouseEvent; +import javafx.stage.Stage; + +public class WindowController { + @FXML + private Button closeButton; + + @FXML + private Button configButton; + + private ObservableList locations; + + public void init(ObservableList locations) { + this.locations = locations; + } + + private Object targetButton; + + @FXML + private void handleEnterIngnoreAction(MouseEvent mouseEvent) { + targetButton = mouseEvent.getTarget(); + } + @FXML + private void handleExitIngnoreAction(MouseEvent mouseEvent) { + targetButton = null; + } + + @FXML + private void handleCloseWindowAction(MouseEvent mouseEvent) { + if (closeButton.equals(targetButton)) { + Stage stage = (Stage) closeButton + .getScene() + .getWindow(); + stage.close(); + } + } + + @FXML + private void handleConfigWorldClockAction(MouseEvent mouseEvent) { + if (configButton.equals(targetButton)) { + System.out.println("config button hit " + mouseEvent); + } + } +} diff --git a/src/main/resources/com/carlfx/worldclock/styles.css b/src/main/resources/com/carlfx/worldclock/styles.css index b4f441b..e2d0686 100644 --- a/src/main/resources/com/carlfx/worldclock/styles.css +++ b/src/main/resources/com/carlfx/worldclock/styles.css @@ -1,5 +1,5 @@ .root { - -fx-background-color:rgba(0,0,0,0.75); + -fx-background-color: transparent; } /* Not working look at App.java init() method */ @font-face { @@ -24,7 +24,9 @@ font-family: 'RobotoMono Medium'; src: url('RobotoMono-Medium.ttf'); } - +.window-container { + -fx-background-color: transparent; +} .clock-background { -fx-background-color: rgba(0,0,0,.90); } diff --git a/src/main/resources/com/carlfx/worldclock/window-controls.css b/src/main/resources/com/carlfx/worldclock/window-controls.css new file mode 100644 index 0000000..9b5cd39 --- /dev/null +++ b/src/main/resources/com/carlfx/worldclock/window-controls.css @@ -0,0 +1,31 @@ + + +.window-control-bar { + -fx-background-color: linear-gradient(from 0% 0% to 100% 100%, black 0%, #de752f 5%, black 100%); + +} + +.config-gear { + -fx-shape: "M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z"; + -fx-background-color: white; + -fx-min-height: 15; + -fx-min-width: 15; + -fx-max-height: 15; + -fx-max-width: 15; +} +.config-gear:hover { + -fx-background-color: "#2fdae0"; +} + +.window-close { + -fx-shape: "M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"; + -fx-background-color: white; + -fx-min-height: 15; + -fx-min-width: 15; + -fx-max-height: 15; + -fx-max-width: 15; +} + +.window-close:hover { + -fx-background-color: "#2fdae0"; +} \ No newline at end of file diff --git a/src/main/resources/com/carlfx/worldclock/window-controls.fxml b/src/main/resources/com/carlfx/worldclock/window-controls.fxml new file mode 100644 index 0000000..a47b28b --- /dev/null +++ b/src/main/resources/com/carlfx/worldclock/window-controls.fxml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + +