Skip to content

Commit 4e2d177

Browse files
Flex box implementation (#81)
* Added initial implementation for FlexBox. (Also added DynamicCSS as a subproject) * Various improvements in general - and an improved test application * Added the FlexItem
1 parent 8956567 commit 4e2d177

36 files changed

Lines changed: 3788 additions & 6 deletions

build.gradle

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,13 @@ configure(subprojects.findAll { it.name != 'example' }) {
148148
}
149149
}
150150

151-
configure([project("jpro-auth:core"), project("jpro-auth:routing"), project("jpro-file"),
152-
project("jpro-image-manager"), project("jpro-mail"), project("jpro-mdfx"), project("jpro-media"),
153-
project("jpro-scenegraph"), project("jpro-session"), project("jpro-sipjs"), project("jpro-utils"),
154-
project("jpro-html-scrollpane"), project("jpro-routing:core"), project("jpro-routing:dev"),
155-
project("jpro-routing:popup"), project("jpro-webrtc"), project("jpro-youtube"),
156-
rootProject]) {
151+
configure([project("jpro-auth:core"), project("jpro-auth:routing"), project("jpro-dynamic-css"),
152+
project("jpro-file"), project("jpro-flexbox"), project("jpro-image-manager"), project("jpro-mail"),
153+
project("jpro-mdfx"),
154+
project("jpro-media"), project("jpro-scenegraph"), project("jpro-session"), project("jpro-sipjs"),
155+
project("jpro-utils"), project("jpro-html-scrollpane"), project("jpro-routing:core"),
156+
project("jpro-routing:dev"), project("jpro-routing:popup"), project("jpro-webrtc"),
157+
project("jpro-youtube"), rootProject]) {
157158
apply plugin: 'java'
158159
apply plugin: 'maven-publish'
159160
apply plugin: 'signing'

jpro-dynamic-css/build.gradle

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
buildscript {
2+
repositories {
3+
mavenCentral()
4+
gradlePluginPortal()
5+
maven {
6+
url "https://sandec.jfrog.io/artifactory/repo"
7+
}
8+
}
9+
10+
dependencies {
11+
classpath "SANDEC:simplefx-plugin-gradle:$SIMPLEFX_VERSION"
12+
}
13+
}
14+
15+
apply plugin: 'de.sandec.simplefx'
16+
17+
dependencies {
18+
api "SANDEC:simplefx_2.13:$SIMPLEFX_VERSION"
19+
api "org.scala-lang:scala-library:2.13.18"
20+
}
21+
22+
publishing {
23+
publications {
24+
mavenJava(MavenPublication) {
25+
pom {
26+
name = 'JPro Dynamic CSS'
27+
description = 'Reactive dynamic CSS for JavaFX scenes and parents.'
28+
}
29+
}
30+
}
31+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module one.jpro.platform.dynamic.css {
2+
requires transitive javafx.graphics;
3+
requires transitive simplefx.core;
4+
requires transitive simplefx.wrapping;
5+
requires transitive scala.library;
6+
7+
exports one.jpro.platform.css;
8+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package one.jpro.platform.css;
2+
3+
import javafx.application.Platform;
4+
import javafx.scene.Parent;
5+
import javafx.scene.Scene;
6+
7+
import java.io.File;
8+
import java.io.IOException;
9+
import java.io.PrintWriter;
10+
import java.net.URL;
11+
import java.util.HashMap;
12+
import java.util.Map;
13+
import java.util.concurrent.atomic.AtomicInteger;
14+
15+
/**
16+
* Java-friendly API for applying dynamic CSS strings to a {@link Parent} or {@link Scene}.
17+
* <p>
18+
* CSS content is written to temporary files and registered as stylesheets.
19+
* Each call replaces the previous CSS for that target.
20+
* <p>
21+
* <strong>Usage:</strong>
22+
* <pre>{@code
23+
* DynamicCSSUtil.setCssString(scene, ".button { -fx-background-color: red; }");
24+
* // later:
25+
* DynamicCSSUtil.setCssString(scene, ".button { -fx-background-color: blue; }");
26+
* }</pre>
27+
*/
28+
public class DynamicCSSUtil {
29+
30+
private static final String KEY_PROP = "DynamicCSSUtil.key";
31+
private static final String URL_PROP = "DynamicCSSUtil.url";
32+
33+
private static final AtomicInteger counter = new AtomicInteger(0);
34+
private static final Map<String, File> files = new HashMap<>();
35+
private static final File tempDir;
36+
37+
static {
38+
try {
39+
tempDir = File.createTempFile("dynamiccss", "");
40+
tempDir.delete();
41+
tempDir.mkdirs();
42+
tempDir.deleteOnExit();
43+
} catch (IOException e) {
44+
throw new RuntimeException("Failed to create temp dir for dynamic CSS", e);
45+
}
46+
}
47+
48+
/**
49+
* Sets or updates a dynamic CSS string on a {@link Scene}.
50+
*/
51+
public static void setCssString(Scene scene, String css) {
52+
removePrevious(scene.getProperties(), scene.getStylesheets());
53+
if (css != null && !css.isEmpty()) {
54+
addNew(css, scene.getProperties(), scene.getStylesheets());
55+
}
56+
}
57+
58+
/**
59+
* Sets or updates a dynamic CSS string on a {@link Parent} node.
60+
*/
61+
public static void setCssString(Parent parent, String css) {
62+
removePrevious(parent.getProperties(), parent.getStylesheets());
63+
if (css != null && !css.isEmpty()) {
64+
addNew(css, parent.getProperties(), parent.getStylesheets());
65+
}
66+
}
67+
68+
private static void removePrevious(javafx.collections.ObservableMap<Object, Object> props,
69+
java.util.List<String> stylesheets) {
70+
String prevKey = (String) props.get(KEY_PROP);
71+
String prevURL = (String) props.get(URL_PROP);
72+
if (prevURL != null) {
73+
stylesheets.remove(prevURL);
74+
props.remove(KEY_PROP);
75+
props.remove(URL_PROP);
76+
Platform.runLater(() -> unregister(prevKey));
77+
}
78+
}
79+
80+
private static void addNew(String css,
81+
javafx.collections.ObservableMap<Object, Object> props,
82+
java.util.List<String> stylesheets) {
83+
String key = "dcss" + counter.incrementAndGet();
84+
File file = new File(tempDir, key + ".css");
85+
try (PrintWriter w = new PrintWriter(file)) {
86+
w.write(css);
87+
} catch (IOException e) {
88+
throw new RuntimeException("Failed to write CSS file", e);
89+
}
90+
file.deleteOnExit();
91+
synchronized (files) {
92+
files.put(key, file);
93+
}
94+
95+
String url;
96+
try {
97+
url = file.toURI().toURL().toString();
98+
} catch (Exception e) {
99+
throw new RuntimeException(e);
100+
}
101+
102+
props.put(KEY_PROP, key);
103+
props.put(URL_PROP, url);
104+
stylesheets.add(url);
105+
}
106+
107+
private static void unregister(String key) {
108+
synchronized (files) {
109+
File f = files.remove(key);
110+
if (f != null) f.delete();
111+
}
112+
}
113+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package one.jpro.platform.css
2+
3+
import simplefx.core._
4+
import simplefx.all._
5+
import simplefx.util.Predef._
6+
import java.io.{File, PrintWriter}
7+
import java.net.URL
8+
9+
object DynamicCSS {
10+
11+
@extension class AddCSSStringParent(parent: Parent) {
12+
def isShowing = parent.scene != null && parent.scene.window != null && parent.scene.window.showing
13+
@Bind private var showingCSS = <--((this.isShowing, cssString))
14+
@Bind var cssString: String = "" <> {
15+
var previousURL: URL = null
16+
var previousKey: String = null
17+
updated(showingCSS --> { x =>
18+
val (showing: Boolean, cssString: String) = x
19+
if (previousURL != null) {
20+
parent.getStylesheets().remove(previousURL.toString)
21+
val pKey = previousKey
22+
previousURL = null
23+
previousKey = null
24+
runLater {
25+
InMemoryURL.unregister(pKey)
26+
}
27+
}
28+
if (showing) {
29+
val r = InMemoryURL.genDynamicContent(cssString)
30+
previousKey = r._1
31+
previousURL = r._2
32+
parent.getStylesheets().add(previousURL.toString)
33+
}
34+
})
35+
}
36+
}
37+
38+
@extension class AddCSSStringScene(scene: Scene) {
39+
def isShowing = scene.window != null && scene.window.showing
40+
@Bind private var showingCSS = <--((this.isShowing, cssString))
41+
@Bind var cssString: String = "" <> {
42+
var previousURL: URL = null
43+
var previousKey: String = null
44+
updated(showingCSS --> { x =>
45+
val (showing: Boolean, cssString: String) = x
46+
if (previousURL != null) {
47+
scene.getStylesheets().remove(previousURL.toString)
48+
val pKey = previousKey
49+
previousURL = null
50+
previousKey = null
51+
runLater {
52+
InMemoryURL.unregister(pKey)
53+
}
54+
}
55+
if (showing) {
56+
val r = InMemoryURL.genDynamicContent(cssString)
57+
previousKey = r._1
58+
previousURL = r._2
59+
scene.getStylesheets().add(previousURL.toString)
60+
}
61+
})
62+
}
63+
}
64+
}
65+
66+
object InMemoryURL {
67+
68+
private var contentMap: Map[String, String] = Map()
69+
private var counter = 0
70+
71+
private val files: File = {
72+
val f = File.createTempFile("cssfiles", "")
73+
f.delete()
74+
assert(f.mkdirs())
75+
f.deleteOnExit()
76+
f
77+
}
78+
79+
def genDynamicContent(content: String): (String, URL) = synchronized {
80+
counter += 1
81+
val key = s"content$counter"
82+
(key, registerContent(key, content))
83+
}
84+
85+
private def getFile(x: String) = new File(files, x + ".css")
86+
87+
def registerContent(x: String, content: String): URL = synchronized {
88+
contentMap += (x -> content)
89+
val f = getFile(x)
90+
new PrintWriter(f) { write(content); close() }
91+
f.toURI.toURL
92+
}
93+
94+
def unregister(x: String): Unit = synchronized {
95+
contentMap -= x
96+
getFile(x).delete()
97+
}
98+
99+
def getContent(x: String): String = synchronized {
100+
contentMap(x)
101+
}
102+
}

jpro-flexbox/build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
publishing {
2+
publications {
3+
mavenJava(MavenPublication) {
4+
pom {
5+
name = 'JPro FlexBox'
6+
description = 'A CSS FlexBox layout implementation for JavaFX.'
7+
}
8+
}
9+
}
10+
}

jpro-flexbox/example/build.gradle

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
plugins {
2+
id 'org.openjfx.javafxplugin'
3+
id 'application'
4+
}
5+
6+
javafx {
7+
version = "$JAVAFX_EXAMPLES_VERSION"
8+
modules = ['javafx.graphics', 'javafx.controls']
9+
}
10+
11+
dependencies {
12+
implementation project(":jpro-flexbox")
13+
implementation project(":jpro-dynamic-css")
14+
}
15+
16+
application {
17+
mainClass = 'one.jpro.platform.flexbox.example.FlexBoxTestApp'
18+
}

0 commit comments

Comments
 (0)