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);
}
}
}