HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
theme_selector.hpp
1// Copyright Take Vos 2024.
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/utility.hpp"
8#include "../macros.hpp"
9#include <iterator>
10
38hi_export_module(hikogui.theme.theme_path);
39
40hi_export namespace hi {
41inline namespace v1 {
42
44 std::string name = {};
45 std::string id = {};
46 std::vector<std::string> classes = {};
47 std::vector<std::string> pseudos = {};
48 bool is_direct_parent = false;
49
50 constexpr void add_class(std::string rhs)
51 {
52 auto it = std::lower_bound(classes.cbegin(), classes.cend(), rhs);
53 if (it != classes.cend() and *it == rhs) {
54 return;
55 }
56 classes.insert(it, std::move(rhs));
57 }
58
59 constexpr void add_pseudo(std::string rhs)
60 {
61 auto it = std::lower_bound(pseudos.cbegin(), pseudos.cend(), rhs);
62 if (it != pseudos.cend() and *it == rhs) {
63 return;
64 }
65 pseudos.insert(it, std::move(rhs));
66 }
67
68 [[nodiscard]] constexpr friend bool operator==(theme_selector_element const& lhs, theme_selector_element const& rhs) noexcept
69 {
70 if (lhs.name != rhs.name) {
71 return false;
72 }
73 if (lhs.id != rhs.id) {
74 return false;
75 }
76
77 if (lhs.classes.size() != rhs.classes.size()) {
78 return false;
79 }
80
81 return lhs.is_direct_parent == rhs.is_direct_parent;
82 }
83
84 [[nodiscard]] constexpr friend bool
86 {
87 if (not needle.name.empty() and needle.name != haystack.name) {
88 return false;
89 }
90 if (not needle.id.empty() and needle.id != haystack.id) {
91 return false;
92 }
93 for (auto const& class_ : needle.classes) {
94 if (std::find(haystack.classes.begin(), haystack.classes.end(), class_) == haystack.classes.end()) {
95 return false;
96 }
97 }
98 for (auto const& pseudo : needle.pseudos) {
99 if (std::find(haystack.pseudos.begin(), haystack.pseudos.end(), pseudo) == haystack.pseudos.end()) {
100 return false;
101 }
102 }
103 return true;
104 }
105};
106
107class theme_selector : public std::vector<theme_selector_element> {};
108
109[[nodiscard]] constexpr bool match(
110 theme_selector::const_iterator needle_first,
111 theme_selector::const_iterator needle_last,
112 theme_selector::const_iterator haystack_first,
113 theme_selector::const_iterator haystack_last,
114 bool is_direct_child) noexcept
115{
116 if (needle_first == needle_last) {
117 // The match is successful if the last element of the needle matches the
118 // last element of the haystack.
120 }
121
122 while (haystack_first != haystack_last) {
123 if (match(*needle_first, *haystack_first)) {
124 // The first element matches, continue with the next element.
125 return match(needle_first + 1, needle_last, haystack_first + 1, haystack_last, needle_first->is_direct_parent);
126 }
127
128 if (is_direct_child) {
129 // This element should have directly matched (parent/child).
130 return false;
131 }
132
133 // This element may still match later on (a descendant).
135 }
136
137 hi_axiom(needle_first != needle_last);
138 return false;
139}
140
141[[nodiscard]] constexpr bool match(theme_selector const& needle, theme_selector const& haystack) noexcept
142{
143 return match(needle.begin(), needle.end(), haystack.begin(), haystack.end(), false);
144}
145
146template<std::input_iterator ItIn, std::sentinel_for<ItIn> ItEnd, std::output_iterator<theme_selector_element> ItOut>
147constexpr void parse_theme_selector(ItIn first, ItEnd last, ItOut out) noexcept
148{
149 enum class state_type { name, _class, id, pseudo, space };
150
151 auto state = state_type::name;
152 auto r = theme_selector_element{};
153 auto element_used = false;
154 auto buffer = std::string{};
155
156 auto flush_buffer = [&] () {
157 if (buffer.empty()) {
158 return;
159 }
160 if (state == state_type::name) {
161 r.name = std::exchange(buffer, {});
162 } else if (state == state_type::id) {
163 r.id = std::exchange(buffer, {});
164 } else if (state == state_type::_class) {
165 r.add_class(std::exchange(buffer, {}));
166 } else if (state == state_type::pseudo) {
167 r.add_pseudo(std::exchange(buffer, {}));
168 } else {
169 hi_no_default();
170 }
171 };
172
173 for (auto it = first; it != last; ++it) {
174 if (*it == ' ' or *it == '\t') {
175 flush_buffer();
176 state = state_type::space;
177
178 } else if (*it == '>') {
179 flush_buffer();
180 state = state_type::space;
181 r.is_direct_parent = true;
182
183 } else {
184 if (state == state_type::space) {
185 if (element_used) {
186 *out++ = std::exchange(r, {});
187 }
188 state = state_type::name;
189 }
190
191 element_used = true;
192 if (*it == '.') {
193 flush_buffer();
194 state = state_type::_class;
195
196 } else if (*it == ':') {
197 flush_buffer();
198 state = state_type::pseudo;
199
200 } else if (*it == '#') {
201 flush_buffer();
202 state = state_type::id;
203
204 } else if (*it == '*') {
205 state = state_type::name;
206
207 } else {
208 buffer += *it;
209 }
210 }
211 }
212
213 flush_buffer();
214 if (element_used) {
215 *out++ = r;
216 }
217}
218
219[[nodiscard]] constexpr theme_selector parse_theme_selector(std::string_view str) noexcept
220{
221 auto r = theme_selector{};
222 parse_theme_selector(str.begin(), str.end(), std::back_inserter(r));
223 return r;
224}
225
226} // namespace v1
227}
228
229template<>
230struct std::formatter<hi::theme_selector_element, char> : std::formatter<std::string, char> {
231 auto format(hi::theme_selector_element const& t, auto& fc) const
232 {
233 auto r = std::string{};
234
235 if (not t.name.empty()) {
236 r = t.name;
237 }
238
239 if (not t.id.empty()) {
240 r += '#';
241 r += t.id;
242 }
243
244 for (auto const& class_ : t.classes) {
245 r += '.';
246 r += class_;
247 }
248
249 for (auto const& pseudo : t.pseudos) {
250 r += ':';
251 r += pseudo;
252 }
253
254 if (r.empty()) {
255 r = "*";
256 }
257
258 if (t.is_direct_parent) {
259 r += " >";
260 }
261
262 return std::formatter<std::string, char>::format(std::move(r), fc);
263 }
264};
265
266template<>
267struct std::formatter<hi::theme_selector, char> : std::formatter<std::string, char> {
268 auto format(hi::theme_selector const& t, auto& fc) const
269 {
270 auto r = std::string{};
271
272 for (auto const& element : t) {
273 if (not r.empty()) {
274 r += ' ';
275 }
276 r += std::format("{}", element);
277 }
278
279 return std::formatter<std::string, char>::format(std::move(r), fc);
280 }
281};
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
The HikoGUI namespace.
Definition recursive_iterator.hpp:15
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:378
Definition theme_selector.hpp:43
Definition theme_selector.hpp:107
T back_inserter(T... args)
T cbegin(T... args)
T empty(T... args)
T cend(T... args)
T find(T... args)
T insert(T... args)
T lower_bound(T... args)
T move(T... args)
T size(T... args)