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 width,
149};
150
151// clang-format off
152constexpr auto style_sheet_declaration_name_metadata = enum_metadata{
153 style_sheet_declaration_name::background_color, "background-color",
154 style_sheet_declaration_name::border_bottom_left_radius, "border-bottom-left-radius",
155 style_sheet_declaration_name::border_bottom_right_radius, "border-bottom-right-radius",
156 style_sheet_declaration_name::border_color, "border-color",
157 style_sheet_declaration_name::border_top_left_radius, "border-top-left-radius",
158 style_sheet_declaration_name::border_top_right_radius, "border-top-right-radius",
159 style_sheet_declaration_name::border_width, "border-width",
160 style_sheet_declaration_name::caret_primary_color, "caret-primary-color",
161 style_sheet_declaration_name::caret_secondary_color, "caret-secondary-color",
162 style_sheet_declaration_name::caret_overwrite_color, "caret-overwrite-color",
163 style_sheet_declaration_name::caret_compose_color, "caret-compose-color",
164 style_sheet_declaration_name::fill_color, "fill-color",
165 style_sheet_declaration_name::font_color, "font-color",
166 style_sheet_declaration_name::font_family, "font-family",
167 style_sheet_declaration_name::font_size, "font-size",
168 style_sheet_declaration_name::font_style, "font-style",
169 style_sheet_declaration_name::font_weight, "font-weight",
170 style_sheet_declaration_name::height, "height",
171 style_sheet_declaration_name::margin_bottom, "margin-bottom",
172 style_sheet_declaration_name::margin_left, "margin-left",
173 style_sheet_declaration_name::margin_right, "margin-right",
174 style_sheet_declaration_name::margin_top, "margin-top",
175 style_sheet_declaration_name::selection_color, "selection-color",
176 style_sheet_declaration_name::width, "width",
177};
178// clang-format on
179
180// clang-format off
181constexpr auto style_sheet_declaration_name_value_mask_metadata = enum_metadata{
182 style_sheet_declaration_name::background_color, style_sheet_value_mask::color,
183 style_sheet_declaration_name::border_bottom_left_radius, style_sheet_value_mask::length,
184 style_sheet_declaration_name::border_bottom_right_radius, style_sheet_value_mask::length,
185 style_sheet_declaration_name::border_color, style_sheet_value_mask::color,
186 style_sheet_declaration_name::border_top_left_radius, style_sheet_value_mask::length,
187 style_sheet_declaration_name::border_top_right_radius, style_sheet_value_mask::length,
188 style_sheet_declaration_name::border_width, style_sheet_value_mask::length,
189 style_sheet_declaration_name::caret_primary_color, style_sheet_value_mask::color,
190 style_sheet_declaration_name::caret_secondary_color, style_sheet_value_mask::color,
191 style_sheet_declaration_name::caret_overwrite_color, style_sheet_value_mask::color,
192 style_sheet_declaration_name::caret_compose_color, style_sheet_value_mask::color,
193 style_sheet_declaration_name::fill_color, style_sheet_value_mask::color,
194 style_sheet_declaration_name::font_color, style_sheet_value_mask::color,
195 style_sheet_declaration_name::font_family, style_sheet_value_mask::font_family_id,
196 style_sheet_declaration_name::font_size, style_sheet_value_mask::dips,
197 style_sheet_declaration_name::font_style, style_sheet_value_mask::font_style,
198 style_sheet_declaration_name::font_weight, style_sheet_value_mask::font_weight,
199 style_sheet_declaration_name::height, style_sheet_value_mask::length,
200 style_sheet_declaration_name::margin_bottom, style_sheet_value_mask::length,
201 style_sheet_declaration_name::margin_left, style_sheet_value_mask::length,
202 style_sheet_declaration_name::margin_right, style_sheet_value_mask::length,
203 style_sheet_declaration_name::margin_top, style_sheet_value_mask::length,
204 style_sheet_declaration_name::selection_color, style_sheet_value_mask::color,
205 style_sheet_declaration_name::width, style_sheet_value_mask::length,
206};
207// clang-format on
208
210 style_sheet_declaration_name name;
211 style_sheet_value value;
212 bool important = false;
213};
214
216 style_sheet_selector selector;
217 theme_state state = {};
218 theme_state_mask state_mask = {};
219 text_phrasing_mask phrasing_mask = {};
220 language_tag language_mask = {};
221
223
224 [[nodiscard]] constexpr size_t size() const noexcept
225 {
226 return declarations.size();
227 }
228
229 [[nodiscard]] constexpr style_sheet_declaration const& operator[](size_t i) const noexcept
230 {
231 return declarations[i];
232 }
233
234 [[nodiscard]] constexpr std::string get_selector_as_string() const noexcept
235 {
236 hi_assert(not selector.empty());
237
238 auto r = selector[0].get_path_as_glob_string();
239 for (auto i = 1_uz; i != selector.size(); ++i) {
240 r += ',';
241 r += selector[i].get_path_as_glob_string();
242 }
243
244 return r;
245 }
246
247 [[nodiscard]] generator<theme_state> matching_states(std::string const& model_path) const noexcept
248 {
249 if (selector.matches(model_path)) {
250 for (auto i = theme_state{}; i != theme_state{theme_state_size}; ++i) {
251 if ((i & state_mask) == state) {
252 co_yield i;
253 }
254 }
255 }
256 }
257
258 void activate_model(int phase, std::string const& model_path, theme_model_base& model) const noexcept
259 {
260 for (auto model_state : matching_states(model_path)) {
261 auto& sub_model = model[model_state];
262 for (hilet & [ name, value, important ] : declarations) {
263 _activate_model_declaration(phase, model_path, sub_model, name, value, important);
264 }
265 }
266 }
267
268private:
269 void _activate_model_font_color(int phase, theme_sub_model& sub_model, style_sheet_value value) const noexcept
270 {
271 if (phase != 0) {
272 return;
273 }
274
275 auto& text_style = sub_model.text_theme.find_or_add(phrasing_mask, language_mask);
276 hi_axiom(std::holds_alternative<hi::color>(value));
277 text_style.color = std::get<hi::color>(value);
278 }
279
280 void _activate_model_font_family(int phase, theme_sub_model& sub_model, style_sheet_value value) const noexcept
281 {
282 if (phase != 0) {
283 return;
284 }
285
286 auto& text_style = sub_model.text_theme.find_or_add(phrasing_mask, language_mask);
287 hi_axiom(std::holds_alternative<hi::font_family_id>(value));
288 text_style.family_id = std::get<hi::font_family_id>(value);
289 }
290
291 void _activate_model_font_style(int phase, theme_sub_model& sub_model, style_sheet_value value) const noexcept
292 {
293 if (phase != 0) {
294 return;
295 }
296
297 auto& text_style = sub_model.text_theme.find_or_add(phrasing_mask, language_mask);
298 hi_axiom(std::holds_alternative<hi::font_style>(value));
299 text_style.variant.set_style(std::get<hi::font_style>(value));
300 }
301
302 void _activate_model_font_size(int phase, theme_sub_model& sub_model, style_sheet_value value) const noexcept
303 {
304 if (phase != 0) {
305 return;
306 }
307
308 auto& text_style = sub_model.text_theme.find_or_add(phrasing_mask, language_mask);
309 hi_axiom(std::holds_alternative<hi::dips>(value));
310
311 // When retrieving the text-style it will be scaled by the UI-scale.
312 text_style.size = -narrow_cast<float>(std::get<hi::dips>(value).count());
313
314 if (not language_mask and not to_bool(phrasing_mask)) {
315 sub_model.line_height = std::get<hi::dips>(value);
316 // The following values are estimates.
317 // Hopefully good enough for calculating baselines and such.
318 // We could not get proper sizes anyway since there may be multiple
319 // fonts defined in the test_theme.
320 sub_model.cap_height = std::get<hi::dips>(value) * 0.7;
321 sub_model.x_height = std::get<hi::dips>(value) * 0.48;
322 }
323 }
324
325 void _activate_model_font_weight(int phase, theme_sub_model& sub_model, style_sheet_value value) const noexcept
326 {
327 if (phase != 0) {
328 return;
329 }
330
331 auto& text_style = sub_model.text_theme.find_or_add(phrasing_mask, language_mask);
332 hi_axiom(std::holds_alternative<hi::font_weight>(value));
333 text_style.variant.set_weight(std::get<hi::font_weight>(value));
334 }
335
336 template<style_sheet_declaration_name Name>
337 void _activate_model_color(int phase, theme_sub_model& sub_model, style_sheet_value value, bool important) const noexcept
338 {
340 }
341
342 template<style_sheet_declaration_name Name>
343 void _activate_model_length(int phase, theme_sub_model& sub_model, style_sheet_value value, bool important) const noexcept
344 {
346 }
347
348#define HI_X_COLOR_VALUE(NAME) \
349 template<> \
350 hi_no_inline void _activate_model_color<style_sheet_declaration_name::NAME>( \
351 int phase, theme_sub_model& sub_model, style_sheet_value value, bool important) const noexcept \
352 { \
353 if (phase != 1) { \
354 return; \
355 } \
356\
357 if (not sub_model.NAME##_important or important) { \
358 sub_model.NAME = std::get<hi::color>(value); \
359 } \
360 sub_model.NAME##_important |= important; \
361 sub_model.NAME##_assigned = 1; \
362 }
363
364 HI_X_COLOR_VALUE(background_color)
365 HI_X_COLOR_VALUE(border_color)
366 HI_X_COLOR_VALUE(fill_color)
367 HI_X_COLOR_VALUE(selection_color)
368 HI_X_COLOR_VALUE(caret_primary_color)
369 HI_X_COLOR_VALUE(caret_secondary_color)
370 HI_X_COLOR_VALUE(caret_overwrite_color)
371 HI_X_COLOR_VALUE(caret_compose_color)
372#undef HI_X_COLOR_VALUE
373
374#define HI_X_LENGTH_VALUE(NAME) \
375 template<> \
376 hi_no_inline void _activate_model_length<style_sheet_declaration_name::NAME>( \
377 int phase, theme_sub_model& sub_model, style_sheet_value value, bool important) const noexcept \
378 { \
379 if (phase != 1) { \
380 return; \
381 } \
382\
383 if (not sub_model.NAME##_important or important) { \
384 if (hilet dp_ptr = std::get_if<hi::dips>(&value)) { \
385 sub_model.NAME = *dp_ptr; \
386 } else if (hilet px_ptr = std::get_if<hi::pixels>(&value)) { \
387 sub_model.NAME = *px_ptr; \
388 } else if (hilet em_ptr = std::get_if<hi::em_quads>(&value)) { \
389 sub_model.NAME = static_cast<hi::dips>(sub_model.line_height) * em_ptr->count(); \
390 } else { \
391 hi_no_default(); \
392 } \
393 } \
394 sub_model.NAME##_important |= important; \
395 sub_model.NAME##_assigned = 1; \
396 }
397
398 HI_X_LENGTH_VALUE(width)
399 HI_X_LENGTH_VALUE(height)
400 HI_X_LENGTH_VALUE(border_width)
401 HI_X_LENGTH_VALUE(border_bottom_left_radius)
402 HI_X_LENGTH_VALUE(border_bottom_right_radius)
403 HI_X_LENGTH_VALUE(border_top_left_radius)
404 HI_X_LENGTH_VALUE(border_top_right_radius)
405 HI_X_LENGTH_VALUE(margin_left)
406 HI_X_LENGTH_VALUE(margin_bottom)
407 HI_X_LENGTH_VALUE(margin_right)
408 HI_X_LENGTH_VALUE(margin_top)
409#undef HI_X_LENGTH_VALUE
410
411 void _activate_model_declaration(
412 int phase,
413 std::string const& model_path,
414 theme_sub_model& sub_model,
415 style_sheet_declaration_name name,
416 style_sheet_value value,
417 bool important) const noexcept
418 {
419 using enum style_sheet_declaration_name;
420
421 switch (name) {
422 case background_color:
423 return _activate_model_color<background_color>(phase, sub_model, value, important);
424 case border_bottom_left_radius:
425 return _activate_model_length<border_bottom_left_radius>(phase, sub_model, value, important);
426 case border_bottom_right_radius:
427 return _activate_model_length<border_bottom_right_radius>(phase, sub_model, value, important);
428 case border_color:
429 return _activate_model_color<border_color>(phase, sub_model, value, important);
430 case border_top_left_radius:
431 return _activate_model_length<border_top_left_radius>(phase, sub_model, value, important);
432 case border_top_right_radius:
433 return _activate_model_length<border_top_right_radius>(phase, sub_model, value, important);
434 case border_width:
435 return _activate_model_length<border_width>(phase, sub_model, value, important);
436 case caret_primary_color:
437 return _activate_model_color<caret_primary_color>(phase, sub_model, value, important);
438 case caret_secondary_color:
439 return _activate_model_color<caret_secondary_color>(phase, sub_model, value, important);
440 case caret_overwrite_color:
441 return _activate_model_color<caret_overwrite_color>(phase, sub_model, value, important);
442 case caret_compose_color:
443 return _activate_model_color<caret_compose_color>(phase, sub_model, value, important);
444 case fill_color:
445 return _activate_model_color<fill_color>(phase, sub_model, value, important);
446 case font_color:
447 return _activate_model_font_color(phase, sub_model, value);
448 case font_family:
449 return _activate_model_font_family(phase, sub_model, value);
450 case font_size:
451 return _activate_model_font_size(phase, sub_model, value);
452 case font_style:
453 return _activate_model_font_style(phase, sub_model, value);
454 case font_weight:
455 return _activate_model_font_weight(phase, sub_model, value);
456 case height:
457 return _activate_model_length<height>(phase, sub_model, value, important);
458 case margin_bottom:
459 return _activate_model_length<margin_bottom>(phase, sub_model, value, important);
460 case margin_left:
461 return _activate_model_length<margin_left>(phase, sub_model, value, important);
462 case margin_right:
463 return _activate_model_length<margin_right>(phase, sub_model, value, important);
464 case margin_top:
465 return _activate_model_length<margin_top>(phase, sub_model, value, important);
466 case selection_color:
467 return _activate_model_color<selection_color>(phase, sub_model, value, important);
468 case width:
469 return _activate_model_length<width>(phase, sub_model, value, important);
470 default:
472 }
473 }
474};
475
477 std::string name;
478 theme_mode mode;
479
482
483 [[nodiscard]] constexpr size_t size() const noexcept
484 {
485 return rule_sets.size();
486 }
487
488 [[nodiscard]] style_sheet_rule_set const& operator[](size_t i) const noexcept
489 {
490 return rule_sets[i];
491 }
492
495 void activate() const noexcept
496 {
497 // First activate the font-styles, so that the size of the font can be
498 // used to calculate the size of the other lengths.
499 _clear();
500 _activate_colors();
501 _activate(0);
502 _activate(1);
503 }
504
505private:
506 void _clear() const noexcept
507 {
508 for (hilet& model_path : theme_model_keys()) {
509 auto& model = theme_model_by_key(model_path);
510 model.clear();
511 }
512 }
513
514 void _activate_colors() const noexcept
515 {
516 for (hilet& color_name : color::list()) {
517 hilet it = std::find_if(colors.cbegin(), colors.cend(), [&color_name](hilet& x) {
518 return x.first == color_name;
519 });
520
521 if (it != colors.end()) {
522 hilet color_ptr = color::find(color_name);
523 hi_axiom_not_null(color_ptr);
524 *color_ptr = it->second;
525
526 hi_log_info("Named color '{}' assigned value by theme '{}:{}'", color_name, name, mode);
527 } else {
528 hi_log_error("Named color '{}' not declared in theme '{}:{}'", color_name, name, mode);
529 }
530 }
531 }
532
533 void _activate(int phase) const noexcept
534 {
535 for (hilet& model_path : theme_model_keys()) {
536 auto& model = theme_model_by_key(model_path);
537
538 for (hilet& rule_set : rule_sets) {
539 rule_set.activate_model(phase, model_path, model);
540 }
541 }
542 }
543};
544
545}} // 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:564
std::vector< std::string > theme_model_keys() noexcept
Get a list of all the keys registered at program startup.
Definition theme_model.hpp:553
static color * find(std::string const &name) noexcept
Find a color by name.
Definition color.hpp:311
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:209
Definition style_sheet.hpp:215
Definition style_sheet.hpp:476
void activate() const noexcept
Activate style sheet as the current theme.
Definition style_sheet.hpp:495
Definition theme_length.hpp:12
All the data of a theme for a specific widget-component at a specific state.
Definition theme_model.hpp:109
The theme models for all states for a specific widget component.
Definition theme_model.hpp:273
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)