feat: Introduce CompactToolbar and EditorCard for Enhanced Editor Layout

- Added CompactToolbar class to provide a modern, ultra-compact toolbar for the editor, featuring mode buttons, property editing, and quick actions.
- Implemented EditorCard class for draggable, dockable editor sub-windows, enhancing the user interface with responsive and themed cards.
- Introduced EditorLayout class to manage the overall editor layout, integrating the toolbar and main canvas for a cohesive user experience.
- Updated CMake configuration to include new source files, ensuring proper build integration.
This commit is contained in:
scawful
2025-10-05 17:05:10 -04:00
parent 951e7891b4
commit ea9409f767
4 changed files with 510 additions and 6 deletions

View File

@@ -0,0 +1,316 @@
#define IMGUI_DEFINE_MATH_OPERATORS
#include "app/gui/editor_layout.h"
#include "absl/strings/str_format.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/ui_helpers.h"
#include "app/gui/widgets/widget_id_registry.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
namespace yaze {
namespace gui {
// ============================================================================
// CompactToolbar Implementation
// ============================================================================
void CompactToolbar::Begin() {
// Ultra-compact toolbar with no padding waste
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6, 3));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 6));
ImGui::BeginGroup();
in_toolbar_ = true;
button_count_ = 0;
}
void CompactToolbar::End() {
ImGui::EndGroup();
ImGui::PopStyleVar(3);
ImGui::Separator();
in_toolbar_ = false;
}
void CompactToolbar::BeginModeGroup() {
// Visual grouping with subtle background - taller for better button visibility
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.17f, 0.5f));
ImGui::BeginChild("##ModeGroup", ImVec2(0, 40), true,
ImGuiWindowFlags_NoScrollbar);
}
bool CompactToolbar::ModeButton(const char* icon, bool selected, const char* tooltip) {
if (selected) {
ImGui::PushStyleColor(ImGuiCol_Button, GetAccentColor());
}
bool clicked = ImGui::Button(icon, ImVec2(32, 32)); // Larger buttons for better usability
if (selected) {
ImGui::PopStyleColor();
}
// Register for test automation
if (ImGui::GetItemID() != 0 && tooltip) {
std::string button_path = absl::StrFormat("ModeButton:%s", tooltip);
WidgetIdRegistry::Instance().RegisterWidget(
button_path, "button", ImGui::GetItemID(), tooltip);
}
if (tooltip && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", tooltip);
}
ImGui::SameLine();
button_count_++;
return clicked;
}
void CompactToolbar::EndModeGroup() {
ImGui::EndChild();
ImGui::PopStyleColor();
ImGui::SameLine();
AddSeparator();
}
void CompactToolbar::AddSeparator() {
ImGui::TextDisabled("|");
ImGui::SameLine();
}
void CompactToolbar::AddRomBadge(uint8_t version, std::function<void()> on_upgrade) {
RomVersionBadge(version == 0xFF ? "Vanilla" :
absl::StrFormat("v%d", version).c_str(),
version == 0xFF);
if (on_upgrade && (version == 0xFF || version < 3)) {
ImGui::SameLine();
if (ImGui::SmallButton(ICON_MD_UPGRADE)) {
on_upgrade();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Upgrade to ZSCustomOverworld v3");
}
}
ImGui::SameLine();
AddSeparator();
}
bool CompactToolbar::AddProperty(const char* icon, const char* label,
uint8_t* value,
std::function<void()> on_change) {
ImGui::Text("%s", icon);
ImGui::SameLine();
ImGui::SetNextItemWidth(55);
bool changed = InputHexByte(label, value);
if (changed && on_change) {
on_change();
}
ImGui::SameLine();
return changed;
}
bool CompactToolbar::AddProperty(const char* icon, const char* label,
uint16_t* value,
std::function<void()> on_change) {
ImGui::Text("%s", icon);
ImGui::SameLine();
ImGui::SetNextItemWidth(70);
bool changed = InputHexWord(label, value);
if (changed && on_change) {
on_change();
}
ImGui::SameLine();
return changed;
}
bool CompactToolbar::AddCombo(const char* icon, int* current,
const char* const items[], int count) {
ImGui::Text("%s", icon);
ImGui::SameLine();
ImGui::SetNextItemWidth(110);
bool changed = ImGui::Combo("##combo", current, items, count);
ImGui::SameLine();
return changed;
}
bool CompactToolbar::AddToggle(const char* icon, bool* state, const char* tooltip) {
bool result = ToggleIconButton(icon, icon, state, tooltip);
ImGui::SameLine();
return result;
}
bool CompactToolbar::AddAction(const char* icon, const char* tooltip) {
bool clicked = ImGui::SmallButton(icon);
// Register for test automation
if (ImGui::GetItemID() != 0 && tooltip) {
std::string button_path = absl::StrFormat("ToolbarAction:%s", tooltip);
WidgetIdRegistry::Instance().RegisterWidget(
button_path, "button", ImGui::GetItemID(), tooltip);
}
if (tooltip && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", tooltip);
}
ImGui::SameLine();
return clicked;
}
bool CompactToolbar::BeginCollapsibleSection(const char* label, bool* p_open) {
ImGui::NewLine(); // Start on new line
bool is_open = ImGui::CollapsingHeader(label, ImGuiTreeNodeFlags_None);
if (p_open) *p_open = is_open;
in_section_ = is_open;
return is_open;
}
void CompactToolbar::EndCollapsibleSection() {
in_section_ = false;
}
void CompactToolbar::AddV3StatusBadge(uint8_t version, std::function<void()> on_settings) {
if (version >= 3 && version != 0xFF) {
StatusBadge("v3 Active", ButtonType::Success);
ImGui::SameLine();
if (ImGui::SmallButton(ICON_MD_TUNE " Settings") && on_settings) {
on_settings();
}
} else {
StatusBadge("v3 Available", ButtonType::Default);
ImGui::SameLine();
if (ImGui::SmallButton(ICON_MD_UPGRADE " Upgrade")) {
ImGui::OpenPopup("UpgradeROMVersion");
}
}
ImGui::SameLine();
}
bool CompactToolbar::AddUsageStatsButton(const char* tooltip) {
bool clicked = ImGui::SmallButton(ICON_MD_ANALYTICS " Usage");
if (tooltip && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", tooltip);
}
ImGui::SameLine();
return clicked;
}
// ============================================================================
// EditorCard Implementation
// ============================================================================
EditorCard::EditorCard(const char* title, const char* icon)
: title_(title), icon_(icon ? icon : ""), default_size_(400, 300) {}
void EditorCard::SetDefaultSize(float width, float height) {
default_size_ = ImVec2(width, height);
}
void EditorCard::SetPosition(Position pos) {
position_ = pos;
}
bool EditorCard::Begin(bool* p_open) {
ImGuiWindowFlags flags = ImGuiWindowFlags_None;
// Set initial position based on position enum
if (first_draw_) {
float display_width = ImGui::GetIO().DisplaySize.x;
float display_height = ImGui::GetIO().DisplaySize.y;
switch (position_) {
case Position::Right:
ImGui::SetNextWindowPos(ImVec2(display_width - default_size_.x - 10, 30),
ImGuiCond_FirstUseEver);
break;
case Position::Left:
ImGui::SetNextWindowPos(ImVec2(10, 30), ImGuiCond_FirstUseEver);
break;
case Position::Bottom:
ImGui::SetNextWindowPos(
ImVec2(10, display_height - default_size_.y - 10),
ImGuiCond_FirstUseEver);
break;
case Position::Floating:
case Position::Free:
ImGui::SetNextWindowPos(
ImVec2(display_width * 0.5f - default_size_.x * 0.5f,
display_height * 0.3f),
ImGuiCond_FirstUseEver);
break;
}
ImGui::SetNextWindowSize(default_size_, ImGuiCond_FirstUseEver);
first_draw_ = false;
}
// Create window title with icon
std::string window_title = icon_.empty() ? title_ : icon_ + " " + title_;
// Modern card styling
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10));
ImGui::PushStyleColor(ImGuiCol_TitleBg, GetThemeColor(ImGuiCol_TitleBg));
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, GetAccentColor());
bool visible = ImGui::Begin(window_title.c_str(), p_open, flags);
// Register card window for test automation
if (ImGui::GetCurrentWindow() && ImGui::GetCurrentWindow()->ID != 0) {
std::string card_path = absl::StrFormat("EditorCard:%s", title_.c_str());
WidgetIdRegistry::Instance().RegisterWidget(
card_path, "window", ImGui::GetCurrentWindow()->ID,
absl::StrFormat("Editor card: %s", title_.c_str()));
}
return visible;
}
void EditorCard::End() {
ImGui::End();
ImGui::PopStyleColor(2);
ImGui::PopStyleVar(2);
}
// ============================================================================
// EditorLayout Implementation
// ============================================================================
void EditorLayout::Begin() {
toolbar_.Begin();
in_layout_ = true;
}
void EditorLayout::End() {
toolbar_.End();
in_layout_ = false;
}
void EditorLayout::BeginMainCanvas() {
// Main canvas takes remaining space
ImGui::BeginChild("##MainCanvas", ImVec2(0, 0), false);
}
void EditorLayout::EndMainCanvas() {
ImGui::EndChild();
}
void EditorLayout::RegisterCard(EditorCard* card) {
cards_.push_back(card);
}
} // namespace gui
} // namespace yaze

