75 std::function<
bool()> has_rom) {
77 const ImGuiViewport* viewport = ImGui::GetMainViewport();
79 const float panel_width =
84 const float bottom_reserved =
88 const float panel_height =
89 std::max(0.0f, viewport->WorkSize.y - top_inset - safe_area.bottom -
94 ImVec2(viewport->WorkPos.x + bar_width, viewport->WorkPos.y + top_inset),
95 ImVec2(panel_width, panel_height),
97 .padding = {8.0f, 8.0f},
100 ImGuiWindowFlags_NoFocusOnAppearing);
106 ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]);
107 ImGui::Text(
"%s", category.c_str());
110 float avail_width = ImGui::GetContentRegionAvail().x;
112 ImGui::SameLine(ImGui::GetCursorPosX() + avail_width - chrome_icon_size.x);
114 "Collapse Sidebar",
false, ImVec4(0, 0, 0, 0),
115 "window_sidebar",
"collapse_side_panel")) {
123 const float standard_spacing =
125 const float compact_spacing = std::max(4.0f, standard_spacing * 0.75f);
126 const float search_spacing = compact_spacing;
127 const float search_width =
128 std::max(140.0f, ImGui::GetContentRegionAvail().x - search_button_size.x -
130 ImGui::SetNextItemWidth(search_width);
131 ImGui::InputTextWithHint(
"##SidebarSearch",
ICON_MD_SEARCH " Filter...",
133 ImGui::SameLine(0.0f, search_spacing);
136 ImGui::BeginDisabled();
139 "Clear filter",
false,
141 "clear_sidebar_filter")) {
145 ImGui::EndDisabled();
148 auto read_dungeon_workbench_mode = [&]() ->
bool {
149 if (category !=
"Dungeon") {
157 bool dungeon_workbench_mode = read_dungeon_workbench_mode();
159 auto switch_to_dungeon_workbench_mode = [&]() ->
bool {
160 if (category !=
"Dungeon" || dungeon_workbench_mode) {
165 dungeon_workbench_mode =
true;
173 for (
const auto& descriptor :
175 const std::string& window_id = descriptor.card_id;
176 if (window_id ==
"dungeon.workbench") {
182 if (IsDungeonPanelModeWindow(window_id)) {
186 dungeon_workbench_mode = read_dungeon_workbench_mode();
187 return dungeon_workbench_mode;
190 auto switch_to_dungeon_window_mode = [&]() ->
bool {
191 if (category !=
"Dungeon" || !dungeon_workbench_mode) {
196 dungeon_workbench_mode =
false;
202 dungeon_workbench_mode = read_dungeon_workbench_mode();
203 return !dungeon_workbench_mode;
206 auto ensure_dungeon_window_mode_for_window =
207 [&](
const std::string& window_id) ->
bool {
208 if (category !=
"Dungeon" || !dungeon_workbench_mode ||
209 !IsDungeonPanelModeWindow(window_id)) {
212 return switch_to_dungeon_window_mode();
215 if (category ==
"Dungeon") {
220 const float workflow_gap = compact_spacing;
221 const float workflow_min_button_width = 96.0f;
222 const float workflow_available_width =
223 std::max(1.0f, ImGui::GetContentRegionAvail().x);
224 const bool stack_workflow_buttons =
225 workflow_available_width <=
226 (workflow_min_button_width * 2.0f + workflow_gap);
227 const float workflow_button_width =
228 stack_workflow_buttons
229 ? workflow_available_width
230 : std::max(workflow_min_button_width,
231 (workflow_available_width - workflow_gap) * 0.5f);
232 const float workflow_button_height =
234 auto draw_workflow_button =
235 [&](
const char* id,
const char* icon,
const char* label,
bool active,
236 const char* tooltip,
const ImVec2& button_size) ->
bool {
241 {{ImGuiCol_Button, active ? active_bg : inactive_bg},
242 {ImGuiCol_ButtonHovered, active ? active_bg : inactive_hover},
243 {ImGuiCol_ButtonActive, active ? active_bg : inactive_hover}});
244 const std::string button_label =
245 absl::StrFormat(
"%s %s##%s", icon, label,
id);
246 const bool clicked = ImGui::Button(button_label.c_str(), button_size);
247 if (ImGui::IsItemHovered() && tooltip && *tooltip) {
248 ImGui::SetTooltip(
"%s", tooltip);
252 const ImVec2 workflow_button_size(workflow_button_width,
253 workflow_button_height);
255 if (draw_workflow_button(
257 dungeon_workbench_mode,
258 "Workbench mode: integrated room browser + inspector",
259 workflow_button_size)) {
260 switch_to_dungeon_workbench_mode();
262 if (!stack_workflow_buttons) {
263 ImGui::SameLine(0.0f, workflow_gap);
265 if (draw_workflow_button(
267 !dungeon_workbench_mode,
268 "Window mode: standalone Room List + Room Matrix + room windows",
269 workflow_button_size)) {
270 switch_to_dungeon_window_mode();
279 const bool rom_loaded = has_rom ? has_rom() :
true;
280 const bool disable_windows = !rom_loaded && category !=
"Emulator";
282 const auto category_windows =
284 int visible_windows_in_category = 0;
285 int total_windows_in_category = 0;
286 for (
const auto& category_window : category_windows) {
287 ++total_windows_in_category;
288 if (category_window.visibility_flag && *category_window.visibility_flag) {
289 ++visible_windows_in_category;
293 ImGui::TextDisabled(tr(
"%d / %d visible"), visible_windows_in_category,
294 total_windows_in_category);
297 const float action_gap = compact_spacing;
298 const float action_min_button_width = 84.0f;
299 const float action_available_width =
300 std::max(1.0f, ImGui::GetContentRegionAvail().x);
301 const bool stack_action_buttons =
302 action_available_width <=
303 (action_min_button_width * 3.0f + (action_gap * 2.0f));
304 const float action_button_width =
306 ? action_available_width
307 : std::max(action_min_button_width,
308 (action_available_width - (action_gap * 2.0f)) / 3.0f);
309 const float action_button_height =
311 auto draw_action_button = [&](
const char* id,
const char* icon,
312 const char* label,
const char* tooltip,
313 const ImVec2& button_size) ->
bool {
314 const std::string button_label =
315 absl::StrFormat(
"%s %s##%s", icon, label,
id);
316 const bool clicked = ImGui::Button(button_label.c_str(), button_size);
317 if (ImGui::IsItemHovered() && tooltip && *tooltip) {
318 ImGui::SetTooltip(
"%s", tooltip);
322 const ImVec2 action_button_size(action_button_width, action_button_height);
324 if (draw_action_button(
"open_window_browser",
ICON_MD_APPS,
"Browser",
325 "Open Window Browser", action_button_size)) {
328 if (!stack_action_buttons) {
329 ImGui::SameLine(0.0f, action_gap);
332 const bool bulk_actions_enabled =
333 !disable_windows && !(category ==
"Dungeon" && dungeon_workbench_mode);
334 bool bulk_action_hovered =
false;
335 if (!bulk_actions_enabled) {
336 ImGui::BeginDisabled();
339 "Show all windows in this category",
340 action_button_size)) {
343 if (!stack_action_buttons) {
344 ImGui::SameLine(0.0f, action_gap);
346 if (!bulk_actions_enabled &&
347 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
348 bulk_action_hovered =
true;
351 "Hide",
"Hide all windows in this category",
352 action_button_size)) {
355 if (!bulk_actions_enabled) {
356 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
357 bulk_action_hovered =
true;
359 if (bulk_action_hovered && category ==
"Dungeon" &&
360 dungeon_workbench_mode) {
362 tr(
"Switch to Window workflow to bulk-manage room windows."));
364 ImGui::EndDisabled();
369 if (disable_windows) {
371 " Open a ROM to enable this category");
375 if (disable_windows) {
376 ImGui::BeginDisabled();
380 const ImVec4 disabled_text = ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled);
381 auto window_text_color = [&](
bool visible) -> ImVec4 {
382 if (disable_windows) {
383 return disabled_text;
387 const float pin_button_side =
389 const ImVec2 pin_button_size(pin_button_side, pin_button_side);
390 auto draw_pin_toggle_button = [&](
const std::string& widget_id,
391 bool pinned) ->
bool {
392 ImGui::PushID(widget_id.c_str());
397 pinned ?
"Unpin window" :
"Pin window", pinned, pin_col,
398 "window_sidebar", widget_id.c_str());
403 bool pinned_section_open =
false;
405 bool has_pinned_in_category =
false;
406 for (
const auto& window_id : pinned_windows) {
409 if (window && window->category == category) {
410 has_pinned_in_category =
true;
415 if (has_pinned_in_category) {
416 pinned_section_open = ImGui::CollapsingHeader(
418 if (pinned_section_open) {
419 for (
const auto& window_id : pinned_windows) {
422 if (!window || window->category != category) {
429 if (draw_pin_toggle_button(
"pin_" + window->card_id,
true)) {
433 ImGui::SameLine(0.0f, compact_spacing);
435 std::string label = absl::StrFormat(
"%s %s", window->icon.c_str(),
436 window->display_name.c_str());
438 (std::string(
"pinned_select_") + window->card_id).c_str());
441 window_text_color(visible));
442 ImVec2 item_size(ImGui::GetContentRegionAvail().x,
444 if (ImGui::Selectable(label.c_str(), visible,
445 ImGuiSelectableFlags_None, item_size)) {
446 const bool switched_mode =
447 ensure_dungeon_window_mode_for_window(window->card_id);
454 const bool new_visible =
455 window->visibility_flag ? *window->visibility_flag :
false;
458 const std::string window_name =
460 if (!window_name.empty()) {
461 ImGui::SetWindowFocus(window_name.c_str());
478 if (window_content_open) {
479 for (
const auto& window : windows) {
481 window.card_id, window.shortcut_hint)) {
486 if (pinned_section_open && is_pinned) {
491 window.visibility_flag ? *window.visibility_flag :
false;
493 if (draw_pin_toggle_button(
"pin_" + window.card_id, is_pinned)) {
496 ImGui::SameLine(0.0f, compact_spacing);
498 std::string label = absl::StrFormat(
"%s %s", window.icon.c_str(),
499 window.display_name.c_str());
500 ImGui::PushID((std::string(
"window_select_") + window.card_id).c_str());
503 window_text_color(visible));
504 ImVec2 item_size(ImGui::GetContentRegionAvail().x, pin_button_size.y);
505 if (ImGui::Selectable(label.c_str(), visible, ImGuiSelectableFlags_None,
507 const bool switched_mode =
508 ensure_dungeon_window_mode_for_window(window.card_id);
515 const bool new_visible =
516 window.visibility_flag ? *window.visibility_flag :
false;
520 const std::string window_name =
522 if (!window_name.empty()) {
523 ImGui::SetWindowFocus(window_name.c_str());
530 if (ImGui::IsItemHovered() && !window.shortcut_hint.empty()) {
531 ImGui::SetTooltip(
"%s", window.shortcut_hint.c_str());
537 if (disable_windows) {
538 ImGui::EndDisabled();
541 const float handle_width = 6.0f;
542 const ImVec2 panel_pos = ImGui::GetWindowPos();
543 const float panel_draw_height = ImGui::GetWindowHeight();
544 ImGui::SetCursorScreenPos(
545 ImVec2(panel_pos.x + panel_width - handle_width * 0.5f, panel_pos.y));
546 ImGui::InvisibleButton(
"##SidePanelResizeHandle",
547 ImVec2(handle_width, panel_draw_height));
548 const bool handle_hovered = ImGui::IsItemHovered();
549 const bool handle_active = ImGui::IsItemActive();
550 if (handle_hovered || handle_active) {
551 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
553 if (handle_hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
557 const float new_width = panel_width + ImGui::GetIO().MouseDelta.x;
560 tr(
"Width: %.0f px"),
565 handle_color.w = handle_active ? 0.95f : (handle_hovered ? 0.72f : 0.35f);
566 ImGui::GetWindowDrawList()->AddLine(
567 ImVec2(panel_pos.x + panel_width - 1.0f, panel_pos.y),
568 ImVec2(panel_pos.x + panel_width - 1.0f, panel_pos.y + panel_draw_height),
569 ImGui::GetColorU32(handle_color), handle_active ? 2.0f : 1.0f);