diff --git a/prometeu-studio/build.gradle.kts b/prometeu-studio/build.gradle.kts index 93d2ea90..4993f398 100644 --- a/prometeu-studio/build.gradle.kts +++ b/prometeu-studio/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation(libs.javafx.controls) implementation(libs.javafx.fxml) implementation(libs.richtextfx) + testImplementation(project(":prometeu-compiler:frontends:prometeu-frontend-pbs")) testImplementation(project(":prometeu-packer:prometeu-packer-v1")) } diff --git a/prometeu-studio/src/main/java/p/studio/utilities/FrontendVisualThemeCssRenderer.java b/prometeu-studio/src/main/java/p/studio/utilities/FrontendVisualThemeCssRenderer.java new file mode 100644 index 00000000..24fef5ab --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/utilities/FrontendVisualThemeCssRenderer.java @@ -0,0 +1,69 @@ +package p.studio.utilities; + +import p.studio.compiler.models.FrontendSemanticPresentationSpec; +import p.studio.compiler.models.FrontendTokenStyleSpec; +import p.studio.compiler.models.FrontendVisualThemeSpec; + +import java.util.Objects; + +public final class FrontendVisualThemeCssRenderer { + + public String renderDefaultTheme( + final String languageId, + final FrontendSemanticPresentationSpec presentation) { + Objects.requireNonNull(presentation, "presentation"); + return renderTheme(languageId, presentation.defaultTheme()); + } + + public String renderTheme( + final String languageId, + final FrontendVisualThemeSpec theme) { + final String normalizedLanguageId = requireText(languageId, "languageId"); + final FrontendVisualThemeSpec resolvedTheme = Objects.requireNonNull(theme, "theme"); + final String editorSelector = ".editor-workspace-code-area-type-" + normalizedLanguageId; + final String statusChipSelector = ".editor-workspace-status-chip-type-" + normalizedLanguageId; + final StringBuilder css = new StringBuilder(); + + css.append(editorSelector).append(" {\n") + .append(" -fx-highlight-fill: ").append(resolvedTheme.editorPalette().activeHighlightBackground()).append(";\n") + .append("}\n\n") + .append(editorSelector).append(" .text {\n") + .append(" -fx-fill: ").append(resolvedTheme.editorPalette().baseForeground()).append(";\n") + .append("}\n\n") + .append(editorSelector).append(" .lineno {\n") + .append(" -fx-text-fill: ").append(resolvedTheme.editorPalette().lineNumberForeground()).append(";\n") + .append("}\n\n"); + + for (final FrontendTokenStyleSpec tokenStyle : resolvedTheme.tokenStyles()) { + css.append(editorSelector).append(" .text.editor-semantic-").append(tokenStyle.semanticKey()).append(" {\n") + .append(" -fx-fill: ").append(tokenStyle.foreground()).append(";\n"); + if (tokenStyle.italic()) { + css.append(" -fx-font-style: italic;\n"); + } + if (tokenStyle.bold()) { + css.append(" -fx-font-weight: bold;\n"); + } + if (tokenStyle.underline()) { + css.append(" -fx-underline: true;\n"); + } + css.append("}\n\n"); + } + + css.append(statusChipSelector).append(" {\n") + .append(" -fx-background-color: ").append(resolvedTheme.editorPalette().statusChipBackground()).append(";\n") + .append(" -fx-border-color: ").append(resolvedTheme.editorPalette().statusChipBorder()).append(";\n") + .append(" -fx-text-fill: ").append(resolvedTheme.editorPalette().statusChipForeground()).append(";\n") + .append("}\n"); + return css.toString(); + } + + private static String requireText( + final String value, + final String field) { + final String candidate = Objects.requireNonNull(value, field).trim(); + if (candidate.isEmpty()) { + throw new IllegalArgumentException(field + " must not be blank"); + } + return candidate; + } +} diff --git a/prometeu-studio/src/test/java/p/studio/utilities/FrontendVisualThemeCssRendererTest.java b/prometeu-studio/src/test/java/p/studio/utilities/FrontendVisualThemeCssRendererTest.java new file mode 100644 index 00000000..3f4b2a17 --- /dev/null +++ b/prometeu-studio/src/test/java/p/studio/utilities/FrontendVisualThemeCssRendererTest.java @@ -0,0 +1,25 @@ +package p.studio.utilities; + +import org.junit.jupiter.api.Test; +import p.studio.compiler.PBSDefinitions; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FrontendVisualThemeCssRendererTest { + + @Test + void rendersLegacyCompatibleCssFromStructuredFrontendTheme() { + final FrontendVisualThemeCssRenderer renderer = new FrontendVisualThemeCssRenderer(); + + final String css = renderer.renderDefaultTheme( + PBSDefinitions.PBS.getLanguageId(), + PBSDefinitions.PBS.getSemanticPresentation()); + + assertTrue(css.contains(".editor-workspace-code-area-type-pbs")); + assertTrue(css.contains(".text.editor-semantic-pbs-keyword")); + assertTrue(css.contains("-fx-fill: #569cd6;")); + assertTrue(css.contains("-fx-font-style: italic;")); + assertTrue(css.contains(".editor-workspace-status-chip-type-pbs")); + assertTrue(css.contains("-fx-border-color: #a47dff;")); + } +}