72 std::function<
bool()> has_rom) {
74 const ImGuiViewport* viewport = ImGui::GetMainViewport();
76 const float panel_width =
81 const float panel_height =
82 std::max(0.0f, viewport->WorkSize.y - top_inset - safe_area.bottom);
86 ImVec2(viewport->WorkPos.x + bar_width, viewport->WorkPos.y + top_inset),
87 ImVec2(panel_width, panel_height),
89 .padding = {8.0f, 8.0f},
92 ImGuiWindowFlags_NoFocusOnAppearing);
98 ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]);
99 ImGui::Text(
"%s", category.c_str());
102 float avail_width = ImGui::GetContentRegionAvail().x;
104 ImGui::SameLine(ImGui::GetCursorPosX() + avail_width - chrome_icon_size.x);
106 "Collapse Sidebar",
false, ImVec4(0, 0, 0, 0),
107 "window_sidebar",
"collapse_side_panel")) {
115 const float standard_spacing =
117 const float compact_spacing = std::max(4.0f, standard_spacing * 0.75f);
118 const float search_spacing = compact_spacing;
119 const float search_width =
120 std::max(140.0f, ImGui::GetContentRegionAvail().x -
121 search_button_size.x - search_spacing);
122 ImGui::SetNextItemWidth(search_width);
123 ImGui::InputTextWithHint(
"##SidebarSearch",
ICON_MD_SEARCH " Filter...",
125 ImGui::SameLine(0.0f, search_spacing);
128 ImGui::BeginDisabled();
131 "Clear filter",
false,
133 "clear_sidebar_filter")) {
137 ImGui::EndDisabled();
140 auto read_dungeon_workbench_mode = [&]() ->
bool {
141 if (category !=
"Dungeon") {
149 bool dungeon_workbench_mode = read_dungeon_workbench_mode();
151 auto switch_to_dungeon_workbench_mode = [&]() ->
bool {
152 if (category !=
"Dungeon" || dungeon_workbench_mode) {
157 dungeon_workbench_mode =
true;
161 for (
const auto& descriptor :
163 const std::string& window_id = descriptor.card_id;
164 if (window_id ==
"dungeon.workbench") {
170 if (IsDungeonPanelModeWindow(window_id)) {
174 dungeon_workbench_mode = read_dungeon_workbench_mode();
175 return dungeon_workbench_mode;
178 auto switch_to_dungeon_window_mode = [&]() ->
bool {
179 if (category !=
"Dungeon" || !dungeon_workbench_mode) {
184 dungeon_workbench_mode =
false;
190 dungeon_workbench_mode = read_dungeon_workbench_mode();
191 return !dungeon_workbench_mode;
194 auto ensure_dungeon_window_mode_for_window =
195 [&](
const std::string& window_id) ->
bool {
196 if (category !=
"Dungeon" || !dungeon_workbench_mode ||
197 !IsDungeonPanelModeWindow(window_id)) {
200 return switch_to_dungeon_window_mode();
203 if (category ==
"Dungeon") {
208 const float workflow_gap = compact_spacing;
209 const float workflow_min_button_width = 96.0f;
210 const float workflow_available_width =
211 std::max(1.0f, ImGui::GetContentRegionAvail().x);
212 const bool stack_workflow_buttons =
213 workflow_available_width <=
214 (workflow_min_button_width * 2.0f + workflow_gap);
215 const float workflow_button_width =
216 stack_workflow_buttons
217 ? workflow_available_width
218 : std::max(workflow_min_button_width,
219 (workflow_available_width - workflow_gap) * 0.5f);
220 const float workflow_button_height =
222 auto draw_workflow_button = [&](
const char* id,
const char* icon,
223 const char* label,
bool active,
225 const ImVec2& button_size) ->
bool {
230 {{ImGuiCol_Button, active ? active_bg : inactive_bg},
231 {ImGuiCol_ButtonHovered, active ? active_bg : inactive_hover},
232 {ImGuiCol_ButtonActive, active ? active_bg : inactive_hover}});
233 const std::string button_label =
234 absl::StrFormat(
"%s %s##%s", icon, label,
id);
235 const bool clicked = ImGui::Button(button_label.c_str(), button_size);
236 if (ImGui::IsItemHovered() && tooltip && *tooltip) {
237 ImGui::SetTooltip(
"%s", tooltip);
241 const ImVec2 workflow_button_size(workflow_button_width,
242 workflow_button_height);
245 "Workbench", dungeon_workbench_mode,
246 "Workbench mode: integrated room browser + inspector",
247 workflow_button_size)) {
248 switch_to_dungeon_workbench_mode();
250 if (!stack_workflow_buttons) {
251 ImGui::SameLine(0.0f, workflow_gap);
254 "Windows", !dungeon_workbench_mode,
255 "Window mode: standalone Room List + Room Matrix + room windows",
256 workflow_button_size)) {
257 switch_to_dungeon_window_mode();
266 const bool rom_loaded = has_rom ? has_rom() :
true;
267 const bool disable_windows = !rom_loaded && category !=
"Emulator";
269 const auto category_windows =
271 int visible_windows_in_category = 0;
272 for (
const auto& category_window : category_windows) {
273 if (category_window.visibility_flag && *category_window.visibility_flag) {
274 ++visible_windows_in_category;
278 ImGui::TextDisabled(
"%d / %d visible", visible_windows_in_category,
279 static_cast<int>(category_windows.size()));
282 const float action_gap = compact_spacing;
283 const float action_min_button_width = 84.0f;
284 const float action_available_width =
285 std::max(1.0f, ImGui::GetContentRegionAvail().x);
286 const bool stack_action_buttons =
287 action_available_width <=
288 (action_min_button_width * 3.0f + (action_gap * 2.0f));
289 const float action_button_width =
291 ? action_available_width
292 : std::max(action_min_button_width,
293 (action_available_width - (action_gap * 2.0f)) / 3.0f);
294 const float action_button_height =
296 auto draw_action_button = [&](
const char* id,
const char* icon,
297 const char* label,
const char* tooltip,
298 const ImVec2& button_size) ->
bool {
299 const std::string button_label =
300 absl::StrFormat(
"%s %s##%s", icon, label,
id);
301 const bool clicked = ImGui::Button(button_label.c_str(), button_size);
302 if (ImGui::IsItemHovered() && tooltip && *tooltip) {
303 ImGui::SetTooltip(
"%s", tooltip);
307 const ImVec2 action_button_size(action_button_width, action_button_height);
309 if (draw_action_button(
"open_window_browser",
ICON_MD_APPS,
"Browser",
310 "Open Window Browser", action_button_size)) {
313 if (!stack_action_buttons) {
314 ImGui::SameLine(0.0f, action_gap);
317 const bool bulk_actions_enabled =
318 !disable_windows && !(category ==
"Dungeon" && dungeon_workbench_mode);
319 bool bulk_action_hovered =
false;
320 if (!bulk_actions_enabled) {
321 ImGui::BeginDisabled();
324 "Show all windows in this category",
325 action_button_size)) {
328 if (!stack_action_buttons) {
329 ImGui::SameLine(0.0f, action_gap);
331 if (!bulk_actions_enabled &&
332 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
333 bulk_action_hovered =
true;
336 "Hide",
"Hide all windows in this category",
337 action_button_size)) {
340 if (!bulk_actions_enabled) {
341 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
342 bulk_action_hovered =
true;
344 if (bulk_action_hovered && category ==
"Dungeon" &&
345 dungeon_workbench_mode) {
347 "Switch to Window workflow to bulk-manage room windows.");
349 ImGui::EndDisabled();
354 if (disable_windows) {
356 " Open a ROM to enable this category");
360 if (disable_windows) {
361 ImGui::BeginDisabled();
365 const ImVec4 disabled_text =
366 ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled);
367 auto window_text_color = [&](
bool visible) -> ImVec4 {
368 if (disable_windows) {
369 return disabled_text;
373 const float pin_button_side =
375 const ImVec2 pin_button_size(pin_button_side, pin_button_side);
376 auto draw_pin_toggle_button = [&](
const std::string& widget_id,
377 bool pinned) ->
bool {
378 ImGui::PushID(widget_id.c_str());
383 pinned ?
"Unpin window" :
"Pin window", pinned, pin_col,
384 "window_sidebar", widget_id.c_str());
389 bool pinned_section_open =
false;
391 bool has_pinned_in_category =
false;
392 for (
const auto& window_id : pinned_windows) {
394 if (window && window->category == category) {
395 has_pinned_in_category =
true;
400 if (has_pinned_in_category) {
401 pinned_section_open = ImGui::CollapsingHeader(
403 ImGuiTreeNodeFlags_DefaultOpen);
404 if (pinned_section_open) {
405 for (
const auto& window_id : pinned_windows) {
407 if (!window || window->category != category) {
414 if (draw_pin_toggle_button(
"pin_" + window->card_id,
true)) {
418 ImGui::SameLine(0.0f, compact_spacing);
420 std::string label = absl::StrFormat(
"%s %s", window->icon.c_str(),
421 window->display_name.c_str());
423 (std::string(
"pinned_select_") + window->card_id).c_str());
426 window_text_color(visible));
427 ImVec2 item_size(ImGui::GetContentRegionAvail().x,
429 if (ImGui::Selectable(label.c_str(), visible,
430 ImGuiSelectableFlags_None, item_size)) {
431 const bool switched_mode =
432 ensure_dungeon_window_mode_for_window(window->card_id);
439 const bool new_visible =
440 window->visibility_flag ? *window->visibility_flag :
false;
443 const std::string window_name =
445 if (!window_name.empty()) {
446 ImGui::SetWindowFocus(window_name.c_str());
463 if (window_content_open) {
464 for (
const auto& window : windows) {
466 window.card_id, window.shortcut_hint)) {
471 if (pinned_section_open && is_pinned) {
476 window.visibility_flag ? *window.visibility_flag :
false;
478 if (draw_pin_toggle_button(
"pin_" + window.card_id, is_pinned)) {
481 ImGui::SameLine(0.0f, compact_spacing);
484 absl::StrFormat(
"%s %s", window.icon.c_str(), window.display_name.c_str());
485 ImGui::PushID((std::string(
"window_select_") + window.card_id).c_str());
488 window_text_color(visible));
489 ImVec2 item_size(ImGui::GetContentRegionAvail().x, pin_button_size.y);
490 if (ImGui::Selectable(label.c_str(), visible, ImGuiSelectableFlags_None,
492 const bool switched_mode =
493 ensure_dungeon_window_mode_for_window(window.card_id);
500 const bool new_visible =
501 window.visibility_flag ? *window.visibility_flag :
false;
505 const std::string window_name =
507 if (!window_name.empty()) {
508 ImGui::SetWindowFocus(window_name.c_str());
515 if (ImGui::IsItemHovered() && !window.shortcut_hint.empty()) {
516 ImGui::SetTooltip(
"%s", window.shortcut_hint.c_str());
522 if (disable_windows) {
523 ImGui::EndDisabled();
526 const float handle_width = 6.0f;
527 const ImVec2 panel_pos = ImGui::GetWindowPos();
528 const float panel_draw_height = ImGui::GetWindowHeight();
529 ImGui::SetCursorScreenPos(
530 ImVec2(panel_pos.x + panel_width - handle_width * 0.5f, panel_pos.y));
531 ImGui::InvisibleButton(
"##SidePanelResizeHandle",
532 ImVec2(handle_width, panel_draw_height));
533 const bool handle_hovered = ImGui::IsItemHovered();
534 const bool handle_active = ImGui::IsItemActive();
535 if (handle_hovered || handle_active) {
536 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
538 if (handle_hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
542 const float new_width = panel_width + ImGui::GetIO().MouseDelta.x;
544 ImGui::SetTooltip(
"Width: %.0f px",
549 handle_color.w = handle_active ? 0.95f : (handle_hovered ? 0.72f : 0.35f);
550 ImGui::GetWindowDrawList()->AddLine(
551 ImVec2(panel_pos.x + panel_width - 1.0f, panel_pos.y),
552 ImVec2(panel_pos.x + panel_width - 1.0f,
553 panel_pos.y + panel_draw_height),
554 ImGui::GetColorU32(handle_color), handle_active ? 2.0f : 1.0f);