yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
layout_designer_panel.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <string>
6#include <utility>
7#include <vector>
8
9#include "absl/status/status.h"
10#include "absl/status/statusor.h"
11#include "absl/strings/str_cat.h"
24#include "imgui/imgui.h"
25#include "imgui/imgui_internal.h"
26#include "imgui/misc/cpp/imgui_stdlib.h"
27#include "nlohmann/json.hpp"
28
29namespace yaze {
30namespace editor {
31namespace layout_designer {
32namespace {
33
34constexpr float kPaletteInitialWidth = 240.0f;
35constexpr float kPropertiesInitialWidth = 280.0f;
36constexpr const char* kOpenPopupId = "LayoutDesigner_OpenPopup";
37constexpr const char* kSaveAsPopupId = "LayoutDesigner_SaveAsPopup";
38constexpr const char* kUntitledName = "Untitled";
39
40// True when the tree has no name we should persist under — empty or the
41// seed "Untitled" both need to route through Save As.
42bool TreeNeedsName(const DockTree& tree) {
43 return tree.name.empty() || tree.name == kUntitledName;
44}
45
49
51 switch (d) {
53 return "Left";
55 return "Right";
57 return "Up";
59 return "Down";
60 }
61 return "Left";
62}
63
64} // namespace
65
67
71
73 tree_ = std::move(new_tree);
75 drag_ = ActiveDrag{};
76 undo_.Clear();
77}
78
81 if (settings == nullptr) {
82 status_message_ = "Save failed: UserSettings not available.";
83 status_is_error_ = true;
84 return;
85 }
86 if (tree_.name.empty()) {
87 status_message_ = "Save failed: tree has no name (use Save As…).";
88 status_is_error_ = true;
89 return;
90 }
91 // DockTreeToJson is infallible — any tree serializes. Validation is a
92 // separate concern; a partially-built tree is still persistable.
93 const nlohmann::json body = DockTreeToJson(tree_);
94 settings->prefs().named_layouts[tree_.name] = body.dump();
95 const absl::Status save_status = settings->Save();
96 if (save_status.ok()) {
97 status_message_ = absl::StrCat("Saved \"", tree_.name, "\".");
98 status_is_error_ = false;
99 } else {
100 status_message_ = absl::StrCat("Saved to memory, but disk write failed: ",
101 save_status.message());
102 status_is_error_ = true;
103 }
104}
105
106void LayoutDesignerPanel::LoadNamedLayoutIntoTree(const std::string& name) {
108 if (settings == nullptr) {
109 status_message_ = "Open failed: UserSettings not available.";
110 status_is_error_ = true;
111 return;
112 }
113 const auto it = settings->prefs().named_layouts.find(name);
114 if (it == settings->prefs().named_layouts.end()) {
116 absl::StrCat("Open failed: no layout named \"", name, "\".");
117 status_is_error_ = true;
118 return;
119 }
120 nlohmann::json parsed;
121 try {
122 parsed = nlohmann::json::parse(it->second);
123 } catch (const nlohmann::json::parse_error& e) {
125 absl::StrCat("Open failed: invalid JSON (", e.what(), ").");
126 status_is_error_ = true;
127 return;
128 }
129 auto tree_or = DockTreeFromJson(parsed);
130 if (!tree_or.ok()) {
131 status_message_ = absl::StrCat("Open failed: ", tree_or.status().message());
132 status_is_error_ = true;
133 return;
134 }
135 // DockTreeFromJson does not Validate (per its documented contract).
136 // Apply the gate here so a malformed-on-disk layout (duplicate ids,
137 // out-of-range ratios, etc.) doesn't silently install — Phase 8.5
138 // selection-by-id depends on the structural invariants.
139 std::string validation_error;
140 if (!tree_or->Validate(&validation_error)) {
142 absl::StrCat("Open failed: invalid layout (", validation_error, ").");
143 status_is_error_ = true;
144 return;
145 }
146 // DockTreeFromJson preserves the name from the JSON body; fall back to
147 // the lookup key if the body lacks one (legacy migrations).
148 if (tree_or->name.empty()) {
149 tree_or->name = name;
150 }
151 ReplaceTree(*std::move(tree_or));
152 status_message_ = absl::StrCat("Loaded \"", name, "\".");
153 status_is_error_ = false;
154}
155
159 if (manager == nullptr) {
160 status_message_ = "Apply failed: LayoutManager not available.";
161 status_is_error_ = true;
162 return;
163 }
164 // ImGui IDs are scoped by the current window's ID stack. Calling
165 // `ImGui::GetID("MainDockSpace")` from inside this panel's window
166 // would hash to a different ID than the one the controller binds to
167 // the live dockspace. Read the value cached by the controller
168 // instead — it sets it every frame from inside DockSpaceWindow.
169 const ImGuiID dockspace_id = manager->GetMainDockspaceId();
170 if (dockspace_id == 0) {
172 "Apply failed: main dockspace not yet created (try again next frame).";
173 status_is_error_ = true;
174 return;
175 }
176 const absl::Status apply_status = manager->ApplyDockTree(tree_, dockspace_id);
177 if (!apply_status.ok()) {
178 status_message_ = absl::StrCat("Apply failed: ", apply_status.message());
179 status_is_error_ = true;
180 return;
181 }
182 // Persist the last-applied name so EditorManager can reapply on the
183 // next startup (hook lands in a follow-up commit; the field itself
184 // already exists in UserSettings).
185 if (settings != nullptr && !tree_.name.empty()) {
187 (void)settings->Save(); // best-effort; apply succeeded regardless.
188 }
189 status_message_ = absl::StrCat(
190 "Applied \"", tree_.name.empty() ? "(unnamed)" : tree_.name.c_str(),
191 "\" to main dockspace.");
192 status_is_error_ = false;
193}
194
196 // Treat the seed name "Untitled" as unnamed so the Save button and
197 // Ctrl/Cmd+S agree — otherwise clicking Save on a fresh tree would
198 // write a literal "Untitled" entry while the shortcut correctly
199 // routes to Save As.
200 if (TreeNeedsName(tree_)) {
201 save_as_buffer_ = tree_.name.empty() ? kUntitledName : tree_.name;
203 return;
204 }
206}
207
211 const bool has_named_layouts =
212 settings != nullptr && !settings->prefs().named_layouts.empty();
213 // Save is always enabled when a settings backend is bound; the routing
214 // to Save As for unnamed trees happens in SaveOrSaveAs().
215 const bool can_save = settings != nullptr;
216 const bool can_apply = manager != nullptr;
217
218 ImGui::TextUnformatted(tr("File:"));
219 ImGui::SameLine();
220 if (ImGui::SmallButton(tr("New"))) {
221 ReplaceTree(MakeEmptyTree(kUntitledName));
222 status_message_ = "New layout.";
223 status_is_error_ = false;
224 }
225 ImGui::SameLine();
226 ImGui::BeginDisabled(!has_named_layouts);
227 if (ImGui::SmallButton(tr("Open..."))) {
229 }
230 ImGui::EndDisabled();
231 ImGui::SameLine();
232 ImGui::BeginDisabled(!can_save);
233 if (ImGui::SmallButton(tr("Save"))) {
234 SaveOrSaveAs();
235 }
236 ImGui::EndDisabled();
237 ImGui::SameLine();
238 ImGui::BeginDisabled(settings == nullptr);
239 if (ImGui::SmallButton(tr("Save As..."))) {
240 save_as_buffer_ = tree_.name.empty() ? kUntitledName : tree_.name;
242 }
243 ImGui::EndDisabled();
244 ImGui::SameLine();
245 ImGui::BeginDisabled(!can_apply);
246 if (ImGui::SmallButton(tr("Apply"))) {
248 }
249 ImGui::EndDisabled();
250 ImGui::SameLine();
251 ImGui::TextDisabled("|");
252 ImGui::SameLine();
253 ImGui::BeginDisabled(!undo_.CanUndo());
254 if (ImGui::SmallButton(tr("Undo"))) {
255 // Phase 8.5: selection is by stable id, so undo no longer needs to
256 // force-clear it. If the post-undo tree still contains the
257 // selected id, FindNode finds it and the cursor stays put;
258 // otherwise FindNode returns nullptr and Draw quietly renders no
259 // outline.
260 undo_.Undo(&tree_);
261 drag_ = ActiveDrag{};
262 }
263 ImGui::EndDisabled();
264 ImGui::SameLine();
265 ImGui::BeginDisabled(!undo_.CanRedo());
266 if (ImGui::SmallButton(tr("Redo"))) {
267 undo_.Redo(&tree_);
268 drag_ = ActiveDrag{};
269 }
270 ImGui::EndDisabled();
271}
272
274 if (!ImGui::BeginPopupModal(kOpenPopupId, nullptr,
275 ImGuiWindowFlags_AlwaysAutoResize)) {
276 return;
277 }
279 if (settings == nullptr) {
280 ImGui::TextUnformatted(tr("UserSettings unavailable."));
281 if (ImGui::Button(tr("Close")))
282 ImGui::CloseCurrentPopup();
283 ImGui::EndPopup();
284 return;
285 }
286 // Gather + sort so the list is stable across frames (named_layouts is
287 // an unordered_map).
288 std::vector<std::string> names;
289 names.reserve(settings->prefs().named_layouts.size());
290 for (const auto& entry : settings->prefs().named_layouts) {
291 names.push_back(entry.first);
292 }
293 std::sort(names.begin(), names.end());
294
295 ImGui::TextUnformatted(tr("Choose a saved layout:"));
296 ImGui::BeginChild("layout_designer_open_list", ImVec2(320.0f, 200.0f),
297 ImGuiChildFlags_Borders);
298 for (const auto& name : names) {
299 const bool is_selected = (name == open_selection_);
300 if (ImGui::Selectable(name.c_str(), is_selected,
301 ImGuiSelectableFlags_AllowDoubleClick)) {
302 open_selection_ = name;
303 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
305 ImGui::CloseCurrentPopup();
306 }
307 }
308 }
309 ImGui::EndChild();
310
311 const bool can_open = !open_selection_.empty() &&
312 settings->prefs().named_layouts.count(open_selection_);
313 ImGui::BeginDisabled(!can_open);
314 if (ImGui::Button(tr("Open"))) {
316 ImGui::CloseCurrentPopup();
317 }
318 ImGui::EndDisabled();
319 ImGui::SameLine();
320 if (ImGui::Button(tr("Cancel")))
321 ImGui::CloseCurrentPopup();
322 ImGui::EndPopup();
323}
324
326 if (!ImGui::BeginPopupModal(kSaveAsPopupId, nullptr,
327 ImGuiWindowFlags_AlwaysAutoResize)) {
328 return;
329 }
330 ImGui::TextUnformatted(tr("Save layout as:"));
331 ImGui::SetNextItemWidth(320.0f);
332 const bool submitted_via_enter =
333 ImGui::InputText("##layout_designer_save_as", &save_as_buffer_,
334 ImGuiInputTextFlags_EnterReturnsTrue);
335 const bool name_empty =
336 save_as_buffer_.find_first_not_of(" \t") == std::string::npos;
337 ImGui::BeginDisabled(name_empty);
338 const bool save_clicked = ImGui::Button(tr("Save"));
339 ImGui::EndDisabled();
340 ImGui::SameLine();
341 if (ImGui::Button(tr("Cancel"))) {
342 ImGui::CloseCurrentPopup();
343 }
344 if ((save_clicked || submitted_via_enter) && !name_empty) {
347 ImGui::CloseCurrentPopup();
348 }
349 ImGui::EndPopup();
350}
351
352void LayoutDesignerPanel::Draw(bool* p_open) {
353 (void)p_open; // Closing the window is handled by the workspace shell.
354
355 // Keyboard shortcuts: Ctrl/Cmd+Z (undo), Ctrl/Cmd+Shift+Z (redo),
356 // Ctrl/Cmd+S (save — or Save As if the tree has no name yet).
357 // Gated on focus so typing in palette/property text fields doesn't
358 // trip them.
359 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
360 const ImGuiIO& io = ImGui::GetIO();
361 const bool chord = io.KeyCtrl || io.KeySuper;
362 if (chord && ImGui::IsKeyPressed(ImGuiKey_Z, /*repeat=*/false)) {
363 // Selection is by stable id (Phase 8.5) — let it persist across
364 // undo/redo when the id still exists in the swapped-in tree.
365 if (io.KeyShift) {
366 undo_.Redo(&tree_);
367 } else {
368 undo_.Undo(&tree_);
369 }
370 drag_ = ActiveDrag{};
371 }
372 if (chord && ImGui::IsKeyPressed(ImGuiKey_S, /*repeat=*/false)) {
373 SaveOrSaveAs();
374 }
375 }
376
377 // WindowContent::Draw contract: no ImGui::Begin/End and no BeginMenuBar
378 // (the outer PanelWindow does not opt into ImGuiWindowFlags_MenuBar). A
379 // button row stands in for the File menu.
380 DrawFileRow();
381 ImGui::Separator();
382
383 constexpr ImGuiTableFlags kBodyFlags =
384 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV;
385
386 if (ImGui::BeginTable("layout_designer_body", 3, kBodyFlags)) {
387 ImGui::TableSetupColumn("Palette", ImGuiTableColumnFlags_WidthFixed,
388 kPaletteInitialWidth);
389 ImGui::TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch);
390 ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed,
391 kPropertiesInitialWidth);
392 ImGui::TableNextRow();
393
394 ImGui::TableSetColumnIndex(0);
395 if (ImGui::BeginChild("layout_designer_palette", ImVec2(0, 0),
396 ImGuiChildFlags_None)) {
397 // Exclude self so the designer can't be dropped inside its own
398 // canvas.
399 const auto entries = CollectPaletteEntries(GetId());
401 }
402 ImGui::EndChild();
403
404 ImGui::TableSetColumnIndex(1);
405 if (ImGui::BeginChild("layout_designer_canvas", ImVec2(0, 0),
406 ImGuiChildFlags_None)) {
407 const ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
408 const ImVec2 canvas_size = ImGui::GetContentRegionAvail();
409 if (canvas_size.x >= kMinCellSize && canvas_size.y >= kMinCellSize) {
410 const ImRect viewport(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x,
411 canvas_pos.y + canvas_size.y));
412 const DockTreeLayout layout = ComputeLayout(tree_, viewport);
413 // Resolve selection id → ptr for this frame's render. FindNode
414 // returns nullptr when the id no longer exists (e.g., the
415 // selected leaf was just consumed by a SplitInPlace mutation),
416 // and the renderer treats that as "no outline".
417 const DockNode* selected_node = tree_.FindNode(selected_id_);
418 RenderDockTree(tree_, layout, selected_node,
419 ImGui::GetWindowDrawList());
420 ImGui::Dummy(canvas_size);
421
422 const ImVec2 mouse = ImGui::GetIO().MousePos;
423 const bool hovered = ImGui::IsItemHovered();
424
425 if (ImGui::BeginDragDropTarget()) {
426 const DockNode* leaf_const = HitTestNode(layout, mouse);
427 if (leaf_const != nullptr &&
428 leaf_const->type == DockNode::Type::kLeaf) {
429 const ImRect& leaf_rect = layout.node_rects.at(leaf_const);
430 const DropSuggestion suggestion = SuggestDrop(leaf_rect, mouse);
431 const ImRect preview =
432 ComputeDropPreviewRect(leaf_rect, suggestion);
433 if (preview.GetWidth() > 0.0f && preview.GetHeight() > 0.0f) {
434 ImGui::GetWindowDrawList()->AddRectFilled(
435 preview.Min, preview.Max,
436 ImGui::ColorConvertFloat4ToU32(
437 ImVec4(0.25f, 0.55f, 0.95f, 0.25f)));
438 }
439 gui::PanelDragPayload payload;
440 if (gui::AcceptPanelDropWithinTarget(&payload)) {
441 PanelEntry new_panel;
442 new_panel.panel_id = payload.panel_id;
443 if (const WindowContent* registered =
445 new_panel.display_name = registered->GetDisplayName();
446 new_panel.icon = registered->GetIcon();
447 }
448 DockNode* leaf_mut = const_cast<DockNode*>(leaf_const);
449 // Snapshot before mutation so this drop is reversible.
451 if (DockNode* selected_leaf = ApplyDropSuggestion(
452 &tree_, leaf_mut, suggestion, std::move(new_panel))) {
453 selected_id_ = selected_leaf->id;
454 } else {
455 // Applier refused (e.g. duplicate panel_id). Discard the
456 // speculative push without polluting redo.
458 }
459 }
460 }
461 ImGui::EndDragDropTarget();
462 }
463
465 // Mid-drag: resolve the split id back to a mutable pointer
466 // each frame. If the id is gone (tree was structurally
467 // edited under us), bail.
468 DockNode* drag_split_node = tree_.FindNode(drag_.split_id);
469 if (drag_split_node == nullptr) {
470 drag_ = ActiveDrag{};
471 } else if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
472 const bool horizontal =
473 IsSplitHorizontal(drag_split_node->split_direction);
474 const float axis_delta = horizontal
475 ? (mouse.x - drag_.start_mouse.x)
476 : (mouse.y - drag_.start_mouse.y);
477 const float axis_size = horizontal ? drag_.start_rect.GetWidth()
478 : drag_.start_rect.GetHeight();
479 drag_split_node->split_ratio = ComputeDraggedSplitRatio(
480 drag_.start_ratio, axis_delta, axis_size);
481 ImGui::SetMouseCursor(horizontal ? ImGuiMouseCursor_ResizeEW
482 : ImGuiMouseCursor_ResizeNS);
483 } else {
484 drag_ = ActiveDrag{};
485 }
486 } else if (hovered) {
487 const SplitBoundaryHit boundary =
488 HitTestSplitBoundary(tree_, layout, mouse);
489 if (boundary.split_node != nullptr) {
490 ImGui::SetMouseCursor(boundary.horizontal
491 ? ImGuiMouseCursor_ResizeEW
492 : ImGuiMouseCursor_ResizeNS);
493 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
494 // Snapshot before the first ratio edit so the whole drag
495 // collapses into a single undo step.
497 drag_.split_id = boundary.split_node->id;
499 drag_.start_mouse = mouse;
500 drag_.start_rect = layout.node_rects.at(boundary.split_node);
501 // Also surface the split as the current selection so the
502 // accent outline tracks the resize target.
504 }
505 } else if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
506 const DockNode* hit = HitTestNode(layout, mouse);
507 selected_id_ = (hit != nullptr) ? hit->id : kInvalidDockNodeId;
508 }
509 }
510 }
511 }
512 ImGui::EndChild();
513
514 ImGui::TableSetColumnIndex(2);
515 if (ImGui::BeginChild("layout_designer_properties", ImVec2(0, 0),
516 ImGuiChildFlags_None)) {
518 }
519 ImGui::EndChild();
520
521 ImGui::EndTable();
522 }
523
524 // Popups are opened from the File row handlers, then drawn once each
525 // frame at panel scope so ImGui can size them against the panel's
526 // frame.
528 ImGui::OpenPopup(kOpenPopupId);
529 open_popup_requested_ = false;
530 }
532 ImGui::OpenPopup(kSaveAsPopupId);
534 }
537
538 ImGui::Separator();
539 ImGui::Text(tr("Editing: %s | Undo: %zu / Redo: %zu"),
540 tree_.name.empty() ? "(unnamed)" : tree_.name.c_str(),
542 if (!status_message_.empty()) {
543 ImGui::SameLine();
544 ImGui::TextDisabled("|");
545 ImGui::SameLine();
546 if (status_is_error_) {
547 ImGui::TextColored(ImVec4(0.95f, 0.45f, 0.35f, 1.0f), "%s",
548 status_message_.c_str());
549 } else {
550 ImGui::TextUnformatted(status_message_.c_str());
551 }
552 }
553}
554
556 // Snapshot on the first frame of any interactive ImGui item
557 // (slider, input-text, combo). Collapses a continuous drag or a
558 // keystroke burst into a single undo step.
559 auto snapshot_on_activation = [this]() {
560 if (ImGui::IsItemActivated())
562 };
563
564 // Resolve the selected id to a mutable pointer. FindNode returns
565 // nullptr when the id is invalid, was previously selected on a tree
566 // we've since replaced, or was consumed by a structural mutation.
568 if (node == nullptr) {
569 ImGui::SeparatorText(tr("Layout"));
570 // Bind the InputText buffer directly to the tree fields; imgui_stdlib
571 // keeps the std::string in sync per keystroke, and IsItemActivated
572 // fires once per focus-in so the undo step covers the whole edit.
573 ImGui::InputText(tr("Name"), &tree_.name);
574 snapshot_on_activation();
575 ImGui::InputTextMultiline(tr("Description"), &tree_.description,
576 ImVec2(0.0f, 80.0f));
577 snapshot_on_activation();
578 ImGui::TextDisabled(tr("Select a cell in the canvas to edit it."));
579 return;
580 }
581
582 if (node->type == DockNode::Type::kSplit) {
583 ImGui::SeparatorText(tr("Split"));
584
585 const SplitDirection current_dir = node->split_direction;
586 if (ImGui::BeginCombo(tr("Direction"), SplitDirectionLabel(current_dir))) {
587 for (int i = 0; i < 4; ++i) {
588 const auto candidate = static_cast<SplitDirection>(i);
589 const bool is_selected = candidate == current_dir;
590 if (ImGui::Selectable(SplitDirectionLabel(candidate), is_selected)) {
591 if (candidate != current_dir) {
593 node->split_direction = candidate;
594 }
595 }
596 if (is_selected)
597 ImGui::SetItemDefaultFocus();
598 }
599 ImGui::EndCombo();
600 }
601
602 ImGui::SliderFloat(tr("Ratio"), &node->split_ratio, kMinSplitRatio,
603 kMaxSplitRatio, "%.2f");
604 snapshot_on_activation();
605 return;
606 }
607
608 // Leaf properties.
609 ImGui::SeparatorText(tr("Leaf"));
610 if (node->panels.empty()) {
611 ImGui::TextDisabled(tr("(no panels — drop from the palette)"));
612 return;
613 }
614
615 // Active-tab selector. Snapshot once on drag start; live-commit the
616 // value while the slider is active.
617 const int panel_count = static_cast<int>(node->panels.size());
618 int active = std::clamp(node->active_tab_index, 0, panel_count - 1);
619 ImGui::SliderInt(tr("Active tab"), &active, 0, panel_count - 1);
620 snapshot_on_activation();
621 if (active != node->active_tab_index) {
622 node->active_tab_index = active;
623 }
624
625 ImGui::Separator();
626 // Panel list with reorder + remove actions.
627 for (int i = 0; i < panel_count; ++i) {
628 ImGui::PushID(i);
629 const auto& entry = node->panels[i];
630 const std::string label = entry.icon.empty()
631 ? entry.display_name
632 : entry.icon + " " + entry.display_name;
633 ImGui::TextUnformatted(label.c_str());
634 ImGui::SameLine();
635
636 ImGui::BeginDisabled(i == 0);
637 if (ImGui::SmallButton(tr("Up"))) {
639 std::swap(node->panels[i], node->panels[i - 1]);
640 if (node->active_tab_index == i)
641 --node->active_tab_index;
642 else if (node->active_tab_index == i - 1)
643 ++node->active_tab_index;
644 }
645 ImGui::EndDisabled();
646
647 ImGui::SameLine();
648 ImGui::BeginDisabled(i + 1 >= panel_count);
649 if (ImGui::SmallButton(tr("Down"))) {
651 std::swap(node->panels[i], node->panels[i + 1]);
652 if (node->active_tab_index == i)
653 ++node->active_tab_index;
654 else if (node->active_tab_index == i + 1)
655 --node->active_tab_index;
656 }
657 ImGui::EndDisabled();
658
659 ImGui::SameLine();
660 if (ImGui::SmallButton(tr("Remove"))) {
662 // If the removed entry sits before the active tab, the active
663 // panel's index shifts down by one. std::clamp afterward handles
664 // the "removed the active tab" and "removed the last tab" cases.
665 if (i < node->active_tab_index)
666 --node->active_tab_index;
667 node->panels.erase(node->panels.begin() + i);
668 if (!node->panels.empty()) {
669 node->active_tab_index =
670 std::clamp(node->active_tab_index, 0,
671 static_cast<int>(node->panels.size()) - 1);
672 } else {
673 node->active_tab_index = 0;
674 }
675 ImGui::PopID();
676 break;
677 }
678
679 ImGui::PopID();
680 }
681}
682
684
685} // namespace layout_designer
686} // namespace editor
687} // namespace yaze
Manages ImGui DockBuilder layouts for each editor type.
absl::Status ApplyDockTree(const layout_designer::DockTree &tree, ImGuiID dockspace_id)
Apply a DockTree to the given dockspace.
ImGuiID GetMainDockspaceId() const
Get the cached main dockspace ID.
Manages user preferences and settings persistence.
Base interface for all logical window content components.
void Draw(bool *p_open) override
Draw the panel content.
std::string GetId() const override
Unique identifier for this panel.
UserSettings * user_settings()
Get the current UserSettings instance.
LayoutManager * layout_manager()
Get the shared LayoutManager instance.
WindowContent * Get(const std::string &id)
Get a specific panel by its ID.
DropSuggestion SuggestDrop(const ImRect &leaf_rect, ImVec2 mouse)
const DockNode * HitTestNode(const DockTreeLayout &layout, ImVec2 mouse)
ImRect ComputeDropPreviewRect(const ImRect &leaf_rect, const DropSuggestion &suggestion)
void RenderDockTree(const DockTree &tree, const DockTreeLayout &layout, const DockNode *selected, ImDrawList *dl)
void DrawPanelPalette(const std::vector< PanelPaletteEntry > &entries, std::string *query)
absl::StatusOr< DockTree > DockTreeFromJson(const nlohmann::json &j)
DockTree MakeEmptyTree(std::string name)
Definition dock_tree.cc:262
nlohmann::json DockTreeToJson(const DockTree &tree)
DockTreeLayout ComputeLayout(const DockTree &tree, const ImRect &viewport)
SplitBoundaryHit HitTestSplitBoundary(const DockTree &tree, const DockTreeLayout &layout, ImVec2 mouse, float tolerance)
DockNode * ApplyDropSuggestion(DockTree *tree, DockNode *leaf, const DropSuggestion &suggestion, PanelEntry panel)
float ComputeDraggedSplitRatio(float start_ratio, float axis_delta_px, float axis_size_px)
constexpr DockNodeId kInvalidDockNodeId
Definition dock_tree.h:22
std::vector< PanelPaletteEntry > CollectPaletteEntries(const std::string &exclude_panel_id)
bool AcceptPanelDropWithinTarget(PanelDragPayload *out)
Definition drag_drop.h:188
#define REGISTER_PANEL(PanelClass)
Auto-registration macro for panels with default constructors.
std::unordered_map< std::string, std::string > named_layouts
Represents a dock node in the layout tree.
Definition dock_tree.h:61
std::vector< PanelEntry > panels
Definition dock_tree.h:72
absl::flat_hash_map< const DockNode *, ImRect > node_rects
const DockNode * FindNode(DockNodeId id) const
Definition dock_tree.cc:251