CYD-UI
A C++ library for building native graphic user interfaces
Loading...
Searching...
No Matches
layout.cppm
Go to the documentation of this file.
1
5
6module;
7#include <cyd_fabric_modules/headers/macros/async_events.h>
8
9#include <tracy/Tracy.hpp>
10#define SDL_MAIN_HANDLED
11#include <SDL3/SDL.h>
12
13
14export module cydui:layout;
15
16import std;
17import fabric.logging;
18import fabric.profiling;
19
20export import cydui.application;
21export import cydui.dimensions;
22export import cydui.styling;
23
24export import cydui.components;
25export import cydui.components.renderer;
26export import cydui.components.updater;
27export import cydui.components.stylist;
28
30
31export import :window;
32
33export namespace cydui {
34 EVENT(RequestComponentFocus) {
35 std::shared_ptr<cydui::components::component_base_t> component;
36 };
37
38 template <components::ComponentConcept C>
39 Layout* create(C&& root_component);
40
41 template <components::ComponentConcept C>
42 Layout* create(C& root_component);
43
44 class Layout {
45 Layout(
46 const components::component_state_ref& _root_state,
48 )
49 : root_state(_root_state),
50 root(_root),
51 focused(_root_state) {
52 focused->focused = true;
53
54 component_renderer->compositing_signal.connect([this](compositing::compositing_node_t* node) {
55 this->win->compositor.compose(node);
56 });
57 component_updater->queue_render_signal.connect(
58 {component_renderer.get(), &components::component_renderer_t::queue_render}
59 );
60 component_updater->apply_style_signal.connect(
61 {component_stylist.get(), &components::component_stylist_t::apply_style}
62 );
63 component_updater->compile_style_rules_signal.connect(
65 );
66 }
67
68 public:
70 for (auto& item: listeners) {
71 item->remove();
72 }
73 }
74
75 public:
78
80
82
84
85 void clear_hovering_flag(const components::component_state_ref& state, const MotionEvent& ev);
86
88 components::component_state_t* state, const MotionEvent& ev, bool clear_children = true
89 );
90
91 void attach_stylesheet(const StyleSheet::sptr& style_sheet);
92
93 private:
94 void update_dimensions();
95
96 public:
97 template <components::ComponentConcept C>
98 friend Layout* create(C&& root_component);
99
100 template <components::ComponentConcept C>
101 friend Layout* create(C& root_component);
102
103 public:
104 // drag_n_drop::dragging_context_t dragging_context {};
105
106 void bind_window(const CWindow::sptr& _win);
107
109 return root_state;
110 }
111
112 private:
113 std::vector<fabric::async::raw_listener::sptr> make_event_listeners();
114
115 private:
116 CWindow::sptr win = nullptr;
117
120
121 components::component_state_ref hovering = nullptr;
122 components::component_state_ref focused = nullptr;
123
124 std::vector<fabric::async::raw_listener::sptr> listeners{};
125
126 StyleArchive::sptr style_archive{StyleArchive::make()};
127
130 };
132 };
134 };
135 };
136
137 //* IMPL
138
139 template <components::ComponentConcept C>
140 Layout* create(C&& root_component) {
141 ZoneScopedN("Layout:create");
142 auto root = std::make_shared<C>(std::forward<C>(root_component));
144 root_state->component_instance = root;
145 auto* lay = new Layout(root_state, root);
146 return lay;
147 }
148
149 template <components::ComponentConcept C>
150 Layout* create(C& root_component) {
151 ZoneScopedN("Layout:create");
152 auto root = std::make_shared<C>(root_component);
154 root_state->component_instance = root;
155 auto* lay = new Layout(root_state, root);
156 return lay;
157 }
158
159#define COMPUTE(DIM) \
160 { \
161 auto compute_res = cydui::dimensions::compute_dimension(DIM); \
162 if (not compute_res) { \
163 return false; \
164 } \
165 }
166
167} // namespace cydui
168
169namespace cydui {
171 using namespace cydui::dimensions;
172
173 static const refl::field_info* width_fi =
174 refl::type_info::from<components::style_base_t>().field_by_name("width").value();
175 static const refl::field_info* height_fi =
176 refl::type_info::from<components::style_base_t>().field_by_name("height").value();
177
178 auto dim = rt->get_dimensional_relations();
179 auto& int_rel = rt->get_internal_relations();
180
181 bool fixed_w = rt->get_style_data().has_base_field_override(width_fi);
182 bool fixed_h = rt->get_style_data().has_base_field_override(height_fi);
183
185 COMPUTE(dim.x)
186 COMPUTE(dim.y)
187
188 COMPUTE(dim.margin_top)
189 COMPUTE(dim.margin_right)
190 COMPUTE(dim.margin_bottom)
191 COMPUTE(dim.margin_left)
192
193 COMPUTE(dim.padding_top)
194 COMPUTE(dim.padding_right)
195 COMPUTE(dim.padding_bottom)
196 COMPUTE(dim.padding_left)
197
198
199 if (rt->parent.has_value()) {
200 auto& parent_int_rel = rt->parent.value()->get_internal_relations();
201
202 int_rel.cx = parent_int_rel.cx + dim.x + dim.margin_left + dim.padding_left;
203 int_rel.cy = parent_int_rel.cy + dim.y + dim.margin_top + dim.padding_top;
204 } else {
205 int_rel.cx = dim.x + dim.margin_left + dim.padding_left;
206 int_rel.cy = dim.y + dim.margin_top + dim.padding_top;
207 }
208 COMPUTE(int_rel.cx)
209 COMPUTE(int_rel.cy)
210
211
212 if (fixed_w) {
213 COMPUTE(dim.width)
214 int_rel.cw =
215 dim.width - dim.padding_left - dim.padding_right - dim.margin_left - dim.margin_right;
216 COMPUTE(int_rel.cw)
217 }
218 if (fixed_h) {
219 COMPUTE(dim.height)
220 int_rel.ch =
221 dim.height - dim.padding_top - dim.padding_bottom - dim.margin_top - dim.margin_bottom;
222 COMPUTE(int_rel.ch)
223 }
224
226 std::vector<std::shared_ptr<components::component_base_t>> pending;
227
228 auto total_w = 0_px;
229 auto total_h = 0_px;
230 for (auto& child: rt->children) {
231 // compute_dimensions(child);
232 // if error (circular dependency), skip for now, and then recalculate
233 if (compute_dimensions(child.get())) {
234 auto c_dim = child->get_dimensional_relations();
235 auto child_max_w = dimensions::get_value(c_dim.x) + dimensions::get_value(c_dim.width);
236 auto child_max_h = dimensions::get_value(c_dim.y) + dimensions::get_value(c_dim.height);
237 total_w = std::max(total_w, child_max_w);
238 total_h = std::max(total_h, child_max_h);
239 } else {
240 pending.push_back(child);
241 }
242 }
243
244 if (not fixed_w) {
245 // If not given, or given has error (ie: circular dep)
246 int_rel.cw = total_w;
247 COMPUTE(int_rel.cw)
248 dim.width =
249 int_rel.cw + dim.padding_left + dim.padding_right + dim.margin_left + dim.margin_right;
250 COMPUTE(dim.width)
251 }
252
253 if (not fixed_h) {
254 // If not given, or given has error (ie: circular dep)
255 int_rel.ch = total_h;
256 COMPUTE(int_rel.ch)
257 dim.height =
258 int_rel.ch + dim.padding_top + dim.padding_bottom + dim.margin_top + dim.margin_bottom;
259 COMPUTE(dim.height)
260 }
261
262 return std::all_of(pending.begin(), pending.end(), [](const auto& it) {
263 return compute_dimensions(it.get());
264 });
265 }
266
267#undef COMPUTE
268
269 void Layout::update_dimensions() {
270 ZoneScopedN("Dimensions");
271
272 if (!compute_dimensions(root.get()) && root->parent.has_value()) {
273 components::component_base_t* c = root->parent.value();
274 while (c && !compute_dimensions(c)) {
275 if (!c->parent.has_value()) {
276 LOG::print{ERROR}("Could not compute dimensions");
277 // TODO - Catch dimensional error
278 }
279 c = c->parent.value();
280 }
281 }
282 }
283
285 ZoneScopedN("Update Component");
286 std::scoped_lock lock{component_renderer->compositing_mutex()};
287 component_updater->update(target, *style_archive);
288 component_stylist->apply_style(target);
289 }
290
292 if (c->state()->_dirty) {
293 std::scoped_lock lock{component_renderer->compositing_mutex()};
294 component_updater->update(c, *style_archive);
295 return true;
296 } else {
297 bool any = false;
298 for (auto& item: c->children)
299 any = update_all_dirty(item) || any; // ! Order here matters
300 // ? update_if_dirty() needs to be called before `any` is checked.
301 return any;
302 }
303 }
304
306 ZoneScopedN("Render If Dirty");
307 {
308 ZoneScopedN("Update");
309 if (not update_all_dirty(c)) {
310 return false;
311 }
312 }
313 component_stylist->apply_style(root);
314 update_dimensions();
315 component_renderer->render(*win->native(), root);
316 return true;
317 }
318
321 ZoneScopedN("Find by coords");
322 return root->find_by_coords(x, y);
323 }
324
326 style_archive->add_stylesheet(style_sheet);
327 }
328
329
330 // static event_handler_t* get_instance_ev_handler(component_state_t* component_state) {
331 //
332 // }
333
335 ZoneScopedN("Layout:bind_window");
336 this->win = _win;
337
339 root_state->window = this->win;
340
341 auto [w, h] = win->get_size();
342 auto dim = root->get_dimensional_relations();
343 static const auto* width_field = refl::type_info::from<components::style_base_t>()
344 .field_by_offset(offsetof(components::style_base_t, width))
345 .value();
346 static const auto* height_field = refl::type_info::from<components::style_base_t>()
347 .field_by_offset(offsetof(components::style_base_t, height))
348 .value();
349 root->get_style_data().set_base_field_override(
350 width_field, dimension_t{dimensions::screen_measure{double(w)}}
351 );
352 root->get_style_data().set_base_field_override(
353 height_field, dimension_t{dimensions::screen_measure{double(h)}}
354 );
356 component_stylist->apply_style(root);
357
358 // TODO - Not sure why this needs to be done twice
359 component_updater->update(root, *style_archive);
360 update_dimensions();
361 component_updater->update(root, *style_archive);
362 update_dimensions();
363 // ================================================
364
365 component_renderer->render(*win->native(), root);
366
368 listeners = make_event_listeners();
369
370 AnimationSystem& anim_system =
371 *win->get_executor()->get_spawn_context()->get_resource<AnimationSystem>();
372 anim_system.s_repaint.connect([&](const components::component_base_t::sptr& component) {
373 component_renderer->repaint_component(component);
374 });
375 anim_system.s_render_all.connect([&](bool& should_compose) {
376 should_compose = should_compose or component_renderer->render_all(*win->native(), root);
377 });
378 anim_system.s_compose_all.connect([&]() {
379 component_renderer->compose_all(*win->native(), root);
380 });
381 }
382
384 components::component_state_t* state, const MotionEvent& ev, bool clear_children
385 ) {
386 ZoneScopedN("Layout:set_hovering_flag");
387 if (clear_children) {
388 for (const auto& c_state: std::ranges::views::values(state->children_states)) {
389 clear_hovering_flag(c_state, ev);
390 }
391 }
392
393 if (not state->hovering) {
394 state->hovering = true;
395
396 if (state->component_instance.has_value()) {
397 auto& int_rel = state->component_instance.value()->get_internal_relations();
398 auto rel_x = ev.x - dimensions::get_value(int_rel.cx);
399 auto rel_y = ev.y - dimensions::get_value(int_rel.cy);
400 state->component_instance.value()->get_event_dispatcher()->dispatch_mouse_enter(
401 rel_x, rel_y
402 );
403
404 component_stylist->apply_style(state->component_instance.value());
405 }
406
407 state->mark_dirty();
408
409 if (state->parent()) {
410 if (state->parent()->hovering) {
411 for (auto& [id, c_state]: state->parent()->children_states) {
412 if (c_state.get() != state) {
413 clear_hovering_flag(c_state, ev);
414 }
415 }
416 } else {
417 set_hovering_flag(state->parent(), ev, false);
418 }
419 }
420
421 return true;
422 }
423
424 return false;
425 }
426
427 void
429 ZoneScopedN("Layout:clear_hovering_flag");
430 if (state->hovering) {
431 state->hovering = false;
432
433 if (state->component_instance.has_value()) {
434 auto& h_int_rel = state->component_instance.value()->get_internal_relations();
435 auto exit_rel_x = ev.x - dimensions::get_value(h_int_rel.cx);
436 auto exit_rel_y = ev.y - dimensions::get_value(h_int_rel.cy);
437 state->component_instance.value()->get_event_dispatcher()->dispatch_mouse_exit(
438 exit_rel_x, exit_rel_y
439 );
440
441 component_stylist->apply_style(state->component_instance.value());
442 }
443
444 state->mark_dirty();
445
446 for (const auto& c_state: std::ranges::views::values(state->children_states)) {
447 clear_hovering_flag(c_state, ev);
448 }
449 }
450 }
451
452 template <typename Component>
453 CWindow::builder_t CWindow::make(typename Component::props_t props) {
454 ZoneScopedN("CWindow:make");
455 auto layout = cydui::create(Component{props});
457 }
458} // namespace cydui
459
460namespace cydui {
461 void bind_layout(Layout* layout, const CWindow::sptr& window) {
462 layout->bind_window(window);
463 }
464
465 void CWindow::builder_t::configure_layout_style() {
466 ZoneScopedN("CWindow:builder:configure_layout_style");
467 for (const auto& path: this->stylesheets_) {
468 this->layout_->attach_stylesheet(StyleSheet::parse(path));
469 }
470 for (const auto& str: this->styles_) {
471 this->layout_->attach_stylesheet(StyleSheet::parse(str));
472 }
473 }
474} // namespace cydui
fabric::wiring::output_signal< AnimationSystem, bool & > s_render_all
fabric::wiring::output_signal< AnimationSystem, components::component_base_t::sptr > s_repaint
fabric::wiring::output_signal< AnimationSystem > s_compose_all
Layout * layout
Definition window.cppm:162
std::shared_ptr< CWindow > sptr
Definition window.cppm:46
static builder_t make(Layout *layout)
Definition window.cppm:152
bool render_if_dirty(const components::component_base_t::sptr &c)
Definition layout.cppm:305
void bind_window(const CWindow::sptr &_win)
Definition layout.cppm:334
bool set_hovering_flag(components::component_state_t *state, const MotionEvent &ev, bool clear_children=true)
Definition layout.cppm:383
bool update_all_dirty(const components::component_base_t::sptr &c)
Definition layout.cppm:291
void update_component(const components::component_base_t::sptr &target)
Definition layout.cppm:284
void attach_stylesheet(const StyleSheet::sptr &style_sheet)
Definition layout.cppm:325
void clear_hovering_flag(const components::component_state_ref &state, const MotionEvent &ev)
Definition layout.cppm:428
friend Layout * create(C &&root_component)
Definition layout.cppm:140
components::component_state_ref get_root_state() const
Definition layout.cppm:108
components::component_base_t * find_by_coords(dimensions::screen_measure x, dimensions::screen_measure y)
Definition layout.cppm:320
std::shared_ptr< StyleArchive > sptr
std::shared_ptr< StyleSheet > sptr
static sptr parse(auto input)
std::list< std::shared_ptr< component_base_t > > children
virtual component_dimensional_relations_t get_dimensional_relations()=0
internal_relations_t & get_internal_relations()
std::optional< component_base_t * > parent
std::shared_ptr< component_base_t > sptr
std::shared_ptr< component_renderer_t > sptr
void queue_render(const component_base_t::sptr &component)
std::optional< std::shared_ptr< component_base_t > > component_instance
void mark_dirty()
Marks this component state as needing to be redrawn.
std::unordered_map< std::string, component_state_ref > children_states
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::shared_ptr< component_updater_t > sptr
virtual bool has_base_field_override(refl::field_path field_path)=0
EVENT(RedrawEvent)
#define COMPUTE(DIM)
Definition layout.cppm:159
std::shared_ptr< component_state_t > component_state_ref
const S & get_value(dimension< S > &dimension)
Definition api.cppm:24
quantify::quantity_t< screen::pixel, double > screen_measure
Definition _types.cppm:21
static bool compute_dimensions(cydui::components::component_base_t *rt)
Definition layout.cppm:170
Layout * create(C &&root_component)
Definition layout.cppm:140
void bind_layout(Layout *layout, const CWindow::sptr &window)
Definition layout.cppm:461
dimensions::dimension< dimensions::screen_measure > dimension_t
Definition @index.cppm:15
static void mount_component(component_base_t *component)
static std::shared_ptr< component_state_t > create_state_instance(component_base_t *component)