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
66 [[nodiscard]] constexpr int operator()(int scale) const noexcept
67 {
68 hi_axiom(scale < 0);
69
70 // MSVC: A conditional jump (predicted by default) over the multiply
71 // instruction.
72 // clang: A conditional move of 1 into scale before the multiply.
73 auto r = _v;
74 if (r < 0) [[likely]] {
75 r *= scale;
76 }
77
78 // Round.
79 r += 8;
80
81 // MSVC: Even with `[[assume(r >= 0)]]` wont generate a SRA for r /= 16.
82 // clang: Needs `[[assume(r >= 0)]]` to generate a SRA for r /= 16.
83 r >>= 4;
84 return r;
85
86 // When changing the logic to the following, MSVC will use the
87 // conditional move trick, while clang gets confused and generates
88 // insanely bad and slow code (conditional jump, then in the branch
89 // actually multiplying `r` with the constant 1, then jumping back.
90 // In the other branch it multiplies `r` by `scale`).
91 //
92 // if (r >= 0) [[unlikely]] {
93 // scale = 1;
94 // }
95 // return r * scale;
96 }
97
98private:
103 int _v = 0;
104};
105
112 constexpr theme_sub_model() noexcept = default;
113 constexpr theme_sub_model(theme_sub_model const&) = delete;
114 constexpr theme_sub_model(theme_sub_model&&) = delete;
115 constexpr theme_sub_model& operator=(theme_sub_model const&) = delete;
116 constexpr theme_sub_model& operator=(theme_sub_model&&) = delete;
117
118 hi::text_theme text_theme;
119 color background_color;
120 color fill_color;
121 color caret_primary_color;
122 color caret_secondary_color;
123 color caret_overwrite_color;
124 color caret_compose_color;
125 color selection_color;
126 color border_color;
127
128 theme_model_length border_bottom_left_radius;
129 theme_model_length border_bottom_right_radius;
130 theme_model_length border_top_left_radius;
131 theme_model_length border_top_right_radius;
132 theme_model_length border_width;
133
134 theme_model_length width;
135 theme_model_length height;
136 theme_model_length margin_bottom;
137 theme_model_length margin_left;
138 theme_model_length margin_top;
139 theme_model_length margin_right;
140 theme_model_length spacing_vertical;
141 theme_model_length spacing_horizontal;
142
143 theme_model_length font_x_height;
144 theme_model_length font_cap_height;
145 theme_model_length font_line_height;
146};
147
149 theme_state state;
150 int scale;
151};
152
153template<typename Context>
154concept theme_delegate = requires(Context const& c) {
155 {
156 c.sub_theme_selector()
157 } -> std::convertible_to<sub_theme_selector_type>;
158 };
159
163public:
164 theme_model_base(std::string_view tag) noexcept
165 {
166 hilet lock = std::scoped_lock(_map_mutex);
167 _map[std::string{tag}] = this;
168 }
169
170 [[nodiscard]] theme_sub_model& operator[](theme_state state) noexcept
171 {
172 return _sub_model_by_state[to_underlying(state)];
173 }
174
175 [[nodiscard]] theme_sub_model const& operator[](theme_state state) const noexcept
176 {
177 return _sub_model_by_state[to_underlying(state)];
178 }
179
180 [[nodiscard]] theme_sub_model const& get_model(theme_delegate auto const *delegate) const noexcept
181 {
182 hi_axiom_not_null(delegate);
183
184 hilet selector = delegate->sub_theme_selector();
185 return (*this)[selector.state];
186 }
187
188 [[nodiscard]] std::pair<theme_sub_model const&, int> get_model_and_scale(theme_delegate auto const *delegate) const noexcept
189 {
190 hi_axiom_not_null(delegate);
191
192 hilet selector = delegate->sub_theme_selector();
193 hi_axiom(selector.scale < 0, "scale must be negative so that negative points are converted to positive pixels");
194
195 return {(*this)[selector.state], selector.scale};
196 }
197
204 [[nodiscard]] hi::text_theme text_theme(theme_delegate auto const *delegate) const noexcept
205 {
206 hilet[model, scale] = get_model_and_scale(delegate);
207 auto r = model.text_theme;
208
209 // Scale the text-theme.
210 for (auto &style: r) {
211 // The original size in the model is in `round(DIPs * -4.0)`.
212 // scale is in `round(scale * -4.0)`
213 hi_axiom(style.size < 0);
214 style.size *= scale;
215 hi_axiom(style.size >= 0);
216 style.size += 8;
217 style.size >>= 4;
218 }
219
220 return r;
221 }
222
223 [[nodiscard]] color background_color(theme_delegate auto const *delegate) const noexcept
224 {
225 return get_model(delegate).background_color;
226 }
227
228 [[nodiscard]] color fill_color(theme_delegate auto const *delegate) const noexcept
229 {
230 return get_model(delegate).fill_color;
231 }
232
233 [[nodiscard]] color caret_primary_color(theme_delegate auto const *delegate) const noexcept
234 {
235 return get_model(delegate).caret_primary_color;
236 }
237
238 [[nodiscard]] color caret_secondary_color(theme_delegate auto const *delegate) const noexcept
239 {
240 return get_model(delegate).caret_secondary_color;
241 }
242
243 [[nodiscard]] color caret_overwrite_color(theme_delegate auto const *delegate) const noexcept
244 {
245 return get_model(delegate).caret_overwrite_color;
246 }
247
248 [[nodiscard]] color caret_compose_color(theme_delegate auto const *delegate) const noexcept
249 {
250 return get_model(delegate).caret_compose_color;
251 }
252
253 [[nodiscard]] color selection_color(theme_delegate auto const *delegate) const noexcept
254 {
255 return get_model(delegate).selection_color;
256 }
257
258 [[nodiscard]] color border_color(theme_delegate auto const *delegate) const noexcept
259 {
260 return get_model(delegate).border_color;
261 }
262
263 [[nodiscard]] int border_bottom_left_radius(theme_delegate auto const *delegate) const noexcept
264 {
265 hilet[model, scale] = get_model_and_scale(delegate);
266 return model.border_bottom_left_radius(scale);
267 }
268
269 [[nodiscard]] int border_bottom_right_radius(theme_delegate auto const *delegate) const noexcept
270 {
271 hilet[model, scale] = get_model_and_scale(delegate);
272 return model.border_bottom_right_radius(scale);
273 }
274
275 [[nodiscard]] int border_top_left_radius(theme_delegate auto const *delegate) const noexcept
276 {
277 hilet[model, scale] = get_model_and_scale(delegate);
278 return model.border_top_left_radius(scale);
279 }
280
281 [[nodiscard]] int border_top_right_radius(theme_delegate auto const *delegate) const noexcept
282 {
283 hilet[model, scale] = get_model_and_scale(delegate);
284 return model.border_top_right_radius(scale);
285 }
286
287 [[nodiscard]] corner_radiii border_radius(theme_delegate auto const *delegate) const noexcept
288 {
289 return {
290 border_bottom_left_radius(delegate),
291 border_bottom_right_radius(delegate),
292 border_top_left_radius(delegate),
293 border_top_right_radius(delegate)};
294 }
295
296 [[nodiscard]] int border_width(theme_delegate auto const *delegate) const noexcept
297 {
298 hilet[model, scale] = get_model_and_scale(delegate);
299 return model.border_width(scale);
300 }
301
302 [[nodiscard]] int width(theme_delegate auto const *delegate) const noexcept
303 {
304 hilet[model, scale] = get_model_and_scale(delegate);
305 return model.width(scale);
306 }
307
308 [[nodiscard]] int height(theme_delegate auto const *delegate) const noexcept
309 {
310 hilet[model, scale] = get_model_and_scale(delegate);
311 return model.height(scale);
312 }
313
314 [[nodiscard]] extent2i size(theme_delegate auto const *delegate) const noexcept
315 {
316 return {width(delegate), height(delegate)};
317 }
318
319 [[nodiscard]] int margin_bottom(theme_delegate auto const *delegate) const noexcept
320 {
321 hilet[model, scale] = get_model_and_scale(delegate);
322 return model.margin_bottom(scale);
323 }
324
325 [[nodiscard]] int margin_left(theme_delegate auto const *delegate) const noexcept
326 {
327 hilet[model, scale] = get_model_and_scale(delegate);
328 return model.margin_left(scale);
329 }
330
331 [[nodiscard]] int margin_top(theme_delegate auto const *delegate) const noexcept
332 {
333 hilet[model, scale] = get_model_and_scale(delegate);
334 return model.margin_top(scale);
335 }
336
337 [[nodiscard]] int margin_right(theme_delegate auto const *delegate) const noexcept
338 {
339 hilet[model, scale] = get_model_and_scale(delegate);
340 return model.margin_right(scale);
341 }
342
343 [[nodiscard]] marginsi margin(theme_delegate auto const *delegate) const noexcept
344 {
345 return {margin_left(delegate), margin_bottom(delegate), margin_right(delegate), margin_top(delegate)};
346 }
347
348 [[nodiscard]] int spacing_vertical(theme_delegate auto const *delegate) const noexcept
349 {
350 hilet[model, scale] = get_model_and_scale(delegate);
351 return model.spacing_vertical(scale);
352 }
353
354 [[nodiscard]] int spacing_horizontal(theme_delegate auto const *delegate) const noexcept
355 {
356 hilet[model, scale] = get_model_and_scale(delegate);
357 return model.spacing_horizontal(scale);
358 }
359
360 [[nodiscard]] int font_x_height(theme_delegate auto const *delegate) const noexcept
361 {
362 hilet[model, scale] = get_model_and_scale(delegate);
363 return model.font_x_height(scale);
364 }
365
366 [[nodiscard]] int font_cap_height(theme_delegate auto const *delegate) const noexcept
367 {
368 hilet[model, scale] = get_model_and_scale(delegate);
369 return model.font_cap_height(scale);
370 }
371
372 [[nodiscard]] int font_line_height(theme_delegate auto const *delegate) const noexcept
373 {
374 hilet[model, scale] = get_model_and_scale(delegate);
375 return model.font_line_height(scale);
376 }
377
378 [[nodiscard]] static std::vector<std::string> model_keys() noexcept
379 {
380 hilet lock = std::scoped_lock(_map_mutex);
381 auto r = std::vector<std::string>{};
382 r.reserve(_map.size());
383
384 for (auto& [key, value] : _map) {
385 r.push_back(key);
386 }
387
388 return r;
389 }
390
391 [[nodiscard]] static theme_model_base& model_by_key(std::string const& key) noexcept
392 {
393 hilet lock = std::scoped_lock(_map_mutex);
394 auto it = _map.find(key);
395
396 hi_assert(it != _map.end());
397 auto *ptr = it->second;
398
400 return *ptr;
401 }
402
403private:
404 // Theoretically it possible for global variable initialization to be
405 // done from multiple threads. Practically this may happen when loading
406 // libraries at run-time.
407 inline static unfair_mutex _map_mutex;
409
411};
412
413template<fixed_string Tag>
414class theme_model final : public theme_model_base {
415public:
416 theme_model() noexcept : theme_model_base(Tag) {}
417};
418
432template<fixed_string Tag>
433inline auto theme = theme_model<Tag>{};
434
441[[nodiscard]] inline std::vector<std::string> theme_model_keys() noexcept
442{
443 return theme_model_base::model_keys();
444}
445
451[[nodiscard]] inline theme_model_base& theme_model_by_key(std::string const& key) noexcept
452{
453 return theme_model_base::model_by_key(key);
454}
455
456}} // 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_assert_not_null(x,...)
Assert if an expression is not nullptr.
Definition assert.hpp:238
#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
auto theme
A tagged global variable to a theme model for a widget's component.
Definition theme_model.hpp:433
std::vector< std::string > theme_model_keys() noexcept
Get a list of all the keys registered at program startup.
Definition theme_model.hpp:441
theme_model_base & theme_model_by_key(std::string const &key) noexcept
Get a theme-model by key.
Definition theme_model.hpp:451
This is a RGBA floating point color.
Definition color.hpp:44
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:66
All the data of a theme for a specific widget-component at a specific state.
Definition theme_model.hpp:111
Definition theme_model.hpp:148
The theme models for all states for a specific widget component.
Definition theme_model.hpp:162
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:204
Definition theme_model.hpp:414
Definition theme_model.hpp:154
T lock(T... args)
T reserve(T... args)
T round(T... args)