HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
style_sheet.hpp
1// Copyright Take Vos 2023.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "../utility/module.hpp"
8#include "../color/module.hpp"
9#include "../font/module.hpp"
10#include "../text/module.hpp"
11#include "../i18n/module.hpp"
12#include "../file/module.hpp"
13#include "theme_mode.hpp"
14#include "theme_state.hpp"
15#include "theme_length.hpp"
16#include "theme_model.hpp"
17#include <string>
18#include <vector>
19#include <variant>
20
21namespace hi { inline namespace v1 {
22
23enum class style_sheet_value_mask {
24 pixels = 0b0000'0001,
25 dips = 0b0000'0010,
26 em_quads = 0b0000'0100,
27 color = 0b0000'1000,
28 font_family_id = 0b0001'0000,
29 font_weight = 0b0010'0000,
30 font_style = 0b0100'0000,
31
32 length = pixels | dips | em_quads,
33};
34
35[[nodiscard]] constexpr style_sheet_value_mask
36operator|(style_sheet_value_mask const& lhs, style_sheet_value_mask const& rhs) noexcept
37{
38 return static_cast<style_sheet_value_mask>(to_underlying(lhs) | to_underlying(rhs));
39}
40
41[[nodiscard]] constexpr style_sheet_value_mask
42operator&(style_sheet_value_mask const& lhs, style_sheet_value_mask const& rhs) noexcept
43{
44 return static_cast<style_sheet_value_mask>(to_underlying(lhs) & to_underlying(rhs));
45}
46
47[[nodiscard]] constexpr bool to_bool(style_sheet_value_mask const& rhs) noexcept
48{
49 return static_cast<bool>(to_underlying(rhs));
50}
51
52struct style_sheet_value : std::variant<hi::pixels, hi::dips, hi::em_quads, hi::color, font_family_id, font_weight, font_style> {
53 using super = std::variant<hi::pixels, hi::dips, hi::em_quads, hi::color, font_family_id, font_weight, font_style>;
54 using super::super;
55
56 constexpr style_sheet_value(theme_length length) noexcept :
57 super([&] {
58 if (auto pixels_ptr = std::get_if<hi::pixels>(&length)) {
59 return super{*pixels_ptr};
60 } else if (auto dips_ptr = std::get_if<hi::dips>(&length)) {
61 return super{*dips_ptr};
62 } else if (auto em_quads_ptr = std::get_if<hi::em_quads>(&length)) {
63 return super{*em_quads_ptr};
64 } else {
66 }
67 }())
68 {
69 }
70};
71
74 std::vector<bool> is_child;
75
76 mutable glob_pattern pattern_cache = {};
77 mutable bool pattern_cache_valid = false;
78
79 [[nodiscard]] constexpr bool matches(std::string_view model_path) const noexcept
80 {
81 if (not pattern_cache_valid) {
82 pattern_cache = get_path_as_glob_pattern();
83 pattern_cache_valid = true;
84 }
85 return pattern_cache.matches(model_path);
86 }
87
88 [[nodiscard]] constexpr std::string get_path_as_glob_string() const noexcept
89 {
90 hi_assert(not path.empty());
91
92 auto r = std::string{};
93 r += "/**/";
94 r += path.front();
95
96 hi_assert(path.size() == is_child.size() + 1);
97
98 for (auto i = 0_uz; i != is_child.size(); ++i) {
99 r += is_child[i] ? "/" : "/**/";
100 r += path[i + 1];
101 }
102
103 return r;
104 }
105
106 [[nodiscard]] constexpr glob_pattern get_path_as_glob_pattern() const noexcept
107 {
108 return glob_pattern{get_path_as_glob_string()};
109 }
110};
111
112struct style_sheet_selector : std::vector<style_sheet_pattern> {
113 [[nodiscard]] bool matches(std::string_view path) const noexcept
114 {
115 for (hilet& pattern : *this) {
116 if (pattern.matches(path)) {
117 return true;
118 }
119 }
120 return false;
121 }
122};
123
124enum class style_sheet_declaration_name {
125 background_color,
126 border_bottom_left_radius,
127 border_bottom_right_radius,
128 border_color,
129 border_top_left_radius,
130 border_top_right_radius,
131 border_width,
132 caret_primary_color,
133 caret_secondary_color,
134 caret_overwrite_color,
135 caret_compose_color,
136 fill_color,
137 font_color,
138 font_family,
139 font_size,
141 font_weight,
142 height,
143 margin_bottom,
144 margin_left,
145 margin_right,
146 margin_top,
147 selection_color,
148 spacing_horizontal,
149 spacing_vertical,
150 width,
151};
152
153// clang-format off
154constexpr auto style_sheet_declaration_name_metadata = enum_metadata{
155 style_sheet_declaration_name::background_color, "background-color",
156 style_sheet_declaration_name::border_bottom_left_radius, "border-bottom-left-radius",
157 style_sheet_declaration_name::border_bottom_right_radius, "border-bottom-right-radius",
158 style_sheet_declaration_name::border_color, "border-color",
159 style_sheet_declaration_name::border_top_left_radius, "border-top-left-radius",
160 style_sheet_declaration_name::border_top_right_radius, "border-top-right-radius",
161 style_sheet_declaration_name::border_width, "border-width",
162 style_sheet_declaration_name::caret_primary_color, "caret-primary-color",
163 style_sheet_declaration_name::caret_secondary_color, "caret-secondary-color",
164 style_sheet_declaration_name::caret_overwrite_color, "caret-overwrite-color",
165 style_sheet_declaration_name::caret_compose_color, "caret-compose-color",
166 style_sheet_declaration_name::fill_color, "fill-color",
167 style_sheet_declaration_name::font_color, "font-color",
168 style_sheet_declaration_name::font_family, "font-family",
169 style_sheet_declaration_name::font_size, "font-size",
170 style_sheet_declaration_name::font_style, "font-style",
171 style_sheet_declaration_name::font_weight, "font-weight",
172 style_sheet_declaration_name::height, "height",
173 style_sheet_declaration_name::margin_bottom, "margin-bottom",
174 style_sheet_declaration_name::margin_left, "margin-left",
175 style_sheet_declaration_name::margin_right, "margin-right",
176 style_sheet_declaration_name::margin_top, "margin-top",
177 style_sheet_declaration_name::selection_color, "selection-color",
178 style_sheet_declaration_name::spacing_horizontal, "spacing-horizontal",
179 style_sheet_declaration_name::spacing_vertical, "spacing-vertical",
180 style_sheet_declaration_name::width, "width",
181};
182// clang-format on
183
184// clang-format off
185constexpr auto style_sheet_declaration_name_value_mask_metadata = enum_metadata{
186 style_sheet_declaration_name::background_color, style_sheet_value_mask::color,
187 style_sheet_declaration_name::border_bottom_left_radius, style_sheet_value_mask::length,
188 style_sheet_declaration_name::border_bottom_right_radius, style_sheet_value_mask::length,
189 style_sheet_declaration_name::border_color, style_sheet_value_mask::color,
190 style_sheet_declaration_name::border_top_left_radius, style_sheet_value_mask::length,
191 style_sheet_declaration_name::border_top_right_radius, style_sheet_value_mask::length,
192 style_sheet_declaration_name::border_width, style_sheet_value_mask::length,
193 style_sheet_declaration_name::caret_primary_color, style_sheet_value_mask::color,
194 style_sheet_declaration_name::caret_secondary_color, style_sheet_value_mask::color,
195 style_sheet_declaration_name::caret_overwrite_color, style_sheet_value_mask::color,
196 style_sheet_declaration_name::caret_compose_color, style_sheet_value_mask::color,
197 style_sheet_declaration_name::fill_color, style_sheet_value_mask::color,
198 style_sheet_declaration_name::font_color, style_sheet_value_mask::color,
199 style_sheet_declaration_name::font_family, style_sheet_value_mask::font_family_id,
200 style_sheet_declaration_name::font_size, style_sheet_value_mask::dips,
201 style_sheet_declaration_name::font_style, style_sheet_value_mask::font_style,
202 style_sheet_declaration_name::font_weight, style_sheet_value_mask::font_weight,
203 style_sheet_declaration_name::height, style_sheet_value_mask::length,
204 style_sheet_declaration_name::margin_bottom, style_sheet_value_mask::length,
205 style_sheet_declaration_name::margin_left, style_sheet_value_mask::length,
206 style_sheet_declaration_name::margin_right, style_sheet_value_mask::length,
207 style_sheet_declaration_name::margin_top, style_sheet_value_mask::length,
208 style_sheet_declaration_name::selection_color, style_sheet_value_mask::color,
209 style_sheet_declaration_name::spacing_horizontal, style_sheet_value_mask::length,
210 style_sheet_declaration_name::spacing_vertical, style_sheet_value_mask::length,
211 style_sheet_declaration_name::width, style_sheet_value_mask::length,
212};
213// clang-format on
214
216 style_sheet_declaration_name name;
217 style_sheet_value value;
218 bool important = false;
219};
220
222 style_sheet_selector selector;
223 theme_state state = {};
224 theme_state_mask state_mask = {};
225 text_phrasing_mask phrasing_mask = {};
226 language_tag language_mask = {};
227
229
230 [[nodiscard]] constexpr size_t size() const noexcept
231 {
232 return declarations.size();
233 }
234
235 [[nodiscard]] constexpr style_sheet_declaration const& operator[](size_t i) const noexcept
236 {
237 return declarations[i];
238 }
239
240 [[nodiscard]] constexpr std::string get_selector_as_string() const noexcept
241 {
242 hi_assert(not selector.empty());
243
244 auto r = selector[0].get_path_as_glob_string();
245 for (auto i = 1_uz; i != selector.size(); ++i) {
246 r += ',';
247 r += selector[i].get_path_as_glob_string();
248 }
249
250 return r;
251 }
252
253 [[nodiscard]] generator<theme_state> matching_states(std::string const& model_path) const noexcept
254 {
255 if (selector.matches(model_path)) {
256 for (auto i = theme_state{}; i != theme_state{theme_state_size}; ++i) {
257 if ((i & state_mask) == state) {
258 co_yield i;
259 }
260 }
261 }
262 }
263
264 void activate_model(int phase, std::string const& model_path, theme_model_base& model) const noexcept
265 {
266 for (auto model_state : matching_states(model_path)) {
267 auto& sub_model = model[model_state];
268 for (hilet & [ name, value, important ] : declarations) {
269 _activate_model_declaration(phase, model_path, sub_model, name, value, important);
270 }
271 }
272 }
273
274private:
275 void _activate_model_font_color(int phase, theme_sub_model& sub_model, style_sheet_value value) const noexcept
276 {
277 if (phase != 0) {
278 return;
279 }
280
281 auto& text_style = sub_model.text_theme.find_or_add(phrasing_mask, language_mask);
282 hi_axiom(std::holds_alternative<hi::color>(value));
283 text_style.color = std::get<hi::color>(value);
284 }
285
286 void _activate_model_font_family(int phase, theme_sub_model& sub_model, style_sheet_value value) const noexcept
287 {
288 if (phase != 0) {
289 return;
290 }
291
292 auto& text_style = sub_model.text_theme.find_or_add(phrasing_mask, language_mask);
293 hi_axiom(std::holds_alternative<hi::font_family_id>(value));
294 text_style.family_id = std::get<hi::font_family_id>(value);
295 }
296
297 void _activate_model_font_style(int phase, theme_sub_model& sub_model, style_sheet_value value) const noexcept
298 {
299 if (phase != 0) {
300 return;
301 }
302
303 auto& text_style = sub_model.text_theme.find_or_add(phrasing_mask, language_mask);
304 hi_axiom(std::holds_alternative<hi::font_style>(value));
305 text_style.variant.set_style(std::get<hi::font_style>(value));
306 }
307
308 void _activate_model_font_size(int phase, theme_sub_model& sub_model, style_sheet_value value) const noexcept
309 {
310 if (phase != 0) {
311 return;
312 }
313
314 auto& text_style = sub_model.text_theme.find_or_add(phrasing_mask, language_mask);
315 hi_axiom(std::holds_alternative<hi::dips>(value));
316
317 // When retrieving the text-style it will be scaled by the UI-scale.
318 text_style.size = round_cast<int>(std::get<hi::dips>(value).count() * -4.0);
319
320 if (not language_mask and not to_bool(phrasing_mask)) {
321 sub_model.line_height = std::get<hi::dips>(value);
322 // The following values are estimates.
323 // Hopefully good enough for calculating baselines and such.
324 // We could not get proper sizes anyway since there may be multiple
325 // fonts defined in the test_theme.
326 sub_model.cap_height = std::get<hi::dips>(value) * 0.7;
327 sub_model.x_height = std::get<hi::dips>(value) * 0.48;
328 }
329 }
330
331 void _activate_model_font_weight(int phase, theme_sub_model& sub_model, style_sheet_value value) const noexcept
332 {
333 if (phase != 0) {
334 return;
335 }
336
337 auto& text_style = sub_model.text_theme.find_or_add(phrasing_mask, language_mask);
338 hi_axiom(std::holds_alternative<hi::font_weight>(value));
339 text_style.variant.set_weight(std::get<hi::font_weight>(value));
340 }
341
342 template<style_sheet_declaration_name Name>
343 void _activate_model_color(int phase, theme_sub_model& sub_model, style_sheet_value value, bool important) const noexcept
344 {
346 }
347
348 template<style_sheet_declaration_name Name>
349 void _activate_model_length(int phase, theme_sub_model& sub_model, style_sheet_value value, bool important) const noexcept
350 {
352 }
353
354#define HI_X_COLOR_VALUE(NAME) \
355 template<> \
356 hi_no_inline void _activate_model_color<style_sheet_declaration_name::NAME>( \
357 int phase, theme_sub_model& sub_model, style_sheet_value value, bool important) const noexcept \
358 { \
359 if (phase != 1) { \
360 return; \
361 } \
362\
363 if (not sub_model.NAME##_important or important) { \
364 sub_model.NAME = std::get<hi::color>(value); \
365 } \
366 sub_model.NAME##_important |= important; \
367 sub_model.NAME##_assigned = 1; \
368 }
369
370 HI_X_COLOR_VALUE(background_color)
371 HI_X_COLOR_VALUE(border_color)
372 HI_X_COLOR_VALUE(fill_color)
373 HI_X_COLOR_VALUE(selection_color)
374 HI_X_COLOR_VALUE(caret_primary_color)
375 HI_X_COLOR_VALUE(caret_secondary_color)
376 HI_X_COLOR_VALUE(caret_overwrite_color)
377 HI_X_COLOR_VALUE(caret_compose_color)
378#undef HI_X_COLOR_VALUE
379
380#define HI_X_LENGTH_VALUE(NAME) \
381 template<> \
382 hi_no_inline void _activate_model_length<style_sheet_declaration_name::NAME>( \
383 int phase, theme_sub_model& sub_model, style_sheet_value value, bool important) const noexcept \
384 { \
385 if (phase != 1) { \
386 return; \
387 } \
388\
389 if (not sub_model.NAME##_important or important) { \
390 if (hilet dp_ptr = std::get_if<hi::dips>(&value)) { \
391 sub_model.NAME = *dp_ptr; \
392 } else if (hilet px_ptr = std::get_if<hi::pixels>(&value)) { \
393 sub_model.NAME = *px_ptr; \
394 } else if (hilet em_ptr = std::get_if<hi::em_quads>(&value)) { \
395 sub_model.NAME = static_cast<hi::dips>(sub_model.line_height) * em_ptr->count(); \
396 } else { \
397 hi_no_default(); \
398 } \
399 } \
400 sub_model.NAME##_important |= important; \
401 sub_model.NAME##_assigned = 1; \
402 }
403
404 HI_X_LENGTH_VALUE(width)
405 HI_X_LENGTH_VALUE(height)
406 HI_X_LENGTH_VALUE(border_width)
407 HI_X_LENGTH_VALUE(border_bottom_left_radius)
408 HI_X_LENGTH_VALUE(border_bottom_right_radius)
409 HI_X_LENGTH_VALUE(border_top_left_radius)
410 HI_X_LENGTH_VALUE(border_top_right_radius)
411 HI_X_LENGTH_VALUE(margin_left)
412 HI_X_LENGTH_VALUE(margin_bottom)
413 HI_X_LENGTH_VALUE(margin_right)
414 HI_X_LENGTH_VALUE(margin_top)
415 HI_X_LENGTH_VALUE(spacing_horizontal)
416 HI_X_LENGTH_VALUE(spacing_vertical)
417#undef HI_X_LENGTH_VALUE
418
419 void _activate_model_declaration(
420 int phase,
421 std::string const& model_path,
422 theme_sub_model& sub_model,
423 style_sheet_declaration_name name,
424 style_sheet_value value,
425 bool important) const noexcept
426 {
427 using enum style_sheet_declaration_name;
428
429 switch (name) {
430 case background_color:
431 return _activate_model_color<background_color>(phase, sub_model, value, important);
432 case border_bottom_left_radius:
433 return _activate_model_length<border_bottom_left_radius>(phase, sub_model, value, important);
434 case border_bottom_right_radius:
435 return _activate_model_length<border_bottom_right_radius>(phase, sub_model, value, important);
436 case border_color:
437 return _activate_model_color<border_color>(phase, sub_model, value, important);
438 case border_top_left_radius:
439 return _activate_model_length<border_top_left_radius>(phase, sub_model, value, important);
440 case border_top_right_radius:
441 return _activate_model_length<border_top_right_radius>(phase, sub_model, value, important);
442 case border_width:
443 return _activate_model_length<border_width>(phase, sub_model, value, important);
444 case caret_primary_color:
445 return _activate_model_color<caret_primary_color>(phase, sub_model, value, important);
446 case caret_secondary_color:
447 return _activate_model_color<caret_secondary_color>(phase, sub_model, value, important);
448 case caret_overwrite_color:
449 return _activate_model_color<caret_overwrite_color>(phase, sub_model, value, important);
450 case caret_compose_color:
451 return _activate_model_color<caret_compose_color>(phase, sub_model, value, important);
452 case fill_color:
453 return _activate_model_color<fill_color>(phase, sub_model, value, important);
454 case font_color:
455 return _activate_model_font_color(phase, sub_model, value);
456 case font_family:
457 return _activate_model_font_family(phase, sub_model, value);
458 case font_size:
459 return _activate_model_font_size(phase, sub_model, value);
460 case font_style:
461 return _activate_model_font_style(phase, sub_model, value);
462 case font_weight:
463 return _activate_model_font_weight(phase, sub_model, value);
464 case height:
465 return _activate_model_length<height>(phase, sub_model, value, important);
466 case margin_bottom:
467 return _activate_model_length<margin_bottom>(phase, sub_model, value, important);
468 case margin_left:
469 return _activate_model_length<margin_left>(phase, sub_model, value, important);
470 case margin_right:
471 return _activate_model_length<margin_right>(phase, sub_model, value, important);
472 case margin_top:
473 return _activate_model_length<margin_top>(phase, sub_model, value, important);
474 case selection_color:
475 return _activate_model_color<selection_color>(phase, sub_model, value, important);
476 case spacing_horizontal:
477 return _activate_model_length<spacing_horizontal>(phase, sub_model, value, important);
478 case spacing_vertical:
479 return _activate_model_length<spacing_vertical>(phase, sub_model, value, important);
480 case width:
481 return _activate_model_length<width>(phase, sub_model, value, important);
482 default:
484 }
485 }
486};
487
489 std::string name;
490 theme_mode mode;
491
494
495 [[nodiscard]] constexpr size_t size() const noexcept
496 {
497 return rule_sets.size();
498 }
499
500 [[nodiscard]] style_sheet_rule_set const& operator[](size_t i) const noexcept
501 {
502 return rule_sets[i];
503 }
504
507 void activate() const noexcept
508 {
509 // First activate the font-styles, so that the size of the font can be
510 // used to calculate the size of the other lengths.
511 _clear();
512 _activate_colors();
513 _activate(0);
514 _activate(1);
515 }
516
517private:
518 void _clear() const noexcept
519 {
520 for (hilet& model_path : theme_model_keys()) {
521 auto& model = theme_model_by_key(model_path);
522 model.clear();
523 }
524 }
525
526 void _activate_colors() const noexcept
527 {
528 for (hilet& color_name : color::list()) {
529 hilet it = std::find_if(colors.cbegin(), colors.cend(), [&color_name](hilet& x) {
530 return x.first == color_name;
531 });
532
533 if (it != colors.end()) {
534 hilet color_ptr = color::find(color_name);
535 hi_axiom_not_null(color_ptr);
536 *color_ptr = it->second;
537
538 hi_log_info("Named color '{}' assigned value by theme '{}:{}'", color_name, name, mode);
539 } else {
540 hi_log_error("Named color '{}' not declared in theme '{}:{}'", color_name, name, mode);
541 }
542 }
543 }
544
545 void _activate(int phase) const noexcept
546 {
547 for (hilet& model_path : theme_model_keys()) {
548 auto& model = theme_model_by_key(model_path);
549
550 for (hilet& rule_set : rule_sets) {
551 rule_set.activate_model(phase, model_path, model);
552 }
553 }
554 }
555};
556
557}} // namespace hi::v1
#define hi_assert(expression,...)
Assert if expression is true.
Definition assert.hpp:199
#define hi_no_default(...)
This part of the code should not be reachable, unless a programming bug.
Definition assert.hpp:279
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:253
#define hi_axiom_not_null(expression,...)
Assert if an expression is not nullptr.
Definition assert.hpp:272
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
DOXYGEN BUG.
Definition algorithm.hpp:13
font_weight
Definition font_weight.hpp:17
geometry/margins.hpp
Definition cache.hpp:11
unit< em_length_tag, double > em_quads
Em-quad: A font's line-height.
Definition units.hpp:196
unit< px_length_tag, double > pixels
A physical pixel on a display.
Definition units.hpp:192
unit< si_length_tag, double, std::ratio< 254, 960 '000 >::type > dips
Device Independent Pixels: 1/96 inch.
Definition units.hpp:188
font_style
The different styles a font-family comes with.
Definition font_style.hpp:24
theme_model_base & theme_model_by_key(std::string const &key)
Get a theme-model by key.
Definition theme_model.hpp:597
std::vector< std::string > theme_model_keys() noexcept
Get a list of all the keys registered at program startup.
Definition theme_model.hpp:586
static color * find(std::string const &name) noexcept
Find a color by name.
Definition color.hpp:310
A glob pattern.
Definition glob.hpp:41
constexpr bool matches(std::u32string_view str) const noexcept
Match the pattern with the given string.
Definition glob.hpp:209
Definition text_style.hpp:15
text_style & find_or_add(text_phrasing_mask phrasing_mask, language_tag language_mask) noexcept
Find or add an text_style to the text-theme.
Definition text_theme.hpp:40
Definition style_sheet.hpp:52
Definition style_sheet.hpp:72
Definition style_sheet.hpp:112
Definition style_sheet.hpp:215
Definition style_sheet.hpp:221
Definition style_sheet.hpp:488
void activate() const noexcept
Activate style sheet as the current theme.
Definition style_sheet.hpp:507
Definition theme_length.hpp:12
All the data of a theme for a specific widget-component at a specific state.
Definition theme_model.hpp:117
The theme models for all states for a specific widget component.
Definition theme_model.hpp:293
T cbegin(T... args)
T empty(T... args)
T cend(T... args)
T find_if(T... args)
T front(T... args)
T size(T... args)