HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
theme_model.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
9#pragma once
10
11#include "theme_length.hpp"
12#include "theme_state.hpp"
13#include "../geometry/module.hpp"
14#include "../color/module.hpp"
15#include "../text/module.hpp"
16#include "../concurrency/module.hpp"
17#include <mutex>
18#include <array>
19#include <map>
20#include <tuple>
21
22namespace hi { inline namespace v1 {
23
30public:
31 constexpr theme_model_length() noexcept = default;
32 constexpr theme_model_length(theme_model_length const&) = delete;
33 constexpr theme_model_length(theme_model_length&&) = delete;
34 constexpr theme_model_length& operator=(theme_model_length const&) = delete;
35 constexpr theme_model_length& operator=(theme_model_length&&) = delete;
36
37 constexpr theme_model_length& operator=(theme_length length) noexcept
38 {
39 switch (length.index()) {
40 case 0:
41 // Add precision when scaling with 0.25 intervals.
42 _v = narrow_cast<int>(std::round(std::get<pixels>(length).count() * 16.0));
43 hi_axiom(_v >= 0);
44 break;
45
46 case 1:
47 // Make the value negative to indicate that it needs to be scaled.
48 // Also add a bit of precision when scaling with 0.25 intervals.
49 _v = narrow_cast<int>(std::round(std::get<dips>(length).count() * -4.0));
50 hi_axiom(_v <= 0);
51 break;
52
53 default:
55 }
56
57 return *this;
58 }
59
60 constexpr operator hi::dips() const noexcept
61 {
62 hi_axiom(_v < 0);
63 return hi::dips{-wide_cast<double>(_v)};
64 }
65
72 [[nodiscard]] constexpr int operator()(int scale) const noexcept
73 {
74 hi_axiom(scale < 0);
75
76 // MSVC: A conditional jump (predicted by default) over the multiply
77 // instruction.
78 // clang: A conditional move of 1 into scale before the multiply.
79 auto r = _v;
80 if (r < 0) [[likely]] {
81 r *= scale;
82 }
83
84 // Round.
85 r += 8;
86
87 // MSVC: Even with `[[assume(r >= 0)]]` wont generate a SRA for r /= 16.
88 // clang: Needs `[[assume(r >= 0)]]` to generate a SRA for r /= 16.
89 r >>= 4;
90 return r;
91
92 // When changing the logic to the following, MSVC will use the
93 // conditional move trick, while clang gets confused and generates
94 // insanely bad and slow code (conditional jump, then in the branch
95 // actually multiplying `r` with the constant 1, then jumping back.
96 // In the other branch it multiplies `r` by `scale`).
97 //
98 // if (r >= 0) [[unlikely]] {
99 // scale = 1;
100 // }
101 // return r * scale;
102 }
103
104private:
109 int _v = 0;
110};
111
118 constexpr theme_sub_model() noexcept = default;
119 constexpr theme_sub_model(theme_sub_model const&) = delete;
120 constexpr theme_sub_model(theme_sub_model&&) = delete;
121 constexpr theme_sub_model& operator=(theme_sub_model const&) = delete;
122 constexpr theme_sub_model& operator=(theme_sub_model&&) = delete;
123
125 color background_color;
126 color fill_color;
127 color caret_primary_color;
128 color caret_secondary_color;
129 color caret_overwrite_color;
130 color caret_compose_color;
131 color selection_color;
132 color border_color;
133
134 theme_model_length border_bottom_left_radius;
135 theme_model_length border_bottom_right_radius;
136 theme_model_length border_top_left_radius;
137 theme_model_length border_top_right_radius;
138 theme_model_length border_width;
139
140 theme_model_length width;
141 theme_model_length height;
142 theme_model_length margin_bottom;
143 theme_model_length margin_left;
144 theme_model_length margin_top;
145 theme_model_length margin_right;
146 theme_model_length spacing_vertical;
147 theme_model_length spacing_horizontal;
148
149 theme_model_length x_height;
150 theme_model_length cap_height;
151 theme_model_length line_height;
152
153 uint64_t text_theme_assigned : 1 = 0;
154 uint64_t background_color_assigned : 1 = 0;
155 uint64_t fill_color_assigned : 1 = 0;
156 uint64_t caret_primary_color_assigned : 1 = 0;
157 uint64_t caret_secondary_color_assigned : 1 = 0;
158 uint64_t caret_overwrite_color_assigned : 1 = 0;
159 uint64_t caret_compose_color_assigned : 1 = 0;
160 uint64_t selection_color_assigned : 1 = 0;
161 uint64_t border_color_assigned : 1 = 0;
162 uint64_t border_bottom_left_radius_assigned : 1 = 0;
163 uint64_t border_bottom_right_radius_assigned : 1 = 0;
164 uint64_t border_top_left_radius_assigned : 1 = 0;
165 uint64_t border_top_right_radius_assigned : 1 = 0;
166 uint64_t border_width_assigned : 1 = 0;
167 uint64_t width_assigned : 1 = 0;
168 uint64_t height_assigned : 1 = 0;
169 uint64_t margin_bottom_assigned : 1 = 0;
170 uint64_t margin_left_assigned : 1 = 0;
171 uint64_t margin_top_assigned : 1 = 0;
172 uint64_t margin_right_assigned : 1 = 0;
173 uint64_t spacing_vertical_assigned : 1 = 0;
174 uint64_t spacing_horizontal_assigned : 1 = 0;
175
176 uint64_t text_theme_important : 1 = 0;
177 uint64_t background_color_important : 1 = 0;
178 uint64_t fill_color_important : 1 = 0;
179 uint64_t caret_primary_color_important : 1 = 0;
180 uint64_t caret_secondary_color_important : 1 = 0;
181 uint64_t caret_overwrite_color_important : 1 = 0;
182 uint64_t caret_compose_color_important : 1 = 0;
183 uint64_t selection_color_important : 1 = 0;
184 uint64_t border_color_important : 1 = 0;
185 uint64_t border_bottom_left_radius_important : 1 = 0;
186 uint64_t border_bottom_right_radius_important : 1 = 0;
187 uint64_t border_top_left_radius_important : 1 = 0;
188 uint64_t border_top_right_radius_important : 1 = 0;
189 uint64_t border_width_important : 1 = 0;
190 uint64_t width_important : 1 = 0;
191 uint64_t height_important : 1 = 0;
192 uint64_t margin_bottom_important : 1 = 0;
193 uint64_t margin_left_important : 1 = 0;
194 uint64_t margin_top_important : 1 = 0;
195 uint64_t margin_right_important : 1 = 0;
196 uint64_t spacing_vertical_important : 1 = 0;
197 uint64_t spacing_horizontal_important : 1 = 0;
198
199 void clear() noexcept
200 {
202
203 background_color = {};
204 fill_color = {};
205 caret_primary_color = {};
206 caret_secondary_color = {};
207 caret_overwrite_color = {};
208 caret_compose_color = {};
209 selection_color = {};
210 border_color = {};
211
212 border_bottom_left_radius = dips{0};
213 border_bottom_right_radius = dips{0};
214 border_top_left_radius = dips{0};
215 border_top_right_radius = dips{0};
216 border_width = dips{0};
217
218 width = dips{0};
219 height = dips{0};
220 margin_bottom = dips{0};
221 margin_left = dips{0};
222 margin_top = dips{0};
223 margin_right = dips{0};
224 spacing_vertical = dips{0};
225 spacing_horizontal = dips{0};
226
227 x_height = dips{0};
228 cap_height = dips{0};
229 line_height = dips{0};
230
231 text_theme_assigned = 0;
232 background_color_assigned = 0;
233 fill_color_assigned = 0;
234 caret_primary_color_assigned = 0;
235 caret_secondary_color_assigned = 0;
236 caret_overwrite_color_assigned = 0;
237 caret_compose_color_assigned = 0;
238 selection_color_assigned = 0;
239 border_color_assigned = 0;
240 border_bottom_left_radius_assigned = 0;
241 border_bottom_right_radius_assigned = 0;
242 border_top_left_radius_assigned = 0;
243 border_top_right_radius_assigned = 0;
244 border_width_assigned = 0;
245 width_assigned = 0;
246 height_assigned = 0;
247 margin_bottom_assigned = 0;
248 margin_left_assigned = 0;
249 margin_top_assigned = 0;
250 margin_right_assigned = 0;
251 spacing_vertical_assigned = 0;
252 spacing_horizontal_assigned = 0;
253
254 text_theme_important = 0;
255 background_color_important = 0;
256 fill_color_important = 0;
257 caret_primary_color_important = 0;
258 caret_secondary_color_important = 0;
259 caret_overwrite_color_important = 0;
260 caret_compose_color_important = 0;
261 selection_color_important = 0;
262 border_color_important = 0;
263 border_bottom_left_radius_important = 0;
264 border_bottom_right_radius_important = 0;
265 border_top_left_radius_important = 0;
266 border_top_right_radius_important = 0;
267 border_width_important = 0;
268 width_important = 0;
269 height_important = 0;
270 margin_bottom_important = 0;
271 margin_left_important = 0;
272 margin_top_important = 0;
273 margin_right_important = 0;
274 spacing_vertical_important = 0;
275 spacing_horizontal_important = 0;
276 }
277};
278
280 theme_state state;
281 int scale;
282};
283
284template<typename Context>
285concept theme_delegate = requires(Context const& c) {
286 {
287 c.sub_theme_selector()
288 } -> std::convertible_to<sub_theme_selector_type>;
289 };
290
294public:
295 theme_model_base(std::string_view tag) noexcept
296 {
297 hilet lock = std::scoped_lock(_map_mutex);
298
299 hi_assert(not tag.empty());
300 hi_assert(tag.front() != '/');
301 _map[std::format("/{}", tag)] = this;
302 }
303
304 void clear() noexcept
305 {
306 for (auto &sub_model: _sub_model_by_state) {
307 sub_model.clear();
308 }
309 }
310
311 [[nodiscard]] theme_sub_model& operator[](theme_state state) noexcept
312 {
313 return _sub_model_by_state[to_underlying(state)];
314 }
315
316 [[nodiscard]] theme_sub_model const& operator[](theme_state state) const noexcept
317 {
318 return _sub_model_by_state[to_underlying(state)];
319 }
320
321 [[nodiscard]] theme_sub_model const& get_model(theme_delegate auto const *delegate) const noexcept
322 {
323 hi_axiom_not_null(delegate);
324
325 hilet selector = delegate->sub_theme_selector();
326 return (*this)[selector.state];
327 }
328
329 [[nodiscard]] std::pair<theme_sub_model const&, int> get_model_and_scale(theme_delegate auto const *delegate) const noexcept
330 {
331 hi_axiom_not_null(delegate);
332
333 hilet selector = delegate->sub_theme_selector();
334 hi_axiom(selector.scale < 0, "scale must be negative so that negative points are converted to positive pixels");
335
336 return {(*this)[selector.state], selector.scale};
337 }
338
345 [[nodiscard]] hi::text_theme text_theme(theme_delegate auto const *delegate) const noexcept
346 {
347 hilet[model, scale] = get_model_and_scale(delegate);
348 auto r = model.text_theme;
349
350 // Scale the text-theme.
351 for (auto &style: r) {
352 // The original size in the model is in `round(DIPs * -4.0)`.
353 // scale is in `round(scale * -4.0)`
354 hi_axiom(style.size < 0);
355 style.size *= scale;
356 hi_axiom(style.size >= 0);
357 style.size += 8;
358 style.size >>= 4;
359 }
360
361 return r;
362 }
363
364 [[nodiscard]] color background_color(theme_delegate auto const *delegate) const noexcept
365 {
366 return get_model(delegate).background_color;
367 }
368
369 [[nodiscard]] color fill_color(theme_delegate auto const *delegate) const noexcept
370 {
371 return get_model(delegate).fill_color;
372 }
373
374 [[nodiscard]] color caret_primary_color(theme_delegate auto const *delegate) const noexcept
375 {
376 return get_model(delegate).caret_primary_color;
377 }
378
379 [[nodiscard]] color caret_secondary_color(theme_delegate auto const *delegate) const noexcept
380 {
381 return get_model(delegate).caret_secondary_color;
382 }
383
384 [[nodiscard]] color caret_overwrite_color(theme_delegate auto const *delegate) const noexcept
385 {
386 return get_model(delegate).caret_overwrite_color;
387 }
388
389 [[nodiscard]] color caret_compose_color(theme_delegate auto const *delegate) const noexcept
390 {
391 return get_model(delegate).caret_compose_color;
392 }
393
394 [[nodiscard]] color selection_color(theme_delegate auto const *delegate) const noexcept
395 {
396 return get_model(delegate).selection_color;
397 }
398
399 [[nodiscard]] color border_color(theme_delegate auto const *delegate) const noexcept
400 {
401 return get_model(delegate).border_color;
402 }
403
404 [[nodiscard]] int border_bottom_left_radius(theme_delegate auto const *delegate) const noexcept
405 {
406 hilet[model, scale] = get_model_and_scale(delegate);
407 return model.border_bottom_left_radius(scale);
408 }
409
410 [[nodiscard]] int border_bottom_right_radius(theme_delegate auto const *delegate) const noexcept
411 {
412 hilet[model, scale] = get_model_and_scale(delegate);
413 return model.border_bottom_right_radius(scale);
414 }
415
416 [[nodiscard]] int border_top_left_radius(theme_delegate auto const *delegate) const noexcept
417 {
418 hilet[model, scale] = get_model_and_scale(delegate);
419 return model.border_top_left_radius(scale);
420 }
421
422 [[nodiscard]] int border_top_right_radius(theme_delegate auto const *delegate) const noexcept
423 {
424 hilet[model, scale] = get_model_and_scale(delegate);
425 return model.border_top_right_radius(scale);
426 }
427
428 [[nodiscard]] corner_radiii border_radius(theme_delegate auto const *delegate) const noexcept
429 {
430 return {
431 border_bottom_left_radius(delegate),
432 border_bottom_right_radius(delegate),
433 border_top_left_radius(delegate),
434 border_top_right_radius(delegate)};
435 }
436
437 [[nodiscard]] int border_width(theme_delegate auto const *delegate) const noexcept
438 {
439 hilet[model, scale] = get_model_and_scale(delegate);
440 return model.border_width(scale);
441 }
442
443 [[nodiscard]] int width(theme_delegate auto const *delegate) const noexcept
444 {
445 hilet[model, scale] = get_model_and_scale(delegate);
446 return model.width(scale);
447 }
448
449 [[nodiscard]] int height(theme_delegate auto const *delegate) const noexcept
450 {
451 hilet[model, scale] = get_model_and_scale(delegate);
452 return model.height(scale);
453 }
454
455 [[nodiscard]] extent2i size(theme_delegate auto const *delegate) const noexcept
456 {
457 return {width(delegate), height(delegate)};
458 }
459
460 [[nodiscard]] int margin_bottom(theme_delegate auto const *delegate) const noexcept
461 {
462 hilet[model, scale] = get_model_and_scale(delegate);
463 return model.margin_bottom(scale);
464 }
465
466 [[nodiscard]] int margin_left(theme_delegate auto const *delegate) const noexcept
467 {
468 hilet[model, scale] = get_model_and_scale(delegate);
469 return model.margin_left(scale);
470 }
471
472 [[nodiscard]] int margin_top(theme_delegate auto const *delegate) const noexcept
473 {
474 hilet[model, scale] = get_model_and_scale(delegate);
475 return model.margin_top(scale);
476 }
477
478 [[nodiscard]] int margin_right(theme_delegate auto const *delegate) const noexcept
479 {
480 hilet[model, scale] = get_model_and_scale(delegate);
481 return model.margin_right(scale);
482 }
483
484 [[nodiscard]] marginsi margin(theme_delegate auto const *delegate) const noexcept
485 {
486 return {margin_left(delegate), margin_bottom(delegate), margin_right(delegate), margin_top(delegate)};
487 }
488
489 [[nodiscard]] int spacing_vertical(theme_delegate auto const *delegate) const noexcept
490 {
491 hilet[model, scale] = get_model_and_scale(delegate);
492 return model.spacing_vertical(scale);
493 }
494
495 [[nodiscard]] int spacing_horizontal(theme_delegate auto const *delegate) const noexcept
496 {
497 hilet[model, scale] = get_model_and_scale(delegate);
498 return model.spacing_horizontal(scale);
499 }
500
501 [[nodiscard]] int x_height(theme_delegate auto const *delegate) const noexcept
502 {
503 hilet[model, scale] = get_model_and_scale(delegate);
504 return model.x_height(scale);
505 }
506
507 [[nodiscard]] int cap_height(theme_delegate auto const *delegate) const noexcept
508 {
509 hilet[model, scale] = get_model_and_scale(delegate);
510 return model.cap_height(scale);
511 }
512
513 [[nodiscard]] int line_height(theme_delegate auto const *delegate) const noexcept
514 {
515 hilet[model, scale] = get_model_and_scale(delegate);
516 return model.line_height(scale);
517 }
518
519 [[nodiscard]] static std::vector<std::string> model_keys() noexcept
520 {
521 hilet lock = std::scoped_lock(_map_mutex);
522 auto r = std::vector<std::string>{};
523 r.reserve(_map.size());
524
525 for (auto& [key, value] : _map) {
526 r.push_back(key);
527 }
528
529 return r;
530 }
531
532 [[nodiscard]] static theme_model_base& model_by_key(std::string const& key)
533 {
534 hilet lock = std::scoped_lock(_map_mutex);
535
536 if (hilet it = _map.find(key); it != _map.end()) {
537 auto * const ptr = it->second;
538
540 return *ptr;
541 } else {
542 throw std::out_of_range(std::format("Could not find '{}'", key));
543 }
544 }
545
546private:
547 // The map is protected with a mutex because global variable initialization
548 // may be deferred and run on a different threads. However we can not
549 // use the deadlock detector as it will use a thread_local variable.
550 // The initialization order of static global variables and thread_local
551 // variables are undetermined.
553 inline static unfair_mutex_without_deadlock_detector _map_mutex;
554
556};
557
558template<fixed_string Tag>
559class theme_model final : public theme_model_base {
560public:
561 theme_model() noexcept : theme_model_base(Tag) {}
562};
563
577template<fixed_string Tag>
578inline auto theme = theme_model<Tag>{};
579
586[[nodiscard]] inline std::vector<std::string> theme_model_keys() noexcept
587{
588 return theme_model_base::model_keys();
589}
590
597[[nodiscard]] inline theme_model_base& theme_model_by_key(std::string const& key)
598{
599 return theme_model_base::model_by_key(key);
600}
601
602}} // 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
geo::extent< int, 2 > extent2i
A 2D extent.
Definition extent.hpp:512
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
unit< si_length_tag, double, std::ratio< 254, 960 '000 >::type > dips
Device Independent Pixels: 1/96 inch.
Definition units.hpp:188
auto theme
A tagged global variable to a theme model for a widget's component.
Definition theme_model.hpp:578
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
This is a RGBA floating point color.
Definition color.hpp:44
Definition text_theme.hpp:16
Definition theme_length.hpp:12
A length in pixels or dips (device independent pixels), optimized for read performance.
Definition theme_model.hpp:29
constexpr int operator()(int scale) const noexcept
Get the length in points.
Definition theme_model.hpp:72
All the data of a theme for a specific widget-component at a specific state.
Definition theme_model.hpp:117
Definition theme_model.hpp:279
The theme models for all states for a specific widget component.
Definition theme_model.hpp:293
hi::text_theme text_theme(theme_delegate auto const *delegate) const noexcept
Get the text theme for this widget's model.
Definition theme_model.hpp:345
Definition theme_model.hpp:559
Definition units.hpp:15
Definition theme_model.hpp:285
T clear(T... args)
T lock(T... args)
T reserve(T... args)
T round(T... args)