Leonel FOFOU
2026-07-28 32450e295200b2bc7896518ff74161ff26772e2d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
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.
 *
 * <p>Immuable ; se construit via {@link #builder()} (pré-rempli avec les valeurs par
 * défaut, charte Sirius-FT) ou {@link #defaultTheme()}.</p>
 *
 * <p>Mécanique côté viewer : la CSS embarquée déclare des <em>looked-up colors</em>
 * ({@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}, ...).</p>
 *
 * <p>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(...)}).</p>
 */
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<String> extraViewerStylesheets;
 
    // --- CSS HTML ---
    private final String htmlStylesTemplate;
    private final Map<String, String> 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<String, String> vars = defaultHtmlVars();
        vars.putAll(htmlVars);
        String out = template;
        for (Map.Entry<String, String> 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<String, String> defaultHtmlVars() {
        Map<String, String> 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<String> getExtraViewerStylesheets() { return extraViewerStylesheets; }
    public String getHtmlStylesTemplate() { return htmlStylesTemplate; }
    public Map<String, String> 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<String> extraViewerStylesheets = new ArrayList<>();
        private String htmlStylesTemplate;
        private final Map<String, String> 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);
        }
    }
}