package com.megatim.manuel.core.theme; import javafx.scene.Parent; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Charte graphique du manuel : couleurs du viewer JavaFX, identité (titre, logo), * feuilles de style additionnelles et variables des pages HTML générées. * *

Immuable ; se construit via {@link #builder()} (pré-rempli avec les valeurs par * défaut, charte Sirius-FT) ou {@link #defaultTheme()}.

* *

Mécanique côté viewer : la CSS embarquée déclare des looked-up colors * ({@code -mgt-accent}, {@code -mgt-button}, ...) sur la classe {@code .help-viewer} * et les référence partout ; {@link #applyTo(Parent)} injecte les valeurs du thème * via {@code setStyle} puis attache les feuilles de style. Une application hôte peut * donc changer la charte sans fournir de CSS, ou surcharger finement via * {@code extraViewerStylesheets} (les {@code styleClass} du viewer sont un contrat * stable : {@code help-viewer}, {@code button-primary}, {@code help-left-panel}, * {@code help-project-title}, {@code help-status}, ...).

* *

Mécanique côté pages HTML : {@link #renderHtmlStyles()} rend le template * {@code styles.css} (embarqué ou fourni via {@code htmlStylesTemplate}) en * substituant les variables {@code ${html.*}} (surchargeables une à une via * {@code htmlVar(...)}).

*/ public final class ManuelTheme { /** Feuille de style par défaut du viewer (looked-up colors), embarquée dans la lib. */ public static final String DEFAULT_VIEWER_CSS = "/com/megatim/manuel/core/viewer/help-viewer.css"; /** Template par défaut du styles.css des pages HTML générées. */ public static final String DEFAULT_HTML_TEMPLATE = "/com/megatim/manuel/core/theme/html-styles-template.css"; // --- Couleurs viewer (défauts = charte Sirius) --- private final String accentColor; private final String buttonColor; private final String buttonBorderColor; private final String panelBorderColor; private final String selectionColor; private final String hoverColor; private final String textColor; private final String backgroundColor; // --- Identité --- private final String appTitle; private final URL logo; // --- CSS viewer --- private final String viewerStylesheet; private final List extraViewerStylesheets; // --- CSS HTML --- private final String htmlStylesTemplate; private final Map htmlVars; private ManuelTheme(Builder b) { this.accentColor = b.accentColor; this.buttonColor = b.buttonColor; this.buttonBorderColor = b.buttonBorderColor; this.panelBorderColor = b.panelBorderColor; this.selectionColor = b.selectionColor; this.hoverColor = b.hoverColor; this.textColor = b.textColor; this.backgroundColor = b.backgroundColor; this.appTitle = b.appTitle; this.logo = b.logo; this.viewerStylesheet = b.viewerStylesheet; this.extraViewerStylesheets = Collections.unmodifiableList(new ArrayList<>(b.extraViewerStylesheets)); this.htmlStylesTemplate = b.htmlStylesTemplate; this.htmlVars = Collections.unmodifiableMap(new LinkedHashMap<>(b.htmlVars)); } /** Thème par défaut : charte Sirius-FT. */ public static ManuelTheme defaultTheme() { return builder().build(); } /** Builder pré-rempli avec les valeurs par défaut (charte Sirius-FT). */ public static Builder builder() { return new Builder(); } /** * Applique le thème à la racine du viewer : injection des looked-up colors puis * attachement de la CSS par défaut (ou de remplacement) et des surcharges. */ public void applyTo(Parent root) { root.setStyle("-mgt-accent: " + accentColor + ";" + " -mgt-button: " + buttonColor + ";" + " -mgt-button-border: " + buttonBorderColor + ";" + " -mgt-panel-border: " + panelBorderColor + ";" + " -mgt-selection: " + selectionColor + ";" + " -mgt-hover: " + hoverColor + ";" + " -mgt-text: " + textColor + ";" + " -mgt-bg: " + backgroundColor + ";"); if (viewerStylesheet != null) { root.getStylesheets().add(viewerStylesheet); } else { URL css = ManuelTheme.class.getResource(DEFAULT_VIEWER_CSS); if (css != null) { root.getStylesheets().add(css.toExternalForm()); } } root.getStylesheets().addAll(extraViewerStylesheets); } /** Rend le contenu du styles.css des pages HTML (template + variables du thème). */ public String renderHtmlStyles() { String template = htmlStylesTemplate != null ? htmlStylesTemplate : loadDefaultHtmlTemplate(); Map vars = defaultHtmlVars(); vars.putAll(htmlVars); String out = template; for (Map.Entry e : vars.entrySet()) { out = out.replace("${" + e.getKey() + "}", e.getValue()); } return out; } /** Variables HTML par défaut (charte Sirius) ; base de la substitution du template. */ public static Map defaultHtmlVars() { Map v = new LinkedHashMap<>(); v.put("html.font", "'Segoe UI',Tahoma,Arial,sans-serif"); v.put("html.text", "#1a1a1a"); v.put("html.bg", "#ffffff"); // Charte Sirius-FT : anthracite/violet, aucun bleu v.put("html.h1", "#3d4d5c"); v.put("html.h2", "#5d4a75"); v.put("html.h3", "#756784"); v.put("html.link", "#756784"); v.put("html.caption", "#444444"); v.put("html.muted", "#555555"); v.put("html.rule", "#d0d0d0"); v.put("html.navbar.bg", "#f1edf5"); v.put("html.navbar.border", "#d5cbdf"); v.put("html.navbar.disabled", "#a8b0ba"); v.put("html.table.border", "#b0b8c4"); v.put("html.img.border", "#c0c0c0"); return v; } private static String loadDefaultHtmlTemplate() { try (InputStream in = ManuelTheme.class.getResourceAsStream(DEFAULT_HTML_TEMPLATE)) { if (in == null) { throw new IOException("Resource introuvable : " + DEFAULT_HTML_TEMPLATE); } return new String(in.readAllBytes(), StandardCharsets.UTF_8); } catch (IOException e) { throw new UncheckedIOException(e); } } public String getAccentColor() { return accentColor; } public String getButtonColor() { return buttonColor; } public String getButtonBorderColor() { return buttonBorderColor; } public String getPanelBorderColor() { return panelBorderColor; } public String getSelectionColor() { return selectionColor; } public String getHoverColor() { return hoverColor; } public String getTextColor() { return textColor; } public String getBackgroundColor() { return backgroundColor; } public String getAppTitle() { return appTitle; } public URL getLogo() { return logo; } public String getViewerStylesheet() { return viewerStylesheet; } public List getExtraViewerStylesheets() { return extraViewerStylesheets; } public String getHtmlStylesTemplate() { return htmlStylesTemplate; } public Map getHtmlVars() { return htmlVars; } /** Builder mutable ; toutes les valeurs sont pré-remplies charte Sirius. */ public static final class Builder { private String accentColor = "#e9e4ef"; private String buttonColor = "#f0f0f0"; private String buttonBorderColor = "#bac0c0"; private String panelBorderColor = "#d1d1d1"; private String selectionColor = "#D6ECDB"; private String hoverColor = "#f0f6f2"; private String textColor = "#323232"; private String backgroundColor = "#ffffff"; private String appTitle; private URL logo; private String viewerStylesheet; private final List extraViewerStylesheets = new ArrayList<>(); private String htmlStylesTemplate; private final Map htmlVars = new LinkedHashMap<>(); public Builder accentColor(String v) { this.accentColor = v; return this; } public Builder buttonColor(String v) { this.buttonColor = v; return this; } public Builder buttonBorderColor(String v) { this.buttonBorderColor = v; return this; } public Builder panelBorderColor(String v) { this.panelBorderColor = v; return this; } public Builder selectionColor(String v) { this.selectionColor = v; return this; } public Builder hoverColor(String v) { this.hoverColor = v; return this; } public Builder textColor(String v) { this.textColor = v; return this; } public Builder backgroundColor(String v) { this.backgroundColor = v; return this; } public Builder appTitle(String v) { this.appTitle = v; return this; } public Builder logo(URL v) { this.logo = v; return this; } /** Remplace entièrement la CSS embarquée du viewer (URI de feuille de style). */ public Builder viewerStylesheet(String uri) { this.viewerStylesheet = uri; return this; } /** Ajoute une CSS de surcharge, chargée après la CSS par défaut. */ public Builder extraViewerStylesheet(String uri) { this.extraViewerStylesheets.add(uri); return this; } /** Remplace entièrement le template styles.css des pages HTML. */ public Builder htmlStylesTemplate(String template) { this.htmlStylesTemplate = template; return this; } /** Surcharge une variable du template HTML (ex. {@code html.h1}, {@code html.link}). */ public Builder htmlVar(String key, String value) { this.htmlVars.put(key, value); return this; } public ManuelTheme build() { return new ManuelTheme(this); } } }