Skip to content

Commit 314db35

Browse files
Release 1.1.2: diagram canvas, sans-serif, palette i18n
Swift-style strokes/insets/selection; DiagramKeywordText; do-until layout; Messages_de Fallauswahl; macOS scripts + README + release-notes. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 41850da commit 314db35

15 files changed

Lines changed: 427 additions & 75 deletions

File tree

‎README.md‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ git clone https://github.com/code4teaching/VisuStruct.git
2222

2323
---
2424

25+
## Changes in 1.1.2 (summary)
26+
27+
- **Diagram canvas (light theme):** styling aligned with [VisuStruct-SwiftUI](https://github.com/code4teaching/VisuStruct-SwiftUI): black block borders (2 px), outer frame inset 16 px, content inset 28 px, selection as blue outline (no blue fill).
28+
- **Diagram text:** **Sans-Serif** diagram font (Segoe UI, Helvetica Neue, …), **17 pt**; **bold** control-flow keywords (`if`, `while`, …) like Swift; **do-while** shows **`do`** in the left gutter (extra inset); **Fallauswahl** DE palette label **Fallauswahl**.
29+
- **Left palette:** labels follow **UI language** (`structure.palette.*`), except preset **Java (Standard)** (`if`, `while`, …).
30+
31+
Full notes: [`release-notes/v1.1.2.md`](release-notes/v1.1.2.md).
32+
2533
## Changes in 1.1.1 (summary)
2634

2735
- **About dialog:** clearer links for website, GitHub repository, and developer site.
@@ -103,7 +111,7 @@ This release aligns **branding and technical identity** with **VisuStruct**. **`
103111
- **Java 17** target (build and run with JDK 17+).
104112
- **FlatLaf** light/dark themes; **JDOM2**; no legacy AppleJavaExtensions.
105113
- **Motif** look-and-feel removed; **Metal** and FlatLaf available.
106-
- Current fat JAR name follows **`pom.xml`** **`version`** (e.g. **`visustruct-1.1.1.jar`**).
114+
- Current fat JAR name follows **`pom.xml`** **`version`** (e.g. **`visustruct-1.1.2.jar`**).
107115

108116
---
109117

@@ -127,7 +135,7 @@ On **Windows**, double-click often works if `.jar` is associated with Java. If a
127135

128136
All releases: [github.com/code4teaching/VisuStruct/releases](https://github.com/code4teaching/VisuStruct/releases)
129137

130-
**Maintainers:** To ship a new version, create a GitHub **Release** with a tag such as **`v1.1.1`**. Workflow [`.github/workflows/release-assets.yml`](.github/workflows/release-assets.yml) builds the JARs and attaches **`visustruct.jar`** (stable download URL above).
138+
**Maintainers:** To ship a new version, create a GitHub **Release** with a tag such as **`vX.Y.Z`** (one bump per release — not for every local change). Workflow [`.github/workflows/release-assets.yml`](.github/workflows/release-assets.yml) builds the JARs and attaches **`visustruct.jar`** (stable download URL above). Update **`pom.xml`** `<version>` together with that release.
131139

132140
---
133141

@@ -178,13 +186,13 @@ chmod +x mvnw
178186
Output (version from `pom.xml`):
179187

180188
```text
181-
target/visustruct-1.1.1.jar
189+
target/visustruct-1.1.2.jar
182190
```
183191

184192
## Run
185193

186194
```bash
187-
java -jar target/visustruct-1.1.1.jar
195+
java -jar target/visustruct-1.1.2.jar
188196
```
189197

190198
Double-click may work if `.jar` is associated with Java.

‎packaging/macos/build-lightweight-app.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
set -euo pipefail
55
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
66
ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
7-
VER="${PROJECT_VERSION:-1.1.1}"
7+
VER="${PROJECT_VERSION:-1.1.2}"
88
JAR_NAME="visustruct-${VER}.jar"
99
OUT="${ROOT}/dist/macos-app-light"
1010
APP="${OUT}/VisuStruct.app"

‎packaging/macos/build-macos-app.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Voraussetzung: JDK 17+ mit jpackage (im PATH oder JAVA_HOME).
55
set -euo pipefail
66
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
7-
VER="${PROJECT_VERSION:-1.1.1}"
7+
VER="${PROJECT_VERSION:-1.1.2}"
88
JAR="visustruct-${VER}.jar"
99
INPUT="${ROOT}/target"
1010
OUT="${ROOT}/dist/macos-app"

‎pom.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>de.visustruct</groupId>
66
<artifactId>visustruct</artifactId>
7-
<version>1.1.1</version>
7+
<version>1.1.2</version>
88
<name>VisuStruct</name>
99

1010
<properties>

‎release-notes/v1.1.2.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# VisuStruct 1.1.2
2+
3+
Release **after** upstream **1.1.1** (GitHub `master`); intended as the next publish from this fork.
4+
5+
## Diagram canvas (light theme)
6+
7+
- Block and helper lines: **black** strokes at **2 px** (VisuStruct-SwiftUI `DiagramCanvasView` / `DiagramCanvasInk`).
8+
- **Outer frame:** **16 px** inset, **2 px** stroke.
9+
- **Content inset:** **28 px** left/top for the structure.
10+
- **Selection:** **blue outline** (~3.5 px), no blue fill (Swift-style).
11+
12+
## Diagram typography
13+
14+
- **Sans-Serif** stack (Segoe UI, SF Pro Text, Helvetica Neue, Roboto, Ubuntu, …), **17 pt**.
15+
- **Bold** control-flow keywords (`if`, `while`, `switch`, …) and `input:` / `output:` / `print:`; **do-while** shows **`do`** in the left gutter (wider left inset for do-until so text clears the inner border).
16+
17+
## Left palette
18+
19+
- Button labels from **`structure.palette.*`** per **UI language** (de / en / pt_PT), e.g. German *Anweisung*, *Verzweigung*, *Fallauswahl*.
20+
- Preset **Java (Standard)** keeps syntax-style labels (`if`, `while`, `for`, `do`, …).
21+
22+
## Build
23+
24+
- Maven: **`de.visustruct:visustruct:1.1.2`**
25+
- Fat JAR: **`visustruct-1.1.2.jar`** (`./mvnw package` → `target/`)

‎src/main/java/de/visustruct/control/CanvasStyle.java‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,24 @@
1111
/**
1212
* Farben der Struktogramm-Zeichenfläche (nicht die Swing-Fenster).
1313
* Bei <b>Modern · dark</b> (FlatDark) wird eine hellere graue Fläche genutzt, passend zum dunklen UI.
14+
* <p>
15+
* Helles Schema: an {@code VisuStruct-SwiftUI} / {@code DiagramCanvasView} angelehnt (weiße Zellen,
16+
* schwarze Kanten 2 px, blauer Auswahlrahmen).
1417
*/
1518
public final class CanvasStyle {
1619

20+
/** Kanten von Blöcken und Hilfslinien — entspricht Swift {@code DiagramCanvasInk.diagramLineWidth}. */
21+
public static final float DIAGRAM_LINE_WIDTH = 2f;
22+
/** Auswahlrahmen um die aktive Kachel — entspricht Swift {@code DiagramCanvasInk.selectionLineWidth}. */
23+
public static final float SELECTION_LINE_WIDTH = 3.5f;
24+
1725
private static Color background;
1826
private static Color diagramFrame;
1927
private static Color titleText;
2028
private static Color elementBorder;
2129
private static Color elementText;
2230
private static Color elementSelectedFill;
31+
private static Color selectionStroke;
2332
private static Color dropPreview;
2433
private static Color dragFrame;
2534
private static Color elementFill;
@@ -32,13 +41,15 @@ private CanvasStyle() {
3241
}
3342

3443
private static void applyLightPalette() {
44+
// Canvas ~ Swift Color(white: 0.98); Rahmen/Kanten schwarz wie im Swift-Diagramm-Canvas.
3545
background = new Color(0xFAFAFA);
36-
diagramFrame = new Color(0xD1D5DB);
46+
diagramFrame = Color.BLACK;
3747
titleText = new Color(0x111827);
38-
elementBorder = new Color(0x4B5563);
48+
elementBorder = Color.BLACK;
3949
elementText = new Color(0x111827);
40-
// Markierung (hell) soll sich klar von der Drop-Vorschau unterscheiden und Text lesbar lassen.
50+
// Swift: Auswahl = weiße Füllung + blauer Rand; Füllung kommt aus {@code elementFill}, Rahmen aus {@code selectionStroke}.
4151
elementSelectedFill = new Color(0xDBEAFE);
52+
selectionStroke = new Color(0f, 0.48f, 1f, 0.9f);
4253
// Drop-Vorschau: kräftiger + halbtransparent, damit sie über Diagrammflächen nicht "klebt".
4354
dropPreview = new Color(0x803B82F6, true);
4455
dragFrame = new Color(0x3B82F6);
@@ -48,12 +59,12 @@ private static void applyLightPalette() {
4859
/** Zeichenfläche spürbar dunkler; Blöcke bleiben hell genug für Lesbarkeit. */
4960
private static void applyDarkPalette() {
5061
background = new Color(0x1E1E22);
51-
diagramFrame = new Color(0x6B6B70);
62+
diagramFrame = new Color(0xB4B4BA);
5263
titleText = new Color(0xF4F4F5);
53-
elementBorder = new Color(0x71717A);
64+
elementBorder = new Color(0xB4B4BA);
5465
elementText = new Color(0x18181B);
55-
// In dark bleiben Blöcke hell; Markierung muss sich abheben, aber Text (dunkel) lesbar lassen.
5666
elementSelectedFill = new Color(0xA7C7FF);
67+
selectionStroke = new Color(0.35f, 0.63f, 1f, 0.92f);
5768
// Drop-Vorschau: stärker + halbtransparent.
5869
dropPreview = new Color(0x805B9FFF, true);
5970
dragFrame = new Color(0x5B9FFF);
@@ -100,6 +111,11 @@ public static Color getElementSelectedFill() {
100111
return elementSelectedFill;
101112
}
102113

114+
/** Randfarbe für die aktuell ausgewählte Kachel (Swift: {@code selectionStrokeColor}). */
115+
public static Color getSelectionStroke() {
116+
return selectionStroke;
117+
}
118+
103119
public static Color getDropPreview() {
104120
return dropPreview;
105121
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package de.visustruct.control;
2+
3+
import java.awt.Color;
4+
import java.awt.Font;
5+
import java.awt.Graphics2D;
6+
import java.awt.font.FontRenderContext;
7+
import java.awt.geom.Rectangle2D;
8+
import java.util.Locale;
9+
import java.util.Optional;
10+
11+
/**
12+
* Fachliche Diagramm-Textdarstellung: Schlüsselwörter fett, Rest normal (Monospace),
13+
* angelehnt an {@code DiagramCanvasView.keywordSplit} / {@code drawKeywordLine} in VisuStruct-SwiftUI.
14+
*/
15+
public final class DiagramKeywordText {
16+
17+
private DiagramKeywordText() {
18+
}
19+
20+
/** Ergebnis von {@link #splitKeywordLine(String)}. */
21+
public static final class KeywordParts {
22+
public final String keyword;
23+
public final String rest;
24+
25+
public KeywordParts(String keyword, String rest) {
26+
this.keyword = keyword;
27+
this.rest = rest;
28+
}
29+
}
30+
31+
/**
32+
* Wie Swift {@code normalizedDoUntilConditionLines}: führendes {@code until} in der
33+
* Fußbedingung für die Anzeige zu {@code while} umformulieren (Speicher bleibt unverändert).
34+
*/
35+
public static String normalizeDoUntilDisplayLine(String line) {
36+
if (line == null) {
37+
return "";
38+
}
39+
String t = line.trim();
40+
String lower = t.toLowerCase(Locale.ROOT);
41+
if ("until".equals(lower)) {
42+
return "";
43+
}
44+
if (lower.startsWith("until ")) {
45+
return t.substring(5).trim();
46+
}
47+
if (lower.startsWith("until(")) {
48+
return t.substring(5);
49+
}
50+
return line;
51+
}
52+
53+
/**
54+
* Erste Zeile mit führendem Schlüsselwort versehen (Swift {@code withLeadingKeyword}),
55+
* wenn noch nicht vorhanden.
56+
*/
57+
public static String ensureLeadingKeyword(String keyword, String line) {
58+
if (line == null) {
59+
return keyword + " ";
60+
}
61+
String trimmed = line.trim();
62+
if (trimmed.isEmpty()) {
63+
return keyword + " ";
64+
}
65+
String lower = trimmed.toLowerCase(Locale.ROOT);
66+
String k = keyword.toLowerCase(Locale.ROOT);
67+
if (lower.equals(k) || lower.startsWith(k + " ")) {
68+
return line;
69+
}
70+
return keyword + " " + trimmed;
71+
}
72+
73+
/**
74+
* Zeile für die Diagramm-Anzeige aufbereiten (Do-Until-Normalisierung + ggf. führendes Keyword).
75+
*
76+
* @param struktogrammTyp {@link Struktogramm#typAnweisung} …
77+
* @param lineIndex 0-basiert; führendes Keyword nur bei Zeile 0
78+
*/
79+
public static String lineForDisplay(int struktogrammTyp, int lineIndex, String raw) {
80+
if (raw == null) {
81+
return "";
82+
}
83+
String line = raw;
84+
if (struktogrammTyp == Struktogramm.typDoUntilSchleife) {
85+
line = normalizeDoUntilDisplayLine(line);
86+
}
87+
if (lineIndex != 0) {
88+
return line;
89+
}
90+
return switch (struktogrammTyp) {
91+
case Struktogramm.typVerzweigung -> ensureLeadingKeyword("if", line);
92+
case Struktogramm.typWhileSchleife -> ensureLeadingKeyword("while", line);
93+
case Struktogramm.typForSchleife -> ensureLeadingKeyword("for", line);
94+
case Struktogramm.typFallauswahl -> ensureLeadingKeyword("switch", line);
95+
case Struktogramm.typDoUntilSchleife -> ensureLeadingKeyword("while", line);
96+
default -> line;
97+
};
98+
}
99+
100+
/**
101+
* Zerlegt eine Zeile in fett zu zeichnendes Schlüsselwort und Rest (Swift {@code keywordSplit}).
102+
*/
103+
public static Optional<KeywordParts> splitKeywordLine(String line) {
104+
if (line == null || line.isEmpty()) {
105+
return Optional.empty();
106+
}
107+
String trimmed = line.trim();
108+
String lower = trimmed.toLowerCase(Locale.ROOT);
109+
for (String prefix : new String[] { "input:", "output:", "print:" }) {
110+
if (lower.equals(prefix)) {
111+
return Optional.of(new KeywordParts(trimmed.substring(0, prefix.length()), ""));
112+
}
113+
if (lower.startsWith(prefix + " ")) {
114+
String kw = trimmed.substring(0, prefix.length());
115+
String rest = trimmed.substring(prefix.length()).trim();
116+
return Optional.of(new KeywordParts(kw, rest));
117+
}
118+
}
119+
int sp = indexOfFirstSpace(trimmed);
120+
if (sp < 0) {
121+
String w = trimmed;
122+
if (isBareKeyword(w)) {
123+
return Optional.of(new KeywordParts(w, ""));
124+
}
125+
return Optional.empty();
126+
}
127+
String head = trimmed.substring(0, sp);
128+
if (!isBareKeyword(head)) {
129+
return Optional.empty();
130+
}
131+
String rest = trimmed.substring(sp + 1).trim();
132+
return Optional.of(new KeywordParts(head, rest));
133+
}
134+
135+
private static int indexOfFirstSpace(String s) {
136+
for (int i = 0; i < s.length(); i++) {
137+
if (Character.isWhitespace(s.charAt(i))) {
138+
return i;
139+
}
140+
}
141+
return -1;
142+
}
143+
144+
private static boolean isBareKeyword(String w) {
145+
String lower = w.toLowerCase(Locale.ROOT);
146+
return lower.equals("if") || lower.equals("for") || lower.equals("while")
147+
|| lower.equals("until") || lower.equals("switch") || lower.equals("do")
148+
|| lower.equals("true") || lower.equals("false") || lower.equals("default");
149+
}
150+
151+
/** Gesamtbreite der Zeile (fett + normal) in px. */
152+
public static int measureLineWidth(Graphics2D g, String line) {
153+
Optional<KeywordParts> parts = splitKeywordLine(line);
154+
if (parts.isEmpty()) {
155+
return (int) Math.ceil(stringWidth(g, g.getFont(), line));
156+
}
157+
KeywordParts p = parts.get();
158+
Font plain = stripBold(g.getFont());
159+
Font bold = plain.deriveFont(Font.BOLD);
160+
double wKw = stringWidth(g, bold, p.keyword);
161+
double wRest = p.rest.isEmpty() ? 0 : stringWidth(g, plain, " " + p.rest);
162+
return (int) Math.ceil(wKw + wRest);
163+
}
164+
165+
/**
166+
* Zeichnet eine Zeile mit optional fettem Schlüsselwort; {@code y} ist Basislinie wie bei {@link Graphics2D#drawString}.
167+
*/
168+
public static void drawKeywordAwareLine(Graphics2D g, Color color, int x, int y, String line) {
169+
g.setColor(color);
170+
Optional<KeywordParts> parts = splitKeywordLine(line);
171+
if (parts.isEmpty()) {
172+
g.drawString(line, x, y);
173+
return;
174+
}
175+
KeywordParts p = parts.get();
176+
Font plain = stripBold(g.getFont());
177+
Font bold = plain.deriveFont(Font.BOLD);
178+
g.setFont(bold);
179+
g.drawString(p.keyword, x, y);
180+
int x2 = x + (int) Math.ceil(stringWidth(g, bold, p.keyword));
181+
if (!p.rest.isEmpty()) {
182+
g.setFont(plain);
183+
g.drawString(" " + p.rest, x2, y);
184+
} else {
185+
g.setFont(plain);
186+
}
187+
}
188+
189+
private static Font stripBold(Font f) {
190+
return f.deriveFont(f.getStyle() & ~Font.BOLD);
191+
}
192+
193+
private static double stringWidth(Graphics2D g, Font font, String s) {
194+
FontRenderContext frc = g.getFontRenderContext();
195+
Rectangle2D r = font.getStringBounds(s, frc);
196+
return r.getWidth();
197+
}
198+
}

0 commit comments

Comments
 (0)