Unravel Engine C++ Reference
Loading...
Searching...
No Matches
project_settings_panel.cpp
Go to the documentation of this file.
2#include "../panel.h"
3#include "../panels_defs.h"
4
8
9#include <filedialog/filedialog.h>
10#include <imgui/imgui.h>
11#include <imgui/imgui_internal.h>
12
13namespace unravel
14{
15
16namespace
17{
18
19auto to_os_key(input::key_code code) -> os::key::code
20{
21 return static_cast<os::key::code>(code);
22}
23
24auto to_os_key(int32_t code) -> os::key::code
25{
26 return static_cast<os::key::code>(code);
27}
28
29auto from_os_key(os::key::code code) -> input::key_code
30{
31 return static_cast<input::key_code>(code);
32}
33
34auto from_os_key(int32_t code) -> input::key_code
35{
36 return static_cast<input::key_code>(code);
37}
38
39template<typename EnumT, typename ToStringFn>
40auto ImGuiEnumCombo(const char* label,
41 EnumT& current_value,
42 const EnumT* all_values,
43 size_t count,
44 ToStringFn stringify) -> bool
45{
46 // Convert current value to a display name
47 std::string preview_text = stringify(current_value);
48
49 bool changed = false;
50 if(ImGui::BeginCombo(label, preview_text.c_str()))
51 {
52 for(size_t i = 0; i < count; ++i)
53 {
54 EnumT candidate = all_values[i];
55 bool is_selected = (candidate == current_value);
56 if(ImGui::Selectable(stringify(candidate).c_str(), is_selected))
57 {
58 // If user selected a different value
59 if(candidate != current_value)
60 {
61 current_value = candidate;
62 changed = true;
63 }
64 }
65
66 // Set focus if this is the currently selected item
67 if(is_selected)
68 {
69 ImGui::SetItemDefaultFocus();
70 }
71 }
72 ImGui::EndCombo();
73 }
74
75 return changed;
76}
77
78template<typename EnumT, typename ToStringFn, typename FromIntFn, typename GetDescriptionFn>
79auto ImGuiEnumSelector(const char* label,
80 EnumT& selected_value,
81 int enum_count,
82 ToStringFn stringify,
83 FromIntFn from_int,
84 GetDescriptionFn get_description,
85 const char* popup_id = "Enum Selector") -> bool
86{
87 std::vector<std::string> names;
88 std::vector<const char*> names_cstr;
89
90 static ImGuiTextFilter filter;
91
92 ImGui::PushID(label);
93 // Lazy initialization
94 {
95 names.reserve(enum_count);
96 for(int i = 0; i < enum_count; ++i)
97 {
98 auto e = from_int(i);
99 names.push_back(stringify(e));
100 }
101
102 // Build the const char* array
103 names_cstr.reserve(names.size());
104 for(const auto& name : names)
105 {
106 names_cstr.push_back(name.c_str());
107 }
108 }
109
110 // The button text: what is the current selection's name?
111 std::string current_name = stringify(selected_value);
112 if(current_name.empty())
113 {
114 current_name = "None";
115 }
116
117 // If user clicks, open the popup
118 bool selection_changed = false;
119 if(ImGui::Button(current_name.c_str(), ImVec2(150.0f, ImGui::GetFrameHeight())))
120 {
121 // Clear the filter
122 filter.Clear();
123 ImGui::OpenPopup(popup_id);
124 }
125 std::string desc = get_description(selected_value);
126 if(!desc.empty())
127 {
128 ImGui::SetItemTooltipEx("%s", desc.c_str());
129 }
130
131 ImGui::SameLine();
132 ImGui::TextUnformatted(label);
133
134 // The actual popup with a filter input and a list of items
135 if(ImGui::BeginPopup(popup_id))
136 {
137 if(ImGui::IsWindowAppearing())
138 {
139 ImGui::SetKeyboardFocusHere();
140 }
141
142 // Draw filter input box
143 ImGui::DrawFilterWithHint(filter, ICON_MDI_SELECT_SEARCH " Search...", 150.0);
144 ImGui::DrawItemActivityOutline();
145
146 ImGui::Separator();
147
148 // We create a scrolling region for the items
149 if(ImGui::BeginChild("Enum Selector Context", ImVec2(0, 200.0f), true))
150 {
151 for(int i = 0; i < enum_count; ++i)
152 {
153 if(names[i].empty())
154 {
155 continue;
156 }
157
158 // If it doesn't pass the filter, skip
159 if(!filter.PassFilter(names[i].c_str()))
160 {
161 continue;
162 }
163
164 // Check if it's the currently selected
165 bool is_selected = (static_cast<int>(selected_value) == i);
166 if(ImGui::Selectable(names[i].c_str(), is_selected))
167 {
168 // The user picked something new
169 selected_value = from_int(i);
170 selection_changed = true;
171 ImGui::CloseCurrentPopup();
172 }
173
174 std::string desc = get_description(from_int(i));
175 if(!desc.empty())
176 {
177 ImGui::SetItemTooltipEx("%s", desc.c_str());
178 ImGui::SameLine();
179 ImGui::TextDisabled("%s", "(?)");
180 // ImGui::HelpMarker(desc.c_str());
181 }
182 }
183 ImGui::EndChild();
184 }
185
186 ImGui::EndPopup();
187 }
188
189 ImGui::PopID();
190
191 return selection_changed;
192}
193
194void draw_application_settings(rtti::context& ctx)
195{
196 auto& pm = ctx.get_cached<project_manager>();
197 auto& settings = pm.get_settings();
198
199 ImGui::PushItemWidth(150.0f);
200
201 if(inspect(ctx, settings.app).edit_finished)
202 {
203 pm.save_project_settings(ctx);
204 }
205
206 ImGui::PopItemWidth();
207}
208
209void draw_resolution_settings(rtti::context& ctx)
210{
211 auto& pm = ctx.get_cached<project_manager>();
212 auto& settings = pm.get_settings();
213
214 ImGui::PushItemWidth(150.0f);
215
216 if(inspect(ctx, settings.resolution).edit_finished)
217 {
218 pm.save_project_settings(ctx);
219 }
220
221 ImGui::PopItemWidth();
222}
223
224void draw_graphics_settings(rtti::context& ctx)
225{
226 auto& pm = ctx.get_cached<project_manager>();
227 auto& settings = pm.get_settings();
228
229 ImGui::PushItemWidth(150.0f);
230
231 if(inspect(ctx, settings.graphics).edit_finished)
232 {
233 pm.save_project_settings(ctx);
234 }
235
236 ImGui::PopItemWidth();
237}
238
239void draw_standalone_settings(rtti::context& ctx)
240{
241 auto& pm = ctx.get_cached<project_manager>();
242 auto& settings = pm.get_settings();
243
244 ImGui::PushItemWidth(150.0f);
245
246 if(inspect(ctx, settings.standalone).edit_finished)
247 {
248 pm.save_project_settings(ctx);
249 }
250
251 ImGui::PopItemWidth();
252}
253
254void draw_layers_settings(rtti::context& ctx)
255{
256 auto& pm = ctx.get_cached<project_manager>();
257 auto& settings = pm.get_settings();
258
259 ImGui::PushItemWidth(150.0f);
260
261 if(inspect(ctx, settings.layer).edit_finished)
262 {
263 pm.save_project_settings(ctx);
264 }
265
266 ImGui::PopItemWidth();
267}
268
269void draw_asset_settings(rtti::context& ctx)
270{
271 auto& pm = ctx.get_cached<project_manager>();
272 auto& settings = pm.get_settings();
273
274 ImGui::PushItemWidth(150.0f);
275
276 if(inspect(ctx, settings.assets.texture).edit_finished)
277 {
278 pm.save_project_settings(ctx);
279 }
280
281 if(ImGui::Button("Recompile Textures"))
282 {
284 }
285
286 ImGui::PopItemWidth();
287}
288
289void draw_input_settings(rtti::context& ctx)
290{
291 auto& pm = ctx.get_cached<project_manager>();
292 auto& settings = pm.get_settings();
293
294 int total_inputs = 0;
295
296 inspect_result result;
297 ImGui::PushItemWidth(150.0f);
298
299 if(ImGui::TreeNode("Keyboard"))
300 {
301 auto& entries = settings.input.actions.keyboard_map.entries_by_action_id_;
302
303 if(ImGui::Button("Add Action"))
304 {
305 auto& mapping = entries["New Action"];
306 mapping.emplace_back();
307
308 result.changed = true;
309 result.edit_finished = true;
310 }
311
312 std::string rename_from;
313 std::string rename_to;
314 std::string to_delete;
315
316 for(auto& kvp : entries)
317 {
318 ImGui::PushID(total_inputs++);
319 auto& action = kvp.first;
320 auto& mappings = kvp.second;
321
322 if(ImGui::Button(ICON_MDI_DELETE_ALERT))
323 {
324 to_delete = action;
325 }
326
327 ImGui::SameLine();
328
329 if(ImGui::TreeNode(action.c_str()))
330 {
331 int i = 0;
332
333 auto name = action;
334 if(ImGui::InputTextWidget("Name", name, false, ImGuiInputTextFlags_EnterReturnsTrue))
335 {
336 rename_from = action;
337 rename_to = name;
338 }
339
340 if(ImGui::Button("Add Mapping"))
341 {
342 mappings.emplace_back();
343
344 result.changed = true;
345 result.edit_finished = true;
346 }
347
348 int index_to_remove = -1;
349 for(auto& mapping : mappings)
350 {
351 if(&mapping != &mappings.front())
352 {
353 ImGui::Separator();
354 }
355
356 ImGui::PushID(int32_t(i));
357
358 ImGui::PushID(int32_t(mapping.key));
359
360 if(ImGui::Button(ICON_MDI_DELETE))
361 {
362 index_to_remove = i;
363 }
364 ImGui::SameLine();
365
366 ImGui::BeginGroup();
367 {
368 auto oskey = to_os_key(mapping.key);
369
370 if(ImGuiEnumSelector(
371 "Key",
372 oskey,
373 os::key::code::count,
374 [](os::key::code code)
375 {
376 return os::key::to_string(code);
377 },
378 [](int code)
379 {
380 return to_os_key(code);
381 },
382 [](os::key::code code)
383 {
384 return "";
385 },
386 "Key Selector"))
387 {
388 mapping.key = from_os_key(oskey);
389 result.changed = true;
390 result.edit_finished = true;
391 }
392
393 int mod_i = 0;
394 int mod_index_to_remove = -1;
395 for(auto& modifier : mapping.modifiers)
396 {
397 ImGui::PushID(mod_i);
398 auto osmodifier = to_os_key(modifier);
399 if(ImGui::Button(ICON_MDI_DELETE_VARIANT))
400 {
401 mod_index_to_remove = mod_i;
402 }
403 ImGui::SameLine();
404
405 if(ImGuiEnumSelector(
406 "Modifier",
407 osmodifier,
408 os::key::code::count,
409 [](os::key::code code)
410 {
411 return os::key::to_string(code);
412 },
413 [](int code)
414 {
415 return to_os_key(code);
416 },
417 [](os::key::code code)
418 {
419 return "";
420 },
421 "Modifier Selector"))
422 {
423 modifier = from_os_key(osmodifier);
424 result.changed = true;
425 result.edit_finished = true;
426 }
427 ImGui::PopID();
428
429 mod_i++;
430 }
431
432 if(mod_index_to_remove != -1)
433 {
434 if(mod_index_to_remove < mapping.modifiers.size())
435 {
436 mapping.modifiers.erase(mapping.modifiers.begin() + mod_index_to_remove);
437 }
438 }
439
440 ImGui::Dummy(ImVec2(150.0f, ImGui::GetFrameHeight()));
441 ImGui::SameLine();
442 if(ImGui::Button("Add Modifier"))
443 {
444 mapping.modifiers.emplace_back();
445 result.changed = true;
446 result.edit_finished = true;
447 }
448
449 if(ImGui::DragFloat("Analog Value", &mapping.analog_value, 0.05))
450 {
451 result.changed = true;
452 }
453 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
454 }
455 ImGui::EndGroup();
456
457 i++;
458 ImGui::PopID();
459 ImGui::PopID();
460 }
461
462 if(index_to_remove != -1)
463 {
464 if(index_to_remove < mappings.size())
465 {
466 mappings.erase(mappings.begin() + index_to_remove);
467 }
468 result.changed = true;
469 result.edit_finished = true;
470 }
471
472 ImGui::TreePop();
473 }
474
475 ImGui::PopID();
476 }
477
478 if(!rename_to.empty())
479 {
480 entries[rename_to] = entries[rename_from];
481 entries.erase(rename_from);
482 }
483
484 if(!to_delete.empty())
485 {
486 entries.erase(to_delete);
487 }
488
489 ImGui::TreePop();
490 }
491
492 if(ImGui::TreeNode("Gamepad"))
493 {
494 auto& entries = settings.input.actions.gamepad_map.entries_by_action_id_;
495
496 if(ImGui::Button("Add Action"))
497 {
498 auto& mapping = entries["New Action"];
499 mapping.emplace_back();
500
501 result.changed = true;
502 result.edit_finished = true;
503 }
504
505 std::string rename_from;
506 std::string rename_to;
507 std::string to_delete;
508
509 for(auto& kvp : settings.input.actions.gamepad_map.entries_by_action_id_)
510 {
511 auto& action = kvp.first;
512 auto& mappings = kvp.second;
513
514 ImGui::PushID(total_inputs++);
515
516 if(ImGui::Button(ICON_MDI_DELETE_ALERT))
517 {
518 to_delete = action;
519 }
520
521 ImGui::SameLine();
522
523 if(ImGui::TreeNode(action.c_str()))
524 {
525 int i = 0;
526
527 auto name = action;
528 if(ImGui::InputTextWidget("Name", name, false, ImGuiInputTextFlags_EnterReturnsTrue))
529 {
530 rename_from = action;
531 rename_to = name;
532 }
533
534 if(ImGui::Button("Add Mapping"))
535 {
536 mappings.emplace_back();
537
538 result.changed = true;
539 result.edit_finished = true;
540 }
541
542 int index_to_remove = -1;
543 for(auto& mapping : mappings)
544 {
545 if(&mapping != &mappings.front())
546 {
547 ImGui::Separator();
548 }
549
550 ImGui::PushID(int32_t(i));
551
552 ImGui::PushID(int32_t(mapping.type));
553
554 if(ImGui::Button(ICON_MDI_DELETE))
555 {
556 index_to_remove = i;
557 }
558
559 ImGui::SameLine();
560
561 ImGui::BeginGroup();
562 {
564 if(ImGuiEnumCombo("Type",
565 mapping.type,
566 types,
567 IM_ARRAYSIZE(types),
569 {
570 return input::to_string(type);
571 }))
572 {
573 result.changed = true;
574 result.edit_finished = true;
575 }
576
577 if(mapping.type == input::input_type::axis)
578 {
582 if(ImGuiEnumCombo("Range",
583 mapping.range,
584 ranges,
585 IM_ARRAYSIZE(ranges),
587 {
588 return input::to_string(range);
589 }))
590 {
591 result.changed = true;
592 result.edit_finished = true;
593 }
594
595 auto axis = static_cast<input::gamepad_axis>(mapping.value);
596
597 if(ImGuiEnumSelector(
598 "Axis",
599 axis,
601 [](input::gamepad_axis button)
602 {
603 return input::to_string(button);
604 },
605 [](int val)
606 {
607 return static_cast<input::gamepad_axis>(val);
608 },
610 {
611 return "";
612 },
613 "Gamepad Axis Selector"))
614 {
615 mapping.value = static_cast<uint32_t>(axis);
616
617 result.changed = true;
618 result.edit_finished = true;
619 }
620
621 ImGui::DragFloat("Min Analog Value", &mapping.min_analog_value, 0.05);
622 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
623
624 ImGui::DragFloat("Max Analog Value", &mapping.max_analog_value, 0.05);
625 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
626 }
627 else
628 {
629 auto button = static_cast<input::gamepad_button>(mapping.value);
630
631 if(ImGuiEnumSelector(
632 "Button",
633 button,
635 [](input::gamepad_button button)
636 {
637 return input::to_string(button);
638 },
639 [](int val)
640 {
641 return static_cast<input::gamepad_button>(val);
642 },
644 {
645 return input::get_description(button);
646 },
647 "Gamepad Button Selector"))
648 {
649 mapping.value = static_cast<uint32_t>(button);
650
651 result.changed = true;
652 result.edit_finished = true;
653 }
654 }
655 }
656 ImGui::EndGroup();
657
658 i++;
659 ImGui::PopID();
660 ImGui::PopID();
661 }
662
663 if(index_to_remove != -1)
664 {
665 if(index_to_remove < mappings.size())
666 {
667 mappings.erase(mappings.begin() + index_to_remove);
668 }
669 result.changed = true;
670 result.edit_finished = true;
671 }
672
673 ImGui::TreePop();
674 }
675 ImGui::PopID();
676 }
677
678 if(!rename_to.empty())
679 {
680 entries[rename_to] = entries[rename_from];
681 entries.erase(rename_from);
682 }
683
684 if(!to_delete.empty())
685 {
686 entries.erase(to_delete);
687 }
688
689 ImGui::TreePop();
690 }
691
692 if(ImGui::TreeNode("Mouse"))
693 {
694 auto& entries = settings.input.actions.mouse_map.entries_by_action_id_;
695
696 if(ImGui::Button("Add Action"))
697 {
698 auto& mapping = entries["New Action"];
699 mapping.emplace_back();
700
701 result.changed = true;
702 result.edit_finished = true;
703 }
704
705 std::string rename_from;
706 std::string rename_to;
707 std::string to_delete;
708
709 for(auto& kvp : entries)
710 {
711 const auto& action = kvp.first;
712 auto& mappings = kvp.second;
713
714 ImGui::PushID(total_inputs++);
715
716 if(ImGui::Button(ICON_MDI_DELETE_ALERT))
717 {
718 to_delete = action;
719 }
720
721 ImGui::SameLine();
722
723 if(ImGui::TreeNode(action.c_str()))
724 {
725 int i = 0;
726
727 auto name = action;
728 if(ImGui::InputTextWidget("Name", name, false, ImGuiInputTextFlags_EnterReturnsTrue))
729 {
730 rename_from = action;
731 rename_to = name;
732 }
733
734 if(ImGui::Button("Add Mapping"))
735 {
736 mappings.emplace_back();
737
738 result.changed = true;
739 result.edit_finished = true;
740 }
741
742 int index_to_remove = -1;
743 for(auto& mapping : mappings)
744 {
745 if(&mapping != &mappings.front())
746 {
747 ImGui::Separator();
748 }
749
750 ImGui::PushID(i);
751
752 if(ImGui::Button(ICON_MDI_DELETE))
753 {
754 index_to_remove = i;
755 }
756
757 ImGui::SameLine();
758
759 ImGui::BeginGroup();
760 {
762 if(ImGuiEnumCombo("Type",
763 mapping.type,
764 types,
765 IM_ARRAYSIZE(types),
767 {
768 return input::to_string(type);
769 }))
770 {
771 result.changed = true;
772 result.edit_finished = true;
773 }
774
775 if(mapping.type == input::input_type::axis)
776 {
780 if(ImGuiEnumCombo("Range",
781 mapping.range,
782 ranges,
783 IM_ARRAYSIZE(ranges),
785 {
786 return input::to_string(range);
787 }))
788 {
789 result.changed = true;
790 result.edit_finished = true;
791 }
792
793 auto axis = static_cast<input::mouse_axis>(mapping.value);
797 if(ImGuiEnumCombo("Axis",
798 axis,
799 axes,
800 IM_ARRAYSIZE(axes),
801 [](input::mouse_axis axis)
802 {
803 return input::to_string(axis);
804 }))
805 {
806 mapping.value = static_cast<uint32_t>(axis);
807
808 result.changed = true;
809 result.edit_finished = true;
810 }
811 }
812 else
813 {
814 auto button = static_cast<input::mouse_button>(mapping.value);
815
816 if(ImGuiEnumSelector(
817 "Button",
818 button,
820 [](input::mouse_button button)
821 {
822 return input::to_string(button);
823 },
824 [](int val)
825 {
826 return static_cast<input::mouse_button>(val);
827 },
829 {
830 return "";
831 },
832 "Button Selector"))
833 {
834 mapping.value = static_cast<uint32_t>(button);
835
836 result.changed = true;
837 result.edit_finished = true;
838 }
839 }
840 }
841 ImGui::EndGroup();
842
843 i++;
844
845 ImGui::PopID();
846 }
847 ImGui::TreePop();
848
849 if(index_to_remove != -1)
850 {
851 if(index_to_remove < mappings.size())
852 {
853 mappings.erase(mappings.begin() + index_to_remove);
854 }
855 result.changed = true;
856 result.edit_finished = true;
857 }
858 }
859
860 ImGui::PopID();
861 }
862
863 if(!rename_to.empty())
864 {
865 entries[rename_to] = entries[rename_from];
866 entries.erase(rename_from);
867 }
868
869 if(!to_delete.empty())
870 {
871 entries.erase(to_delete);
872 }
873
874 ImGui::TreePop();
875 }
876
877 if(result.edit_finished)
878 {
879 pm.save_project_settings(ctx);
880 }
881}
882
883void draw_time_settings(rtti::context& ctx)
884{
885 auto& pm = ctx.get_cached<project_manager>();
886 auto& settings = pm.get_settings();
887
888 ImGui::PushItemWidth(150.0f);
889
890 if(inspect(ctx, settings.time).edit_finished)
891 {
892 pm.save_project_settings(ctx);
893 }
894
895 ImGui::PopItemWidth();
896}
897
898} // namespace
899
903
904void project_settings_panel::show(bool s, const std::string& hint)
905{
906 show_request_ = s;
907 hint_ = hint;
908}
909
911{
912 if(show_request_)
913 {
914 ImGui::OpenPopup(name);
915 show_request_ = false;
916 }
917
918 ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size * 0.5f);
919 bool show = true;
920 if(ImGui::BeginPopupModal(name, &show))
921 {
922 // ImGui::WindowTimeBlock block(ImGui::GetFont(ImGui::Font::Mono));
923
924 draw_ui(ctx);
925
926 ImGui::EndPopup();
927 }
928}
929
930void project_settings_panel::draw_ui(rtti::context& ctx)
931{
932 // We'll create two child windows side by side:
933 // 1) Left child: categories
934 // 2) Right child: actual settings
935
936 auto avail = ImGui::GetContentRegionAvail();
937 if(avail.x < 1.0f || avail.y < 1.0f)
938 {
939 return;
940 }
941
942 static std::vector<setting_entry> categories{{"Application", &draw_application_settings},
943 {"Resolution", &draw_resolution_settings},
944 {"Assets", &draw_asset_settings},
945 {"Graphics", &draw_graphics_settings},
946 {"Standalone", &draw_standalone_settings},
947 {"Layers", &draw_layers_settings},
948 {"Input", &draw_input_settings},
949 {"Time", &draw_time_settings}};
950 // Child A: the categories list
951 // We fix the width of this child, so the right child uses the remaining space.
952 ImGui::BeginChild("##LeftSidebar", avail * ImVec2(0.15f, 1.0f), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX);
953 {
954 // Display categories
955 for(const auto& category : categories)
956 {
957 if(hint_ == category.id)
958 {
959 selected_entry_ = category;
960 hint_.clear();
961 }
962
963 // 'Selectable' returns true if clicked
964 if(ImGui::Selectable(category.id.c_str(), (selected_entry_.id == category.id)))
965 {
966 selected_entry_ = category;
967 }
968 }
969 }
970 ImGui::EndChild();
971
972 // On the same line:
973 ImGui::SameLine();
974
975 // Child B: show settings for the selected category
976 ImGui::BeginChild("##RightContent");
977 {
978 if(selected_entry_.callback)
979 {
980 selected_entry_.callback(ctx);
981 }
982 }
983 ImGui::EndChild();
984}
985
986} // namespace unravel
manifold_type type
void on_frame_ui_render(rtti::context &ctx, const char *name)
void show(bool s, const std::string &hint)
std::string name
Definition hub.cpp:27
#define ICON_MDI_DELETE_VARIANT
#define ICON_MDI_SELECT_SEARCH
#define ICON_MDI_DELETE
#define ICON_MDI_DELETE_ALERT
auto get_description(gamepad_button button) -> std::string
key_code
Definition key.hpp:6
auto to_string(axis_range range) -> const std::string &
Definition to_string.hpp:15
auto inspect(rtti::context &ctx, T &obj) -> inspect_result
Convenience template function for inspecting objects of known type.
Definition inspectors.h:393
auto get_cached() -> T &
Definition context.hpp:49