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 _v = narrow_cast<float>(std::get<pixels>(length).count());
42 hi_axiom(_v >= 0.0f);
43 break;
44
45 case 1:
46 // Make the value negative to indicate that it needs to be scaled.
47 _v = -narrow_cast<float>(std::get<dips>(length).count());
48 hi_axiom(_v <= 0.0f);
49 break;
50
51 default:
53 }
54
55 return *this;
56 }
57
58 constexpr operator hi::dips() const noexcept
59 {
60 hi_axiom(_v < 0.0f);
61 return hi::dips{-_v};
62 }
63
70 [[nodiscard]] constexpr float operator()(float scale) const noexcept
71 {
72 hi_axiom(scale < 0.0f);
73
74 // MSVC: A conditional jump (predicted by default) over the multiply
75 // instruction.
76 // clang: A conditional move of 1 into scale before the multiply.
77 auto r = _v;
78 if (r < 0.0f) [[likely]] {
79 r *= scale;
80 }
81
82 return r;
83
84 // When changing the logic to the following, MSVC will use the
85 // conditional move trick, while clang gets confused and generates
86 // insanely bad and slow code (conditional jump, then in the branch
87 // actually multiplying `r` with the constant 1, then jumping back.
88 // In the other branch it multiplies `r` by `scale`).
89 //
90 // if (r >= 0) [[unlikely]] {
91 // scale = 1;
92 // }
93 // return r * scale;
94 }
95
96private:
101 float _v = 0;
102};
103
110 constexpr theme_sub_model() noexcept = default;
111 constexpr theme_sub_model(theme_sub_model const&) = delete;
112 constexpr theme_sub_model(theme_sub_model&&) = delete;
113 constexpr theme_sub_model& operator=(theme_sub_model const&) = delete;
114 constexpr theme_sub_model& operator=(theme_sub_model&&) = delete;
115
117 color background_color;
118 color fill_color;
119 color caret_primary_color;
120 color caret_secondary_color;
121 color caret_overwrite_color;
122 color caret_compose_color;
123 color selection_color;
124 color border_color;
125
126 theme_model_length border_bottom_left_radius;
127 theme_model_length border_bottom_right_radius;
128 theme_model_length border_top_left_radius;
129 theme_model_length border_top_right_radius;
130 theme_model_length border_width;
131
132 theme_model_length width;
133 theme_model_length height;
134 theme_model_length margin_bottom;
135 theme_model_length margin_left;
136 theme_model_length margin_top;
137 theme_model_length margin_right;
138
139 theme_model_length x_height;
140 theme_model_length cap_height;
141 theme_model_length line_height;
142
143 uint64_t text_theme_assigned : 1 = 0;
144 uint64_t background_color_assigned : 1 = 0;
145 uint64_t fill_color_assigned : 1 = 0;
146 uint64_t caret_primary_color_assigned : 1 = 0;
147 uint64_t caret_secondary_color_assigned : 1 = 0;
148 uint64_t caret_overwrite_color_assigned : 1 = 0;
149 uint64_t caret_compose_color_assigned : 1 = 0;
150 uint64_t selection_color_assigned : 1 = 0;
151 uint64_t border_color_assigned : 1 = 0;
152 uint64_t border_bottom_left_radius_assigned : 1 = 0;
153 uint64_t border_bottom_right_radius_assigned : 1 = 0;
154 uint64_t border_top_left_radius_assigned : 1 = 0;
155 uint64_t border_top_right_radius_assigned : 1 = 0;
156 uint64_t border_width_assigned : 1 = 0;
157 uint64_t width_assigned : 1 = 0;
158 uint64_t height_assigned : 1 = 0;
159 uint64_t margin_bottom_assigned : 1 = 0;
160 uint64_t margin_left_assigned : 1 = 0;
161 uint64_t margin_top_assigned : 1 = 0;
162 uint64_t margin_right_assigned : 1 = 0;
163
164 uint64_t text_theme_important : 1 = 0;
165 uint64_t background_color_important : 1 = 0;
166 uint64_t fill_color_important : 1 = 0;
167 uint64_t caret_primary_color_important : 1 = 0;
168 uint64_t caret_secondary_color_important : 1 = 0;
169 uint64_t caret_overwrite_color_important : 1 = 0;
170 uint64_t caret_compose_color_important : 1 = 0;
171 uint64_t selection_color_important : 1 = 0;
172 uint64_t border_color_important : 1 = 0;
173 uint64_t border_bottom_left_radius_important : 1 = 0;
174 uint64_t border_bottom_right_radius_important : 1 = 0;
175 uint64_t border_top_left_radius_important : 1 = 0;
176 uint64_t border_top_right_radius_important : 1 = 0;
177 uint64_t border_width_important : 1 = 0;
178 uint64_t width_important : 1 = 0;
179 uint64_t height_important : 1 = 0;
180 uint64_t margin_bottom_important : 1 = 0;
181 uint64_t margin_left_important : 1 = 0;
182 uint64_t margin_top_important : 1 = 0;
183 uint64_t margin_right_important : 1 = 0;
184
185 void clear() noexcept
186 {
188
189 background_color = {};
190 fill_color = {};
191 caret_primary_color = {};
192 caret_secondary_color = {};
193 caret_overwrite_color = {};
194 caret_compose_color = {};
195 selection_color = {};
196 border_color = {};
197
198 border_bottom_left_radius = dips{0};
199 border_bottom_right_radius = dips{0};
200 border_top_left_radius = dips{0};
201 border_top_right_radius = dips{0};
202 border_width = dips{0};
203
204 width = dips{0};
205 height = dips{0};
206 margin_bottom = dips{0};
207 margin_left = dips{0};
208 margin_top = dips{0};
209 margin_right = dips{0};
210
211 x_height = dips{0};
212 cap_height = dips{0};
213 line_height = dips{0};
214
215 text_theme_assigned = 0;
216 background_color_assigned = 0;
217 fill_color_assigned = 0;
218 caret_primary_color_assigned = 0;
219 caret_secondary_color_assigned = 0;
220 caret_overwrite_color_assigned = 0;
221 caret_compose_color_assigned = 0;
222 selection_color_assigned = 0;
223 border_color_assigned = 0;
224 border_bottom_left_radius_assigned = 0;
225 border_bottom_right_radius_assigned = 0;
226 border_top_left_radius_assigned = 0;
227 border_top_right_radius_assigned = 0;
228 border_width_assigned = 0;
229 width_assigned = 0;
230 height_assigned = 0;
231 margin_bottom_assigned = 0;
232 margin_left_assigned = 0;
233 margin_top_assigned = 0;
234 margin_right_assigned = 0;
235
236 text_theme_important = 0;
237 background_color_important = 0;
238 fill_color_important = 0;
239 caret_primary_color_important = 0;
240 caret_secondary_color_important = 0;
241 caret_overwrite_color_important = 0;
242 caret_compose_color_important = 0;
243 selection_color_important = 0;
244 border_color_important = 0;
245 border_bottom_left_radius_important = 0;
246 border_bottom_right_radius_important = 0;
247 border_top_left_radius_important = 0;
248 border_top_right_radius_important = 0;
249 border_width_important = 0;
250 width_important = 0;
251 height_important = 0;
252 margin_bottom_important = 0;
253 margin_left_important = 0;
254 margin_top_important = 0;
255 margin_right_important = 0;
256 }
257};
258
260 theme_state state;
261 float scale;
262};
263
264template<typename Context>
265concept theme_delegate = requires(Context const& c) {
266 {
267 c.sub_theme_selector()
268 } -> std::convertible_to<sub_theme_selector_type>;
269};
270
274public:
275 theme_model_base(std::string_view tag) noexcept
276 {
277 hilet lock = std::scoped_lock(_map_mutex);
278
279 hi_assert(not tag.empty());
280 hi_assert(tag.front() != '/');
281 _map[std::format("/{}", tag)] = this;
282 }
283
284 void clear() noexcept
285 {
286 for (auto& sub_model : _sub_model_by_state) {
287 sub_model.clear();
288 }
289 }
290
291 [[nodiscard]] theme_sub_model& operator[](theme_state state) noexcept
292 {
293 return _sub_model_by_state[to_underlying(state)];
294 }
295
296 [[nodiscard]] theme_sub_model const& operator[](theme_state state) const noexcept
297 {
298 return _sub_model_by_state[to_underlying(state)];
299 }
300
301 [[nodiscard]] theme_sub_model const& get_model(theme_delegate auto const *delegate) const noexcept
302 {
303 hi_axiom_not_null(delegate);
304
305 hilet selector = delegate->sub_theme_selector();
306 return (*this)[selector.state];
307 }
308
309 [[nodiscard]] std::pair<theme_sub_model const&, float> get_model_and_scale(theme_delegate auto const *delegate) const noexcept
310 {
311 hi_axiom_not_null(delegate);
312
313 hilet selector = delegate->sub_theme_selector();
314 hi_axiom(selector.scale < 0.0f, "scale must be negative so that negative points are converted to positive pixels");
315
316 return {(*this)[selector.state], selector.scale};
317 }
318
325 [[nodiscard]] hi::text_theme text_theme(theme_delegate auto const *delegate) const noexcept
326 {
327 hilet[model, scale] = get_model_and_scale(delegate);
328 auto r = model.text_theme;
329
330 // Scale the text-theme.
331 for (auto& style : r) {
332 hi_axiom(style.size < 0.0f);
333 style.size *= scale;
334 hi_axiom(style.size >= 0.0f);
335 }
336
337 return r;
338 }
339
340 [[nodiscard]] color background_color(theme_delegate auto const *delegate) const noexcept
341 {
342 return get_model(delegate).background_color;
343 }
344
345 [[nodiscard]] color fill_color(theme_delegate auto const *delegate) const noexcept
346 {
347 return get_model(delegate).fill_color;
348 }
349
350 [[nodiscard]] color caret_primary_color(theme_delegate auto const *delegate) const noexcept
351 {
352 return get_model(delegate).caret_primary_color;
353 }
354
355 [[nodiscard]] color caret_secondary_color(theme_delegate auto const *delegate) const noexcept
356 {
357 return get_model(delegate).caret_secondary_color;
358 }
359
360 [[nodiscard]] color caret_overwrite_color(theme_delegate auto const *delegate) const noexcept
361 {
362 return get_model(delegate).caret_overwrite_color;
363 }
364
365 [[nodiscard]] color caret_compose_color(theme_delegate auto const *delegate) const noexcept
366 {
367 return get_model(delegate).caret_compose_color;
368 }
369
370 [[nodiscard]] color selection_color(theme_delegate auto const *delegate) const noexcept
371 {
372 return get_model(delegate).selection_color;
373 }
374
375 [[nodiscard]] color border_color(theme_delegate auto const *delegate) const noexcept
376 {
377 return get_model(delegate).border_color;
378 }
379
380 [[nodiscard]] float border_bottom_left_radius(theme_delegate auto const *delegate) const noexcept
381 {
382 hilet[model, scale] = get_model_and_scale(delegate);
383 return std::ceil(model.border_bottom_left_radius(scale));
384 }
385
386 [[nodiscard]] float border_bottom_right_radius(theme_delegate auto const *delegate) const noexcept
387 {
388 hilet[model, scale] = get_model_and_scale(delegate);
389 return std::ceil(model.border_bottom_right_radius(scale));
390 }
391
392 [[nodiscard]] float border_top_left_radius(theme_delegate auto const *delegate) const noexcept
393 {
394 hilet[model, scale] = get_model_and_scale(delegate);
395 return std::ceil(model.border_top_left_radius(scale));
396 }
397
398 [[nodiscard]] float border_top_right_radius(theme_delegate auto const *delegate) const noexcept
399 {
400 hilet[model, scale] = get_model_and_scale(delegate);
401 return std::ceil(model.border_top_right_radius(scale));
402 }
403
404 [[nodiscard]] corner_radii border_radius(theme_delegate auto const *delegate) const noexcept
405 {
406 hilet[model, scale] = get_model_and_scale(delegate);
407 return ceil(corner_radii{
408 model.border_bottom_left_radius(scale),
409 model.border_bottom_right_radius(scale),
410 model.border_top_left_radius(scale),
411 model.border_top_right_radius(scale)});
412 }
413
414 [[nodiscard]] float border_width(theme_delegate auto const *delegate) const noexcept
415 {
416 hilet[model, scale] = get_model_and_scale(delegate);
417 return std::ceil(model.border_width(scale));
418 }
419
420 [[nodiscard]] float width(theme_delegate auto const *delegate) const noexcept
421 {
422 hilet[model, scale] = get_model_and_scale(delegate);
423 return std::ceil(model.width(scale));
424 }
425
426 [[nodiscard]] float height(theme_delegate auto const *delegate) const noexcept
427 {
428 hilet[model, scale] = get_model_and_scale(delegate);
429 return std::ceil(model.height(scale));
430 }
431
432 [[nodiscard]] extent2 size(theme_delegate auto const *delegate) const noexcept
433 {
434 hilet[model, scale] = get_model_and_scale(delegate);
435 return ceil(extent2{model.width(scale), model.height(scale)});
436 }
437
438 [[nodiscard]] float margin_bottom(theme_delegate auto const *delegate) const noexcept
439 {
440 hilet[model, scale] = get_model_and_scale(delegate);
441 return std::ceil(model.margin_bottom(scale));
442 }
443
444 [[nodiscard]] float margin_left(theme_delegate auto const *delegate) const noexcept
445 {
446 hilet[model, scale] = get_model_and_scale(delegate);
447 return std::ceil(model.margin_left(scale));
448 }
449
450 [[nodiscard]] float margin_top(theme_delegate auto const *delegate) const noexcept
451 {
452 hilet[model, scale] = get_model_and_scale(delegate);
453 return std::ceil(model.margin_top(scale));
454 }
455
456 [[nodiscard]] float margin_right(theme_delegate auto const *delegate) const noexcept
457 {
458 hilet[model, scale] = get_model_and_scale(delegate);
459 return std::ceil(model.margin_right(scale));
460 }
461
462 [[nodiscard]] margins margin(theme_delegate auto const *delegate) const noexcept
463 {
464 hilet[model, scale] = get_model_and_scale(delegate);
465 return ceil(margins{model.margin_left(scale), model.margin_bottom(scale), model.margin_right(scale), model.margin_top(scale)});
466 }
467
468 [[nodiscard]] float x_height(theme_delegate auto const *delegate) const noexcept
469 {
470 hilet[model, scale] = get_model_and_scale(delegate);
471 return std::ceil(model.x_height(scale));
472 }
473
474 [[nodiscard]] float cap_height(theme_delegate auto const *delegate) const noexcept
475 {
476 hilet[model, scale] = get_model_and_scale(delegate);
477 return std::ceil(model.cap_height(scale));
478 }
479
480 [[nodiscard]] float line_height(theme_delegate auto const *delegate) const noexcept
481 {
482 hilet[model, scale] = get_model_and_scale(delegate);
483 return std::ceil(model.line_height(scale));
484 }
485
486 [[nodiscard]] static std::vector<std::string> model_keys() noexcept
487 {
488 hilet lock = std::scoped_lock(_map_mutex);
489 auto r = std::vector<std::string>{};
490 r.reserve(_map.size());
491
492 for (auto& [key, value] : _map) {
493 r.push_back(key);
494 }
495
496 return r;
497 }
498
499 [[nodiscard]] static theme_model_base& model_by_key(std::string const& key)
500 {
501 hilet lock = std::scoped_lock(_map_mutex);
502
503 if (hilet it = _map.find(key); it != _map.end()) {
504 auto *const ptr = it->second;
505
507 return *ptr;
508 } else {
509 throw std::out_of_range(std::format("Could not find '{}'", key));
510 }
511 }
512
513private:
514 // The map is protected with a mutex because global variable initialization
515 // may be deferred and run on a different threads. However we can not
516 // use the deadlock detector as it will use a thread_local variable.
517 // The initialization order of static global variables and thread_local
518 // variables are undetermined.
520 inline static unfair_mutex_without_deadlock_detector _map_mutex;
521
523};
524
525template<fixed_string Tag>
526class theme_model final : public theme_model_base {
527public:
528 theme_model() noexcept : theme_model_base(Tag) {}
529};
530
544template<fixed_string Tag>
545inline auto theme = theme_model<Tag>{};
546
553[[nodiscard]] inline std::vector<std::string> theme_model_keys() noexcept
554{
555 return theme_model_base::model_keys();
556}
557
564[[nodiscard]] inline theme_model_base& theme_model_by_key(std::string const& key)
565{
566 return theme_model_base::model_by_key(key);
567}
568
569}} // 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< float, 2 > extent2
A 2D extent.
Definition extent.hpp:502
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:545
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
This is a RGBA floating point color.
Definition color.hpp:44
constexpr value_type & width() noexcept
Access the x-as-width element from the extent.
Definition extent.hpp:166
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 float operator()(float scale) const noexcept
Get the length in points.
Definition theme_model.hpp:70
All the data of a theme for a specific widget-component at a specific state.
Definition theme_model.hpp:109
Definition theme_model.hpp:259
The theme models for all states for a specific widget component.
Definition theme_model.hpp:273
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:325
Definition theme_model.hpp:526
Definition units.hpp:15
Definition theme_model.hpp:265
T ceil(T... args)
T clear(T... args)
T lock(T... args)
T reserve(T... args)