yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
status_bar.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5
6#include "absl/strings/str_format.h"
10#include "app/gui/core/style.h"
15#include "imgui/imgui.h"
16#include "rom/rom.h"
17
18namespace yaze {
19namespace editor {
20
21namespace {
22
36
37const char* WorkflowStatusIcon(const ProjectWorkflowStatus& status,
38 const char* fallback) {
39 switch (status.state) {
41 return ICON_MD_SYNC;
45 return ICON_MD_ERROR;
47 default:
48 return fallback;
49 }
50}
51
53 const char* fallback_icon) {
54 if (!status.visible) {
55 return 0.0f;
56 }
57 const std::string label =
58 absl::StrFormat("%s %s", WorkflowStatusIcon(status, fallback_icon),
59 status.summary.empty() ? status.label : status.summary);
60 return ImGui::CalcTextSize(label.c_str()).x + 24.0f;
61}
62
63// Applies interactive affordances (hand cursor, click callback, tooltip) to the
64// most recently drawn segment item. Call immediately after the segment text
65// has been rendered so `ImGui::IsItemHovered()` / `IsItemClicked()` refer to it.
67 const bool interactive = static_cast<bool>(opts.on_click);
68 if (interactive && ImGui::IsItemHovered()) {
69 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
70 }
71 if (!opts.tooltip.empty() && ImGui::IsItemHovered()) {
72 ImGui::SetTooltip("%s", opts.tooltip.c_str());
73 }
74 if (interactive && ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
75 opts.on_click();
76 }
77}
78
79} // namespace
80
81float StatusBar::GetHeight() const {
82 if (!enabled_) {
83 return 0.0f;
84 }
87 }
88 return kStatusBarHeight;
89}
90
92 context_ = context;
93 if (context_) {
94 // Subscribe to UI status updates
96 [this](const StatusUpdateEvent& e) { HandleStatusUpdate(e); });
97
98 // Subscribe to selection changes from any editor
100 [this](const SelectionChangedEvent& e) {
101 if (e.IsEmpty()) {
103 } else {
104 SetSelection(static_cast<int>(e.Count()));
105 }
106 });
107
108 // Subscribe to zoom changes from any canvas
110 [this](const ZoomChangedEvent& e) { SetZoom(e.new_zoom); });
111 }
112}
113
115 switch (event.type) {
117 SetCursorPosition(event.x, event.y,
118 event.text.empty() ? nullptr : event.text.c_str());
119 break;
121 SetSelection(event.count, event.width, event.height);
122 break;
124 SetZoom(event.zoom);
125 break;
127 SetEditorMode(event.text);
128 break;
130 // Compatibility path: older callers may still emit Clear, but we no
131 // longer allow that event to wipe cursor/selection/zoom segments that can
132 // be owned by legacy event producers outside the new ContributeStatus()
133 // path.
135 break;
136 default:
137 break;
138 }
139}
140
141void StatusBar::SetSessionInfo(size_t session_id, size_t total_sessions,
142 const std::string& display_name) {
143 session_id_ = session_id;
144 total_sessions_ = total_sessions;
145 session_display_name_ = display_name;
146}
147
148void StatusBar::SetActiveEditor(const std::string& name) {
150}
151
152void StatusBar::SetActiveEditor(const std::string& name,
153 StatusBarSegmentOptions options) {
154 has_active_editor_ = !name.empty();
155 active_editor_ = name;
156 active_editor_options_ = std::move(options);
157}
158
164
165void StatusBar::SetDirtyScope(const std::string& short_label) {
167}
168
169void StatusBar::SetDirtyScope(const std::string& short_label,
170 StatusBarSegmentOptions options) {
171 has_dirty_scope_ = !short_label.empty();
172 dirty_scope_ = short_label;
173 dirty_scope_options_ = std::move(options);
174}
175
177 has_dirty_scope_ = false;
178 dirty_scope_.clear();
180}
181
182void StatusBar::SetCursorPosition(int x, int y, const char* label) {
183 has_cursor_ = true;
184 cursor_x_ = x;
185 cursor_y_ = y;
186 cursor_label_ = label ? label : "Pos";
187 cursor_options_ = {};
188}
189
190void StatusBar::SetCursorPosition(int x, int y, const char* label,
191 StatusBarSegmentOptions options) {
192 has_cursor_ = true;
193 cursor_x_ = x;
194 cursor_y_ = y;
195 cursor_label_ = label ? label : "Pos";
196 cursor_options_ = std::move(options);
197}
198
200 has_cursor_ = false;
201 cursor_options_ = {};
202}
203
204void StatusBar::SetSelection(int count, int width, int height) {
205 has_selection_ = true;
206 selection_count_ = count;
207 selection_width_ = width;
208 selection_height_ = height;
209}
210
212 has_selection_ = false;
213}
214
215void StatusBar::SetZoom(float level) {
216 has_zoom_ = true;
217 zoom_level_ = level;
218 zoom_options_ = {};
219}
220
221void StatusBar::SetZoom(float level, StatusBarSegmentOptions options) {
222 has_zoom_ = true;
223 zoom_level_ = level;
224 zoom_options_ = std::move(options);
225}
226
228 has_zoom_ = false;
229 zoom_options_ = {};
230}
231
232void StatusBar::SetEditorMode(const std::string& mode) {
233 has_mode_ = true;
234 editor_mode_ = mode;
235 mode_options_ = {};
236}
237
238void StatusBar::SetEditorMode(const std::string& mode,
239 StatusBarSegmentOptions options) {
240 has_mode_ = true;
241 editor_mode_ = mode;
242 mode_options_ = std::move(options);
243}
244
246 has_mode_ = false;
247 editor_mode_.clear();
248 mode_options_ = {};
249}
250
251void StatusBar::SetCustomSegment(const std::string& key,
252 const std::string& value) {
254}
255
256void StatusBar::SetCustomSegment(const std::string& key,
257 const std::string& value,
258 StatusBarSegmentOptions options) {
259 for (auto& seg : custom_segments_) {
260 if (seg.key == key) {
261 seg.value = value;
262 seg.options = std::move(options);
263 return;
264 }
265 }
266 custom_segments_.push_back({key, value, std::move(options)});
267}
268
269void StatusBar::ClearCustomSegment(const std::string& key) {
270 custom_segments_.erase(
271 std::remove_if(custom_segments_.begin(), custom_segments_.end(),
272 [&](const CustomSegment& s) { return s.key == key; }),
273 custom_segments_.end());
274}
275
285
290
291void StatusBar::SetAgentInfo(const std::string& provider,
292 const std::string& model, bool active) {
293 has_agent_ = true;
294 agent_provider_ = provider;
295 agent_model_ = model;
296 agent_active_ = active;
297}
298
300 has_agent_ = false;
301 agent_provider_.clear();
302 agent_model_.clear();
303 agent_active_ = false;
304}
305
307 if (!enabled_) {
308 return;
309 }
310
311 const ImGuiViewport* viewport = ImGui::GetMainViewport();
312
313 // Position at very bottom of viewport, above the safe area (home indicator).
314 // The dockspace is already reduced by GetBottomLayoutOffset() in controller.cc.
315 const float bar_height = GetHeight();
316 const float bottom_safe = gui::LayoutHelpers::GetSafeAreaInsets().bottom;
317 const float bar_y =
318 viewport->WorkPos.y + viewport->WorkSize.y - bar_height - bottom_safe;
319 const bool touch_mode = gui::LayoutHelpers::IsTouchDevice();
320 const ImVec2 panel_padding =
321 touch_mode ? ImVec2(14.0f, 7.0f) : ImVec2(8.0f, 4.0f);
322 const ImVec2 panel_spacing =
323 touch_mode ? ImVec2(12.0f, 0.0f) : ImVec2(8.0f, 0.0f);
324
325 // Status bar background - slightly elevated surface
326 ImVec4 bar_bg = gui::GetSurfaceContainerVec4();
327 ImVec4 bar_border = gui::GetOutlineVec4();
328
329 ImGuiWindowFlags extra_flags =
330 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse |
331 ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNavFocus |
332 ImGuiWindowFlags_NoBringToFrontOnFocus;
333
334 gui::FixedPanel bar("##StatusBar", ImVec2(viewport->WorkPos.x, bar_y),
335 ImVec2(viewport->WorkSize.x, bar_height),
336 {.bg = bar_bg,
337 .border = bar_border,
338 .padding = panel_padding,
339 .spacing = panel_spacing,
340 .border_size = 1.0f},
341 extra_flags);
342
343 if (bar) {
344 // Left section: ROM | DirtyScope | Session | Editor | Cursor | Selection | Custom
346
347 if (has_dirty_scope_) {
350 }
351
352 if (total_sessions_ > 1) {
355 }
356
357 if (has_active_editor_) {
360 }
361
362 // Middle section: Editor context (cursor, selection)
363 if (has_cursor_) {
366 }
367
368 if (has_selection_) {
371 }
372
373 // Custom segments (Room / Map / Drawer / …)
375
376 // Right section: Zoom, Mode (right-aligned)
377 float right_section_width = 0.0f;
378 std::string agent_label;
379 if (has_agent_) {
380 agent_label = agent_model_.empty() ? agent_provider_ : agent_model_;
381 if (agent_label.empty()) {
382 agent_label = "Agent";
383 }
384 const size_t max_len = 20;
385 if (agent_label.size() > max_len) {
386 agent_label = agent_label.substr(0, max_len - 3) + "...";
387 }
388 std::string label = std::string(ICON_MD_SMART_TOY " ") + agent_label;
389 right_section_width += ImGui::CalcTextSize(label.c_str()).x +
390 ImGui::GetStyle().FramePadding.x * 2.0f + 10.0f;
391 }
392 if (has_zoom_) {
393 right_section_width += ImGui::CalcTextSize("100%").x + 20.0f;
394 }
395 if (has_mode_) {
396 right_section_width +=
397 ImGui::CalcTextSize(editor_mode_.c_str()).x + 30.0f;
398 }
399 right_section_width += WorkflowStatusWidth(build_status_, ICON_MD_BUILD);
400 right_section_width += WorkflowStatusWidth(run_status_, ICON_MD_PLAY_ARROW);
402 right_section_width += 16.0f;
403 }
404 if (run_status_.visible) {
405 right_section_width += 16.0f;
406 }
407
408 if (right_section_width > 0.0f) {
409 float available = ImGui::GetContentRegionAvail().x;
410 if (available > right_section_width + 20.0f) {
411 ImGui::SameLine(ImGui::GetWindowWidth() - right_section_width - 16.0f);
412
417 }
418 }
419
420 if (run_status_.visible) {
422 if (has_agent_ || has_zoom_ || has_mode_) {
424 }
425 }
426
427 if (has_agent_) {
429 if (has_zoom_ || has_mode_) {
431 }
432 }
433
434 if (has_zoom_) {
436 if (has_mode_) {
438 }
439 }
440
441 if (has_mode_) {
443 }
444 }
445 }
446 }
447}
448
450 std::string label = agent_model_.empty() ? agent_provider_ : agent_model_;
451 if (label.empty()) {
452 label = "Agent";
453 }
454 const size_t max_len = 20;
455 if (label.size() > max_len) {
456 label = label.substr(0, max_len - 3) + "...";
457 }
458 std::string button_label = std::string(ICON_MD_SMART_TOY " ") + label;
459
460 ImVec4 text_color =
462 gui::StyleColorGuard agent_colors({
463 {ImGuiCol_Text, text_color},
464 {ImGuiCol_Button, gui::GetSurfaceContainerHighVec4()},
465 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighestVec4()},
466 });
467 if (gui::ThemedButton(button_label.c_str())) {
470 }
471 }
472
473 if (ImGui::IsItemHovered()) {
474 ImGui::BeginTooltip();
475 ImGui::Text(tr("%s Agent"), ICON_MD_SMART_TOY);
476 ImGui::TextDisabled(tr("Provider: %s"), agent_provider_.empty()
477 ? "mock"
478 : agent_provider_.c_str());
479 ImGui::TextDisabled(tr("Model: %s"), agent_model_.empty()
480 ? "not set"
481 : agent_model_.c_str());
482 ImGui::TextDisabled(tr("Toggle chat panel"));
483 ImGui::EndTooltip();
484 }
485}
486
488 const char* default_icon) {
489 const std::string display =
490 status.summary.empty() ? status.label : status.summary;
491 gui::ColoredTextF(WorkflowStatusColor(status.state), "%s %s",
492 WorkflowStatusIcon(status, default_icon), display.c_str());
493
494 if (ImGui::IsItemHovered()) {
495 ImGui::BeginTooltip();
496 ImGui::Text("%s", status.label.c_str());
497 if (!status.summary.empty()) {
498 ImGui::TextDisabled("%s", status.summary.c_str());
499 }
500 if (!status.detail.empty()) {
501 ImGui::Separator();
502 ImGui::TextWrapped("%s", status.detail.c_str());
503 }
504 if (!status.output_tail.empty()) {
505 ImGui::Separator();
506 ImGui::TextWrapped("%s", status.output_tail.c_str());
507 }
508 ImGui::EndTooltip();
509 }
510}
511
513 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
514
515 if (rom_ && rom_->is_loaded()) {
516 // ROM name
517 gui::ColoredTextF(ImGui::GetStyleColorVec4(ImGuiCol_Text), "%s %s",
519
520 // Dirty indicator
521 if (rom_->dirty()) {
522 ImGui::SameLine();
524 gui::ConvertColorToImVec4(theme.warning));
525
526 if (ImGui::IsItemHovered()) {
528 ImGui::SetTooltip("%s", dirty_scope_options_.tooltip.c_str());
529 } else {
530 ImGui::SetTooltip(tr("Unsaved changes"));
531 }
532 }
533 }
534 } else {
535 gui::ColoredTextF(gui::GetTextSecondaryVec4(), "%s No ROM loaded",
537 }
538}
539
541 if (!session_display_name_.empty()) {
543 session_display_name_.c_str());
544 } else {
547 }
548
549 if (ImGui::IsItemHovered()) {
550 ImGui::SetTooltip(tr("Session %zu of %zu"), session_id_ + 1,
552 }
553}
554
557 active_editor_.c_str());
558 ApplySegmentInteraction(active_editor_options_);
559 if (active_editor_options_.tooltip.empty() &&
560 !active_editor_options_.on_click && ImGui::IsItemHovered()) {
561 ImGui::SetTooltip(tr("Active editor"));
562 }
563}
564
566 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
567 gui::ColoredTextF(gui::ConvertColorToImVec4(theme.warning), "%s %s",
569 ApplySegmentInteraction(dirty_scope_options_);
570}
571
575 ApplySegmentInteraction(cursor_options_);
576}
577
579 gui::StyleColorGuard selection_color(ImGuiCol_Text,
581
582 if (selection_width_ > 0 && selection_height_ > 0) {
583 ImGui::Text(tr("%s %d (%dx%d)"), ICON_MD_SELECT_ALL, selection_count_,
585 } else if (selection_count_ > 0) {
586 ImGui::Text(tr("%s %d selected"), ICON_MD_SELECT_ALL, selection_count_);
587 }
588}
589
591 int zoom_percent = static_cast<int>(zoom_level_ * 100.0f);
593 zoom_percent);
594
595 // When no explicit tooltip is configured, fall back to the legacy zoom
596 // readout so existing behavior is preserved.
598 if (ImGui::IsItemHovered()) {
599 ImGui::SetTooltip(tr("Zoom: %d%%"), zoom_percent);
600 }
601 } else {
602 ApplySegmentInteraction(zoom_options_);
603 }
604}
605
608 ApplySegmentInteraction(mode_options_);
609}
610
612 for (const auto& segment : custom_segments_) {
615 segment.key.c_str(), segment.value.c_str());
616 ApplySegmentInteraction(segment.options);
617 }
618}
619
621 ImGui::SameLine();
623 ImGui::SameLine();
624}
625
626} // namespace editor
627} // namespace yaze
HandlerId Subscribe(std::function< void(const T &)> handler)
Definition event_bus.h:22
bool dirty() const
Definition rom.h:156
auto short_name() const
Definition rom.h:177
bool is_loaded() const
Definition rom.h:155
Instance-based runtime context replacing ContentRegistry::Context.
static constexpr float kStatusBarHeight
Definition status_bar.h:241
void HandleStatusUpdate(const StatusUpdateEvent &event)
StatusBarSegmentOptions dirty_scope_options_
Definition status_bar.h:278
GlobalEditorContext * context_
Definition status_bar.h:261
float GetHeight() const
Get the height of the status bar.
Definition status_bar.cc:81
std::string session_display_name_
Definition status_bar.h:268
std::vector< CustomSegment > custom_segments_
Definition status_bar.h:309
StatusBarSegmentOptions cursor_options_
Definition status_bar.h:285
static constexpr float kStatusBarTouchHeight
Definition status_bar.h:242
std::string agent_provider_
Definition status_bar.h:314
StatusBarSegmentOptions active_editor_options_
Definition status_bar.h:273
void SetSessionInfo(size_t session_id, size_t total_sessions, const std::string &display_name={})
Set session information.
void ClearCursorPosition()
Clear cursor position (no cursor in editor)
std::string active_editor_
Definition status_bar.h:272
std::function< void()> agent_toggle_callback_
Definition status_bar.h:316
void SetSelection(int count, int width=0, int height=0)
Set selection information.
StatusBarSegmentOptions zoom_options_
Definition status_bar.h:296
void ClearZoom()
Clear zoom display.
StatusBarSegmentOptions mode_options_
Definition status_bar.h:301
void SetActiveEditor(const std::string &name)
Set the active editor category/name for the context strip.
void SetDirtyScope(const std::string &short_label)
Compact dirty-scope chip (ROM / Project / Rooms / …).
void SetCustomSegment(const std::string &key, const std::string &value)
Set a custom segment with key-value pair.
void SetZoom(float level)
Set current zoom level.
void DrawProjectWorkflowSegment(const ProjectWorkflowStatus &status, const char *default_icon)
std::string cursor_label_
Definition status_bar.h:284
void ClearSelection()
Clear selection info.
void ClearEditorMode()
Clear editor mode display.
void Initialize(GlobalEditorContext *context)
Definition status_bar.cc:91
void ClearEditorContributions()
Clear frame-scoped editor contributions.
void SetCursorPosition(int x, int y, const char *label="Pos")
Set cursor/mouse position in editor coordinates.
void SetEditorMode(const std::string &mode)
Set the current editor mode or tool.
ProjectWorkflowStatus build_status_
Definition status_bar.h:318
void Draw()
Draw the status bar.
void ClearAllContext()
Clear all context (cursor, selection, zoom, mode, custom)
void ClearCustomSegment(const std::string &key)
Remove a custom segment.
ProjectWorkflowStatus run_status_
Definition status_bar.h:319
void SetAgentInfo(const std::string &provider, const std::string &model, bool active)
RAII for fixed-position panels (activity bar, side panel, status bar).
static SafeAreaInsets GetSafeAreaInsets()
RAII guard for ImGui style colors.
Definition style_guard.h:27
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
#define ICON_MD_PLAY_ARROW
Definition icons.h:1479
#define ICON_MD_WARNING_AMBER
Definition icons.h:2124
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_LAYERS
Definition icons.h:1068
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_DESCRIPTION
Definition icons.h:539
#define ICON_MD_BUILD
Definition icons.h:328
#define ICON_MD_ZOOM_IN
Definition icons.h:2194
#define ICON_MD_SELECT_ALL
Definition icons.h:1680
#define ICON_MD_SYNC
Definition icons.h:1919
#define ICON_MD_FIBER_MANUAL_RECORD
Definition icons.h:739
#define ICON_MD_SMART_TOY
Definition icons.h:1781
const char * WorkflowStatusIcon(const ProjectWorkflowStatus &status, const char *fallback)
Definition status_bar.cc:37
ImVec4 WorkflowStatusColor(ProjectWorkflowState state)
Definition status_bar.cc:23
void ApplySegmentInteraction(const StatusBarSegmentOptions &opts)
Definition status_bar.cc:66
float WorkflowStatusWidth(const ProjectWorkflowStatus &status, const char *fallback_icon)
Definition status_bar.cc:52
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
void ColoredText(const char *text, const ImVec4 &color)
ImVec4 GetSurfaceContainerHighestVec4()
ImVec4 GetSuccessColor()
Definition ui_helpers.cc:49
bool ThemedButton(const char *label, const ImVec2 &size, const char *panel_id, const char *anim_id)
Draw a standard text button with theme colors.
ImVec4 GetPrimaryVec4()
ImVec4 GetTextSecondaryVec4()
void ColoredTextF(const ImVec4 &color, const char *fmt,...)
ImVec4 GetErrorColor()
Definition ui_helpers.cc:59
ImVec4 GetSurfaceContainerHighVec4()
ImVec4 GetInfoColor()
Definition ui_helpers.cc:64
ImVec4 GetOutlineVec4()
ImVec4 GetSurfaceContainerVec4()
Published when selection changes in any editor.
Optional behavior for an interactive status bar segment.
Definition status_bar.h:27
std::function< void()> on_click
Definition status_bar.h:28
Published when zoom level changes in any canvas/editor.