Skip to content

Commit

Permalink
CSS live reload
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeSimcoe committed Sep 20, 2024
1 parent 35960c2 commit 9dd08ed
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 5 deletions.
34 changes: 34 additions & 0 deletions docs/modules/ROOT/pages/includes/quarkus-fx.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,38 @@ endif::add-copy-button-to-env-var[]
--|string
|`/`


a|icon:lock[title=Fixed at build time] [[quarkus-fx_quarkus-fx-main-resources]]`link:#quarkus-fx_quarkus-fx-main-resources[quarkus.fx.main-resources]`


[.description]
--
Location for main resources (allowing stylesheet live reload)

ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_FX_MAIN_RESOURCES+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++QUARKUS_FX_MAIN_RESOURCES+++`
endif::add-copy-button-to-env-var[]
--|string
|`src/main/resources/`


a|icon:lock[title=Fixed at build time] [[quarkus-fx_quarkus-fx-test-resources]]`link:#quarkus-fx_quarkus-fx-test-resources[quarkus.fx.test-resources]`


[.description]
--
Location for test resources (allowing stylesheet live reload)

ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_FX_TEST_RESOURCES+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++QUARKUS_FX_TEST_RESOURCES+++`
endif::add-copy-button-to-env-var[]
--|string
|`src/test/resources/`

|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.quarkiverse.fx.style;

import java.io.IOException;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

import javafx.application.Platform;
import javafx.collections.ObservableList;

/**
* This utility class allows live CSS reload by watching filesystem changes
* and re-setting the stylesheet upon change.
* It is automatically used in dev mode for all {@link io.quarkiverse.fx.views.FxView}
*/
public final class StylesheetWatchService {

private StylesheetWatchService() {
// Utility class
}

public static void setStyleAndStartWatchingTask(
final Supplier<ObservableList<String>> stylesheetsSupplier,
final String stylesheet) throws IOException {

// CSS live change monitoring
// Get stylesheet URL from disk (project root)
Path path = Path.of(stylesheet);
URL url = path.toUri().toURL();
WatchService watchService = FileSystems.getDefault().newWatchService();
path.getParent().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

ObservableList<String> stylesheets = stylesheetsSupplier.get();
String stylesheetExternalForm = url.toExternalForm();
updateWithStylesheet(stylesheetExternalForm, stylesheets);

CompletableFuture.runAsync(() -> {
try {
performBlockingWatch(watchService, stylesheets, stylesheetExternalForm);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
});
}

private static void performBlockingWatch(
final WatchService watchService,
final ObservableList<String> stylesheets,
final String stylesheet) throws InterruptedException {

WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
// Reload CSS in FX thread
updateWithStylesheet(stylesheet, stylesheets);
}
key.reset();
}
}

private static void updateWithStylesheet(final String stylesheet, final ObservableList<String> stylesheets) {
Platform.runLater(() -> stylesheets.setAll(stylesheet));
}
}
12 changes: 12 additions & 0 deletions runtime/src/main/java/io/quarkiverse/fx/views/FxViewConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,16 @@ public interface FxViewConfig {
*/
@WithDefault("/")
String bundleRoot();

/**
* Location for main resources (allowing stylesheet live reload)
*/
@WithDefault("src/main/resources/")
String mainResources();

/**
* Location for test resources (allowing stylesheet live reload)
*/
@WithDefault("src/test/resources/")
String testResources();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
Expand All @@ -19,6 +21,8 @@
import org.jboss.logging.Logger;

import io.quarkiverse.fx.FxViewLoadEvent;
import io.quarkiverse.fx.style.StylesheetWatchService;
import io.quarkus.runtime.LaunchMode;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;

Expand Down Expand Up @@ -49,6 +53,9 @@ public void setViewNames(final List<String> views) {
*/
void setupViews(@Observes final FxViewLoadEvent event) {

LaunchMode launchMode = LaunchMode.current();
boolean devOrTest = launchMode.isDevOrTest();

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

for (String name : this.viewNames) {
Expand All @@ -72,10 +79,21 @@ void setupViews(@Observes final FxViewLoadEvent event) {
}

// Style
String style = null;
LOGGER.debugf("Attempting to load css %s", css);
URL styleResource = classLoader.getResource(css);
if (styleResource != null) {
LOGGER.debugf("Found css %s", css);
if (devOrTest) {
String directory = launchMode == LaunchMode.DEVELOPMENT ? this.config.mainResources()
: this.config.testResources();
Path devPath = Paths.get(directory + css);
if (devPath.toFile().exists()) {
style = devPath.toString();
}
} else {
URL styleResource = classLoader.getResource(css);
if (styleResource != null) {
LOGGER.debugf("Found css %s", css);
style = styleResource.toExternalForm();
}
}

// FXML
Expand All @@ -95,8 +113,14 @@ void setupViews(@Observes final FxViewLoadEvent event) {
loader.setLocation(url);

Parent rootNode = loader.load(stream);
if (styleResource != null) {
rootNode.getStylesheets().add(styleResource.toExternalForm());
if (style != null) {
if (devOrTest) {
// Stylesheet live reload in dev mode
StylesheetWatchService.setStyleAndStartWatchingTask(rootNode::getStylesheets, style);
} else {
// Regular setting (no live reload)
rootNode.getStylesheets().add(style);
}
}

Object controller = loader.getController();
Expand Down
2 changes: 2 additions & 0 deletions samples/fxviews/src/main/resources/style/custom-sample.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Label {
-fx-font-size: 2em;
-fx-font-weight: bold;
-fx-text-fill: blue;
}

0 comments on commit 9dd08ed

Please sign in to comment.