CYD-UI
A C++ library for building native graphic user interfaces
Loading...
Searching...
No Matches
component.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>
7#define STYLE_SETTER_RETURN_TYPE T&
8#define STYLE_SETTER_RETURN_EXPR return *static_cast<T*>(this);
10
11export module cydui.components;
12
13import std;
14
15import fabric.logging;
17export import cydui.components.base;
18export import cydui.components.anchors;
22 export template <typename T>
23 class component_t: public component_base_t, public attrs_component<T> {
24 public:
25 explicit component_t(identifier_t identifier = {})
26 : component_base_t(identifier) {
27 style_data = std::make_shared<style_data_t<typename T::style_t>>(std::string{name()});
28
29 auto dim_ctx = style_data->get_dimension_ctx();
30 internal_relations.cx.set_context(dim_ctx, "cx");
31 internal_relations.cy.set_context(dim_ctx, "cy");
32 internal_relations.cw.set_context(dim_ctx, "cw");
33 internal_relations.ch.set_context(dim_ctx, "ch");
36 internal_relations.cx = dim.x + dim.margin_left + dim.padding_left;
37 internal_relations.cy = dim.y + dim.margin_top + dim.padding_top;
38 }
39
40 ~component_t() override {
41 children.clear();
42 if (state()) {
43 state()->component_instance = std::nullopt;
44 }
45 };
46
47 public:
49 // Yes, the order of casting matters here because a conversion from `this` to
50 // `(attrs_component<>*)` does not work since that type is not a base of this
51 // class. So we need to cast to the base class first and then to its `void`
52 // specialization.
53 return reinterpret_cast<attrs_component<>*>(as_attrs());
54 }
55
56 std::string name() const final {
57 return std::string{refl::type_name<T>};
58 }
61 using namespace dimensions;
62
63 component_base_t* found = nullptr;
64 for (auto c = children.rbegin(); c != children.rend(); ++c) {
65 auto dim = c->get()->get_dimensional_relations();
66 auto cx = get_value(dim.x);
67 auto cy = get_value(dim.y);
68 auto mx = get_value(dim.margin_left);
69 auto my = get_value(dim.margin_top);
70 auto px = get_value(dim.padding_left);
71 auto py = get_value(dim.padding_top);
72 found = (*c)->find_by_coords(x - cx - mx - px, y - cy - my - py);
73 if (nullptr != found) {
74 return found;
75 }
76 }
77
78 if (x < 0 || x >= get_value(get_style().width) || y < 0
79 || y >= get_value(get_style().height)) {
80 return nullptr;
81 }
82 return this;
83 }
84
86 style_base_t& s = style_data->as_base();
87 return {
88 .x = s.x,
89 .y = s.y,
90 .width = s.width,
91 .height = s.height,
92 // .fixed_w = ,
93 // .fixed_h = ,
94 .margin_top = s.margin.top,
95 .margin_bottom = s.margin.bottom,
96 .margin_left = s.margin.left,
97 .margin_right = s.margin.right,
98 .padding_top = s.padding.top,
99 .padding_bottom = s.padding.bottom,
100 .padding_left = s.padding.left,
101 .padding_right = s.padding.right
102 };
103 }
104
105 std::shared_ptr<dimension_ctx_t> get_dimensional_context() final {
106 return style_data->get_dimensional_ctx();
107 }
108
109 const refl::type_info& get_style_type_info() const final {
110 using style_t = typename T::style_t;
111 const auto& ti = refl::type_info::from<style_t>();
112 return ti;
113 }
114
115 private:
116 void mount() final {
117 event_dispatcher.emplace(
119 );
120 }
121 void dismount() final {
122 for (const auto& c: children) {
124 }
125 children.clear();
126
127 // Delete event handler, this component will now stop reacting to events
128 event_dispatcher = std::nullopt;
129
130 state()->component_instance = std::nullopt;
131 }
132
133 bool update_with(std::shared_ptr<component_base_t> other) final {
134 ZoneScopedN("Update With");
135 auto other_component = std::dynamic_pointer_cast<component_t>(other);
136 if (!other_component) {
137 LOG::print{ERROR
138 }("Attempted to update component of type ({}) with type ({})", this->name(), other->name());
139 return false;
140 }
141
142 bool dirty = false;
143 if (not refl::deep_eq(props(), other_component->props())) {
144 props() = other_component->props();
145 dirty = true;
146 }
147 if (not(*as_attrs() == *(other_component->as_attrs()))) {
148 as_attrs()->update_with(*(other_component->as_attrs()));
149 dirty = true;
150 }
151
152 if (style_data->update_override_with(other_component->style_data->style_override_data)) {
153 dirty = true;
154 }
155
156 if (update_fields(other_component)) {
157 dirty = true;
158 }
159
160 return dirty;
161 }
162
163 std::shared_ptr<component_state_t> create_state_instance() final {
164 std::shared_ptr<component_state_t> state;
165 if constexpr (requires { new typename T::state_t{std::declval<typename T::props_t*>()}; }) {
166 state = std::shared_ptr<component_state_t>{
167 new typename T::state_t(static_cast<typename T::props_t*>(get_props()))
168 };
169 } else {
170 state = std::shared_ptr<component_state_t>{new typename T::state_t()};
171 }
172 state->set_component_name(this->name());
173
174 using EVH = typename T::event_handler_t;
175 if (EVH::handles_text_input) {
176 // LOG::print {DEBUG}("Component {} handles text input", name());
178 }
179
181 return state;
182 }
183
184 public:
185 T& tag(const std::unordered_set<std::string>& tags) {
186 for (const auto& tag: tags) {
187 style_data->tags.insert(tag);
188 }
189 return *dynamic_cast<T*>(this);
190 }
191
192 T& tag(const std::string& tag_) {
193 style_data->tags.insert(tag_);
194 return *dynamic_cast<T*>(this);
195 }
196
197 T& untag(const std::unordered_set<std::string>& tags) {
198 for (const auto& tag: tags) {
199 style_data->tags.erase(tag);
200 }
201 return *dynamic_cast<T*>(this);
202 }
203
204 T& untag(const std::string& tag_) {
205 style_data->tags.erase(tag_);
206 return *dynamic_cast<T*>(this);
207 }
208
209 T& set_id(const std::string& id_) {
211 return *dynamic_cast<T*>(this);
212 }
213
214 protected:
215 template <typename Fun>
216 void set_style_transform(Fun&& transform_func) {
217 auto& self_so = *dynamic_cast<style_data_t<typename T::style_t>*>(style_data.get());
218 self_so.set_style_transform(std::forward<Fun>(transform_func));
219 }
221 auto& self_so = *dynamic_cast<style_data_t<typename T::style_t>*>(style_data.get());
222 self_so.clear_style_transform();
223 }
224
225 private:
226 attrs_component<T>* as_attrs() {
227 // Yes, the order of casting matters here because a conversion from `this` to
228 // `(attrs_component<>*)` does not work since that type is not a base of this
229 // class. So we need to cast to the base class first and then to its `void`
230 // specialization.
231 return static_cast<attrs_component<T>*>(this);
232 }
233
234 auto& props() {
235 return (dynamic_cast<T*>(this)->props);
236 }
237
238 bool update_fields(std::shared_ptr<component_t>& other) {
239 bool dirty = false;
240 [&]<std::size_t... I>(std::index_sequence<I...>) {
241 (update_field<I>(dirty, other), ...);
242 }(std::make_index_sequence<refl::field_count<T>>());
243 return dirty;
244 }
245
246 template <std::size_t I>
247 void update_field(bool& dirty, std::shared_ptr<component_t>& other_) {
248 auto other = std::dynamic_pointer_cast<T>(other_);
249 using field = refl::field<T, I>;
250 using field_type = typename field::type;
251
252 if constexpr (packtl::is_type<fabric::wiring::signal, field_type>::value) {
253 auto& this_signal = field::from_instance(*dynamic_cast<T*>(this));
254 auto& other_signal = field::from_instance(*other);
255
256 if (this_signal != other_signal) {
257 this_signal = other_signal;
258 dirty = true;
259 }
260 }
261 }
262
263 public:
264#include "./style_setters.inc"
265 };
266} // namespace cydui::components
267
268export template <typename, typename = void>
269constexpr bool is_type_complete_v = false;
270
271export template <typename T>
272constexpr bool is_type_complete_v<T, std::void_t<decltype(sizeof(T))>> = true;
void set_id(const std::string &id)
std::shared_ptr< style_data_base_t > style_data
component_base_t(identifier_t identifier={})
void set_state(const component_state_ref &state)
std::list< std::shared_ptr< component_base_t > > children
void width(const dimension_t &value)
component_state_ref state() const
void y(const dimension_t &value)
virtual component_base_t * find_by_coords(dimension_t::value_type x, dimension_t::value_type y)=0
void height(const dimension_t &value)
std::optional< std::shared_ptr< event_dispatcher_base_t > > event_dispatcher
void x(const dimension_t &value)
static void set_is_text_input(component_state_t *it, bool is_text_input)
std::shared_ptr< dimension_ctx_t > get_dimensional_context() final
T & tag(const std::string &tag_)
component_base_t * find_by_coords(dimension_t::value_type x, dimension_t::value_type y) final
T & tag(const std::unordered_set< std::string > &tags)
T & set_id(const std::string &id_)
void set_style_transform(Fun &&transform_func)
component_dimensional_relations_t get_dimensional_relations() final
const refl::type_info & get_style_type_info() const final
std::string name() const final
attrs_component * attrs() final
component_t(identifier_t identifier={})
T & untag(const std::unordered_set< std::string > &tags)
T & untag(const std::string &tag_)
constexpr bool is_type_complete_v
static void dismount_component(component_base_t *component)
four_sided_property< dimension_t > margin
four_sided_property< dimension_t > padding