CYD-UI
A C++ library for building native graphic user interfaces
Loading...
Searching...
No Matches
component_stylist.cppm
Go to the documentation of this file.
1// Copyright (c) 2024, Víctor Castillo Agüero.
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4module;
5#include <tracy/Tracy.hpp>
6#include "cyd_fabric_modules/headers/macros/async_events.h"
7
8export module cydui.components.stylist;
9
10import std;
11import reflect;
12import fabric.logging;
13import fabric.wiring.signals;
14
16import cydui.graphics;
17export import cydui.components.base;
18
19namespace cydui::components {
20 export class component_stylist_t {
21 public:
22 using sptr = std::shared_ptr<component_stylist_t>;
23
24 static sptr make() {
25 return std::make_shared<component_stylist_t>();
26 }
27
28 public:
29 void compile_style_rule_list(const component_base_t::sptr& component, StyleArchive& style_archive) {
30 auto& style_rules = component->get_style_data().rules;
31 style_rules.clear();
32
33 style_archive.for_each_rule(component->name(), [&](const StyleRule::sptr &rule) {
34 for (const auto & selector : rule->selectors_) {
35 if (check_style_comb_selector(component, selector)) {
36 style_rules.emplace_back(selector.specificity(), rule);
37 break;
38 }
39 }
40 });
41
42 std::stable_sort(style_rules.begin(), style_rules.end(), [](const style_rule_instance_t &lhs, const style_rule_instance_t &rhs) {
43 return lhs.specificity > rhs.specificity;
44 });
45 }
46
47 void apply_style(const component_base_t::sptr& component) {
48 ZoneScopedN("Apply Style");
49 auto& style_data = component->get_style_data();
50
51 apply_style_rules(component);
52 style_data.apply_override();
53 style_data.apply_transform();
54
55 // TODO - This is a possibly expensive workaround
56 // EXPLANATION: I need to update the style of any component
57 // that depends on this one through a combined
58 // selector.
59 for (const auto& c : component->children) {
60 apply_style(c);
61 }
62 }
63
64 private:
65 void apply_style_rules(const component_base_t::sptr& component) {
66 ZoneScopedN("Apply Rules");
67 auto& style_data = component->get_style_data();
68 auto& style_rules = style_data.rules;
69 const auto& bti = refl::type_info::from<style_base_t>();
70 const auto& ti = component->get_style_type_info();
71
72 std::unordered_set<const refl::field_info*> pending_base_fields{};
73 for (const auto & bfti : bti.fields()) {
74 pending_base_fields.insert(&bfti);
75 }
76 std::unordered_set<const refl::field_info*> pending_fields{};
77 for (const auto & fti : ti.fields()) {
78 pending_fields.insert(&fti);
79 }
80
81 // keep track of which fields where just activated from rules being deactivated
82 std::unordered_set<const refl::field_info*> activated_base_fields{};
83 std::unordered_set<const refl::field_info*> activated_fields{};
84
85 // keep track of which fields where just deactivated from rules being deactivated
86 std::unordered_set<const refl::field_info*> deactivated_base_fields{};
87 std::unordered_set<const refl::field_info*> deactivated_fields{};
88
89 style_base_t& base_s = component->get_style();
90 for (auto& rule_instance: style_rules) {
91 auto& [specificity, rule, is_active, _, __] = rule_instance;
92 const bool should_be_active = check_style_comb_selector_vector(component, rule->selectors_, true, true);
93 if (not is_active and should_be_active) {
94 // Activate rule
95 ZoneScopedN("Activate Rule");
97 for (auto field_it = pending_base_fields.begin(); field_it != pending_base_fields.end();) {
98 const refl::field_info* field = *field_it;
99 if (apply_rule(component, &base_s, field_it, pending_base_fields, rule)) {
100 rule_instance.active_base_properties.insert(field);
101 activated_base_fields.insert(field);
102 }
103 }
105 for (auto field_it = pending_fields.begin(); field_it != pending_fields.end();) {
106 const refl::field_info* field = *field_it;
107 if (apply_rule(component, style_data.as_raw(), field_it, pending_fields, rule)) {
108 rule_instance.active_properties.insert(field);
109 activated_fields.insert(field);
110 }
111 }
112
113 rule_instance.active = true;
114 } else if (is_active and not should_be_active) {
115 ZoneScopedN("Deactivate Rule");
116 // Deactivate rule
117 for (const auto& prop: rule_instance.active_base_properties) {
118 deactivated_base_fields.insert(prop);
119 }
120 for (const auto& prop: rule_instance.active_properties) {
121 deactivated_fields.insert(prop);
122 }
123 rule_instance.active_base_properties.clear();
124 rule_instance.active_properties.clear();
125 rule_instance.active = false;
126 } else if (is_active and should_be_active) {
127 // Activate rule
128 ZoneScopedN("Update Rule");
131 for (auto field_it = activated_base_fields.begin(); field_it != activated_base_fields.end(); ++field_it) {
132 rule_instance.active_base_properties.erase(*field_it);
133 }
135 for (auto field_it = activated_fields.begin(); field_it != activated_fields.end(); ++field_it) {
136 rule_instance.active_properties.erase(*field_it);
137 }
138
141 for (auto field_it = deactivated_base_fields.begin(); field_it != deactivated_base_fields.end();) {
142 const refl::field_info* field = *field_it;
143 if (apply_rule(component, &base_s, field_it, deactivated_base_fields, rule)) {
144 rule_instance.active_base_properties.insert(field);
145 }
146 }
148 for (auto field_it = deactivated_fields.begin(); field_it != deactivated_fields.end();) {
149 const refl::field_info* field = *field_it;
150 if (apply_rule(component, style_data.as_raw(), field_it, deactivated_fields, rule)) {
151 rule_instance.active_properties.insert(field);
152 }
153 }
154
157 for (auto field_it = pending_base_fields.begin(); field_it != pending_base_fields.end();) {
158 if (rule_instance.active_base_properties.contains(*field_it)) {
159 field_it = pending_base_fields.erase(field_it);
160 } else {
161 ++field_it;
162 }
163 }
165 for (auto field_it = pending_fields.begin(); field_it != pending_fields.end();) {
166 if (rule_instance.active_properties.contains(*field_it)) {
167 field_it = pending_fields.erase(field_it);
168 } else {
169 ++field_it;
170 }
171 }
172
173 }
174 }
175
176 // Set deactivated fields to default values
177 for (const auto & field : deactivated_base_fields) {
178 style_data.reset_field(field, true);
179 }
180 for (const auto & field : deactivated_fields) {
181 style_data.reset_field(field, false);
182 }
183 }
184
185 bool apply_rule(const component_base_t::sptr &component, void *style_obj,
186 std::unordered_set<const refl::field_info *>::iterator &field_it,
187 std::unordered_set<const refl::field_info *> &pending_fields,
188 const StyleRule::sptr &rule) {
189 ZoneScopedN("Apply Rule");
190 const auto *field = *field_it;
191 if (rule->properties_.contains(field->name)) {
192 const auto &rule_field = rule->properties_.at(field->name);
193
194 if (apply_rule_property(component, style_obj, *field_it, rule_field)) {
195 field_it = pending_fields.erase(field_it);
196 return true;
197 }
198 }
199 ++field_it;
200 return false;
201 }
202
203 bool apply_rule_property(const component_base_t::sptr &component, void *style_obj,
204 const refl::field_info* field,
205 const refl::any &value) {
206 ZoneScopedN("Apply Property");
207 if (value.is(field->type())) {
208 field->type().assign_copy_of(value.data(), field->get_ptr(style_obj));
209 return true;
210 }
211
212 if (value.is(refl::type_info::from<std::string>())
213 and field->has_metadata<custom_style_parser>()) {
214 std::string str = value.as<std::string>();
215
216 refl::any rule_field_parsed =
217 field->get_metadata<custom_style_parser>().parser_function(str);
218 if (rule_field_parsed.is(field->type())) {
219 field->type().assign_copy_of(rule_field_parsed.data(), field->get_ptr(style_obj));
220 return true;
221 } else {
222 LOG::print{WARN} //
223 ("Custom parser type mismatch '{}::{}', expected: '{}', found: '{}'",
224 component->name(),
225 field->name,
226 field->type().name(),
227 rule_field_parsed.type().name());
228 }
229 }
230
231 if (field->type().is_type<vg::paint::type>()) {
232 if (value.is<color::Color>()) {
233 vg::paint::type& paint = field->get_ref<vg::paint::type>(style_obj);
234 const color::Color& paint_color = value.as<color::Color>();
235 paint = vg::paint::type::make(vg::paint::solid{paint_color});
236 return true;
237 }
238 }
239
240 LOG::print{WARN} //
241 ("Type mismatch '{}::{}', expected: '{}', found: '{}'",
242 component->name(),
243 field->name,
244 field->type().name(),
245 value.type().name());
246
247 return false;
248 }
249
250 bool check_style_comb_selector_vector(const component_base_t::sptr& component, const std::vector<StyleRuleCombinedSelector> &selectors,
251 bool check_tags = false, bool check_pseudo_states = false) {
252 ZoneScopedN("Check Selectors");
253 for (const auto &selector: selectors) {
254 if (check_style_comb_selector(component, selector, check_tags, check_pseudo_states)) {
255 return true;
256 }
257 }
258 return false;
259 }
260
261 bool check_style_comb_selector(const component_base_t::sptr& component, const StyleRuleCombinedSelector& selector, bool check_tags = false, bool check_pseudo_states = false) {
262 auto it = selector.selectors.rbegin();
263 if (not check_style_selector(component.get(), it->second, check_tags, check_pseudo_states)) {
264 return false;
265 }
266 StyleRuleCombinedSelector::kind_e kind = it->first;
267 ++it;
268
269 component_base_t* current = component.get();
270 while (it != selector.selectors.rend()) {
272 if (current->parent.has_value() and check_style_selector(current->parent.value(), it->second, check_tags, check_pseudo_states)) {
273 current = current->parent.value();
274 } else {
275 return false;
276 }
278 bool found = false;
279 while (current->parent.has_value()) {
280 if (check_style_selector(current->parent.value(), it->second, check_tags, check_pseudo_states)) {
281 found = true;
282 break;
283 }
284 current = current->parent.value();
285 }
286
287 if (not found) {
288 return false;
289 }
290 }
291
292 kind = it->first;
293 ++it;
294 }
295
296 return true;
297 }
298
299 bool check_style_selector(component_base_t* component, const StyleRuleSelector& selector, bool check_tags, bool check_pseudo_states) {
300 ZoneScopedN("Check Selector");
301 auto& style_data = component->get_style_data();
302 if (component->name() != selector.component) {
303 return false;
304 }
305
306 if (check_tags) {
307 for (const auto & tag : selector.tags) {
308 if (not style_data.tags.contains(tag)) {
309 return false;
310 }
311 }
312 }
313
314 if (check_pseudo_states) {
315 if (selector.pseudo_states.contains("hover") and not component->state()->hovering) {
316 return false;
317 }
318 if (selector.pseudo_states.contains("focus") and not component->state()->focused) {
319 return false;
320 }
321 }
322
323 return true;
324 }
325 };
326}
void for_each_rule(const std::string &component_name, auto &&func) const
std::list< std::shared_ptr< component_base_t > > children
virtual const refl::type_info & get_style_type_info() const =0
std::shared_ptr< component_base_t > sptr
void apply_style(const component_base_t::sptr &component)
void compile_style_rule_list(const component_base_t::sptr &component, StyleArchive &style_archive)
std::shared_ptr< component_stylist_t > sptr
std::vector< style_rule_instance_t > rules
std::shared_ptr< StyleRule > sptr
Definition rule.cppm:16
static type make(Paint paint_data_)