184
src/app/gui/editor_layout.h Normal file
View File

@@ -0,0 +1,184 @@
#ifndef YAZE_APP_GUI_EDITOR_LAYOUT_H
#define YAZE_APP_GUI_EDITOR_LAYOUT_H
#include <functional>
#include <string>
#include <vector>
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @class CompactToolbar
* @brief Ultra-compact toolbar that merges mode buttons with settings
*
* Design Philosophy:
* - Single horizontal bar with everything inline
* - Small icon-only buttons for modes
* - Inline property editing (InputHex with scroll)
* - No wasted vertical space
* - Beautiful, modern appearance
*
* Layout: [Mode Icons] | [ROM Badge] [World] [GFX] [Pal] [Spr] ... | [Quick Actions]
*/
class CompactToolbar {
public:
CompactToolbar() = default;
// Begin the toolbar
void Begin();
// End the toolbar
void End();
// Add mode button group
void BeginModeGroup();
bool ModeButton(const char* icon, bool selected, const char* tooltip);
void EndModeGroup();
// Add separator
void AddSeparator();
// Add ROM version badge
void AddRomBadge(uint8_t version, std::function<void()> on_upgrade = nullptr);
// Add quick property (inline hex editing)
bool AddProperty(const char* icon, const char* label, uint8_t* value,
std::function<void()> on_change = nullptr);
bool AddProperty(const char* icon, const char* label, uint16_t* value,
std::function<void()> on_change = nullptr);
// Add combo selector
bool AddCombo(const char* icon, int* current, const char* const items[], int count);
// Add toggle button
bool AddToggle(const char* icon, bool* state, const char* tooltip);
// Add action button
bool AddAction(const char* icon, const char* tooltip);
// Add collapsible settings section
bool BeginCollapsibleSection(const char* label, bool* p_open);
void EndCollapsibleSection();
// Add v3 settings indicator
void AddV3StatusBadge(uint8_t version, std::function<void()> on_settings);
// Add usage statistics button
bool AddUsageStatsButton(const char* tooltip);
// Get button count for widget registration
int GetButtonCount() const { return button_count_; }
private:
bool in_toolbar_ = false;
bool in_section_ = false;
int button_count_ = 0;
};
/**
* @class EditorCard
* @brief Draggable, dockable card for editor sub-windows
*
* Replaces traditional child windows with modern cards that can be:
* - Dragged and positioned freely
* - Docked to edges (optional)
* - Minimized to title bar
* - Resized responsively
* - Themed beautifully
*
* Usage:
* ```cpp
* EditorCard tile_card("Tile Selector", ICON_MD_GRID_VIEW);
* tile_card.SetDefaultSize(300, 400);
* tile_card.SetPosition(CardPosition::Right);
*
* if (tile_card.Begin()) {
* // Draw tile selector content
* tile_card.End();
* }
* ```
*/
class EditorCard {
public:
enum class Position {
Free, // Floating window
Right, // Docked to right side
Left, // Docked to left side
Bottom, // Docked to bottom
Floating, // Floating but position saved
};
EditorCard(const char* title, const char* icon = nullptr);
// Set card properties
void SetDefaultSize(float width, float height);
void SetPosition(Position pos);
void SetMinimizable(bool minimizable) { minimizable_ = minimizable; }
void SetClosable(bool closable) { closable_ = closable; }
// Begin drawing the card
bool Begin(bool* p_open = nullptr);
// End drawing
void End();
// Minimize/maximize
void SetMinimized(bool minimized) { minimized_ = minimized; }
bool IsMinimized() const { return minimized_; }
private:
std::string title_;
std::string icon_;
ImVec2 default_size_;
Position position_ = Position::Free;
bool minimizable_ = true;
bool closable_ = true;
bool minimized_ = false;
bool first_draw_ = true;
};
/**
* @class EditorLayout
* @brief Modern layout manager for editor components
*
* Manages the overall editor layout with:
* - Compact toolbar at top
* - Main canvas in center
* - Floating/docked cards for tools
* - No redundant headers
* - Responsive sizing
*/
class EditorLayout {
public:
EditorLayout() = default;
// Begin the editor layout
void Begin();
// End the editor layout
void End();
// Get toolbar reference
CompactToolbar& GetToolbar() { return toolbar_; }
// Begin main canvas area
void BeginMainCanvas();
void EndMainCanvas();
// Register a card (for layout management)
void RegisterCard(EditorCard* card);
private:
CompactToolbar toolbar_;
std::vector<EditorCard*> cards_;
bool in_layout_ = false;
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_EDITOR_LAYOUT_H

View File

@@ -18,6 +18,7 @@ set(
app/gui/ui_helpers.cc
app/gui/toolset.cc
app/gui/settings_bar.cc
app/gui/editor_layout.cc
# Canvas system components
app/gui/canvas/canvas_modals.cc
app/gui/canvas/canvas_context_menu.cc

View File

@@ -1,6 +1,5 @@
#include "app/gui/ui_helpers.h"
#include "absl/strings/str_format.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/theme_manager.h"
@@ -43,21 +42,25 @@ ImVec4 GetAccentColor() {
return ConvertColorToImVec4(theme.accent);
}
// Entity/Map marker colors (normalized RGB + proper alpha)
// Entity/Map marker colors (vibrant with good visibility)
ImVec4 GetEntranceColor() {
return ImVec4(1.0f, 1.0f, 0.0f, 0.39f); // Yellow, 100/255 alpha
// Bright yellow with strong visibility
return ImVec4(1.0f, 0.9f, 0.0f, 0.85f); // Yellow-gold, high visibility
}
ImVec4 GetExitColor() {
return ImVec4(1.0f, 1.0f, 1.0f, 0.59f); // White, 150/255 alpha
// Bright cyan-white for contrast
return ImVec4(0.9f, 1.0f, 1.0f, 0.85f); // Cyan-white, high visibility
}
ImVec4 GetItemColor() {
return ImVec4(1.0f, 0.0f, 0.0f, 0.59f); // Red, 150/255 alpha
// Vibrant red for items
return ImVec4(1.0f, 0.2f, 0.2f, 0.85f); // Bright red, high visibility
}
ImVec4 GetSpriteColor() {
return ImVec4(1.0f, 0.0f, 1.0f, 0.59f); // Magenta, 150/255 alpha
// Bright magenta for sprites
return ImVec4(1.0f, 0.3f, 1.0f, 0.85f); // Bright magenta, high visibility
}
ImVec4 GetSelectedColor() {