imgui-frontend-engineer: refresh editor UI layout
This commit is contained in:
@@ -47,7 +47,12 @@ float LayoutCoordinator::GetLeftLayoutOffset() const {
|
||||
|
||||
// Add Side Panel width if expanded
|
||||
if (panel_manager_->IsPanelExpanded()) {
|
||||
width += PanelManager::GetSidePanelWidth();
|
||||
float viewport_width = 0.0f;
|
||||
if (ImGui::GetCurrentContext()) {
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
viewport_width = viewport ? viewport->WorkSize.x : 0.0f;
|
||||
}
|
||||
width += PanelManager::GetSidePanelWidthForViewport(viewport_width);
|
||||
}
|
||||
|
||||
return width;
|
||||
|
||||
@@ -210,7 +210,8 @@ void ActivityBar::DrawSidePanel(size_t session_id, const std::string& category,
|
||||
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
const float bar_width = PanelManager::GetSidebarWidth();
|
||||
const float panel_width = PanelManager::GetSidePanelWidth();
|
||||
const float panel_width =
|
||||
PanelManager::GetSidePanelWidthForViewport(viewport->WorkSize.x);
|
||||
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(viewport->WorkPos.x + bar_width, viewport->WorkPos.y));
|
||||
@@ -661,4 +662,4 @@ void ActivityBar::DrawPanelBrowser(size_t session_id, bool* p_open) {
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
} // namespace yaze
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "app/editor/menu/right_panel_manager.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/agent/agent_chat.h"
|
||||
@@ -14,10 +15,32 @@
|
||||
#include "app/gui/core/style.h"
|
||||
#include "app/gui/core/theme_manager.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "util/platform_paths.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string ResolveAgentChatHistoryPath() {
|
||||
auto agent_dir = util::PlatformPaths::GetAppDataSubdirectory("agent");
|
||||
if (agent_dir.ok()) {
|
||||
return (*agent_dir / "agent_chat_history.json").string();
|
||||
}
|
||||
auto docs_dir = util::PlatformPaths::GetUserDocumentsSubdirectory("agent");
|
||||
if (docs_dir.ok()) {
|
||||
return (*docs_dir / "agent_chat_history.json").string();
|
||||
}
|
||||
auto temp_dir = util::PlatformPaths::GetTempDirectory();
|
||||
if (temp_dir.ok()) {
|
||||
return (*temp_dir / "agent_chat_history.json").string();
|
||||
}
|
||||
return (std::filesystem::current_path() / "agent_chat_history.json")
|
||||
.string();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* GetPanelTypeName(RightPanelManager::PanelType type) {
|
||||
switch (type) {
|
||||
case RightPanelManager::PanelType::kNone:
|
||||
@@ -89,24 +112,50 @@ float RightPanelManager::GetPanelWidth() const {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float width = 0.0f;
|
||||
switch (active_panel_) {
|
||||
case PanelType::kAgentChat:
|
||||
return agent_chat_width_;
|
||||
width = agent_chat_width_;
|
||||
break;
|
||||
case PanelType::kProposals:
|
||||
return proposals_width_;
|
||||
width = proposals_width_;
|
||||
break;
|
||||
case PanelType::kSettings:
|
||||
return settings_width_;
|
||||
width = settings_width_;
|
||||
break;
|
||||
case PanelType::kHelp:
|
||||
return help_width_;
|
||||
width = help_width_;
|
||||
break;
|
||||
case PanelType::kNotifications:
|
||||
return notifications_width_;
|
||||
width = notifications_width_;
|
||||
break;
|
||||
case PanelType::kProperties:
|
||||
return properties_width_;
|
||||
width = properties_width_;
|
||||
break;
|
||||
case PanelType::kProject:
|
||||
return project_width_;
|
||||
width = project_width_;
|
||||
break;
|
||||
default:
|
||||
return 0.0f;
|
||||
width = 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
ImGuiContext* context = ImGui::GetCurrentContext();
|
||||
if (!context) {
|
||||
return width;
|
||||
}
|
||||
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
if (!viewport) {
|
||||
return width;
|
||||
}
|
||||
|
||||
const float max_width = viewport->WorkSize.x * 0.35f;
|
||||
if (max_width > 0.0f && width > max_width) {
|
||||
width = max_width;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
void RightPanelManager::SetPanelWidth(PanelType type, float width) {
|
||||
@@ -503,7 +552,7 @@ void RightPanelManager::DrawAgentChatPanel() {
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MD_FILE_DOWNLOAD " Save", half_width)) {
|
||||
agent_chat_->SaveHistory(".yaze/agent_chat_history.json");
|
||||
agent_chat_->SaveHistory(ResolveAgentChatHistoryPath());
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
@@ -214,6 +214,14 @@ class PanelManager {
|
||||
|
||||
static constexpr float GetSidebarWidth() { return 48.0f; }
|
||||
static constexpr float GetSidePanelWidth() { return 250.0f; }
|
||||
static float GetSidePanelWidthForViewport(float viewport_width) {
|
||||
float width = GetSidePanelWidth();
|
||||
const float max_width = viewport_width * 0.28f;
|
||||
if (viewport_width > 0.0f && max_width > 0.0f && width > max_width) {
|
||||
width = max_width;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
static constexpr float GetCollapsedSidebarWidth() { return 16.0f; }
|
||||
|
||||
static std::string GetCategoryIcon(const std::string& category);
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "app/gui/core/icons.h"
|
||||
#include "app/gui/core/platform_keys.h"
|
||||
#include "app/gui/core/style.h"
|
||||
#include "app/gui/core/theme_manager.h"
|
||||
#include "app/gui/core/color.h"
|
||||
#include "app/gui/widgets/themed_widgets.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "imgui/imgui_internal.h"
|
||||
@@ -18,52 +20,163 @@
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kDashboardCardBaseWidth = 180.0f;
|
||||
constexpr float kDashboardCardBaseHeight = 120.0f;
|
||||
constexpr float kDashboardCardWidthMaxFactor = 1.35f;
|
||||
constexpr float kDashboardCardHeightMaxFactor = 1.35f;
|
||||
constexpr float kDashboardCardMinWidthFactor = 0.9f;
|
||||
constexpr float kDashboardCardMinHeightFactor = 0.9f;
|
||||
constexpr int kDashboardMaxColumns = 5;
|
||||
constexpr float kDashboardRecentBaseWidth = 150.0f;
|
||||
constexpr float kDashboardRecentBaseHeight = 35.0f;
|
||||
constexpr float kDashboardRecentWidthMaxFactor = 1.3f;
|
||||
constexpr int kDashboardMaxRecentColumns = 4;
|
||||
|
||||
struct FlowLayout {
|
||||
int columns = 1;
|
||||
float item_width = 0.0f;
|
||||
float item_height = 0.0f;
|
||||
float spacing = 0.0f;
|
||||
};
|
||||
|
||||
FlowLayout ComputeFlowLayout(float avail_width, float min_width,
|
||||
float max_width, float min_height,
|
||||
float max_height, float aspect_ratio,
|
||||
float spacing, int max_columns,
|
||||
int item_count) {
|
||||
FlowLayout layout;
|
||||
layout.spacing = spacing;
|
||||
if (avail_width <= 0.0f) {
|
||||
layout.columns = 1;
|
||||
layout.item_width = min_width;
|
||||
layout.item_height = min_height;
|
||||
return layout;
|
||||
}
|
||||
|
||||
const int clamped_max =
|
||||
std::max(1, max_columns > 0 ? max_columns : item_count);
|
||||
const auto width_for_columns = [avail_width, spacing](int columns) {
|
||||
return (avail_width - spacing * static_cast<float>(columns - 1)) /
|
||||
static_cast<float>(columns);
|
||||
};
|
||||
|
||||
int columns =
|
||||
std::max(1, static_cast<int>((avail_width + spacing) /
|
||||
(min_width + spacing)));
|
||||
columns = std::min(columns, clamped_max);
|
||||
if (item_count > 0) {
|
||||
columns = std::min(columns, item_count);
|
||||
}
|
||||
columns = std::max(columns, 1);
|
||||
|
||||
float width = width_for_columns(columns);
|
||||
while (columns < clamped_max && width > max_width) {
|
||||
columns += 1;
|
||||
width = width_for_columns(columns);
|
||||
}
|
||||
|
||||
const float clamped_max_width = std::min(max_width, avail_width);
|
||||
const float clamped_min_width = std::min(min_width, clamped_max_width);
|
||||
layout.item_width = std::clamp(width, clamped_min_width, clamped_max_width);
|
||||
layout.item_height =
|
||||
std::clamp(layout.item_width * aspect_ratio, min_height, max_height);
|
||||
|
||||
layout.columns = columns;
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
ImVec4 ScaleColor(const ImVec4& color, float scale, float alpha) {
|
||||
return ImVec4(color.x * scale, color.y * scale, color.z * scale, alpha);
|
||||
}
|
||||
|
||||
ImVec4 ScaleColor(const ImVec4& color, float scale) {
|
||||
return ScaleColor(color, scale, color.w);
|
||||
}
|
||||
|
||||
ImVec4 WithAlpha(ImVec4 color, float alpha) {
|
||||
color.w = alpha;
|
||||
return color;
|
||||
}
|
||||
|
||||
ImVec4 GetEditorAccentColor(EditorType type, const gui::Theme& theme) {
|
||||
switch (type) {
|
||||
case EditorType::kOverworld:
|
||||
return gui::ConvertColorToImVec4(theme.success);
|
||||
case EditorType::kDungeon:
|
||||
return gui::ConvertColorToImVec4(theme.secondary);
|
||||
case EditorType::kGraphics:
|
||||
return gui::ConvertColorToImVec4(theme.warning);
|
||||
case EditorType::kSprite:
|
||||
return gui::ConvertColorToImVec4(theme.info);
|
||||
case EditorType::kMessage:
|
||||
return gui::ConvertColorToImVec4(theme.primary);
|
||||
case EditorType::kMusic:
|
||||
return gui::ConvertColorToImVec4(theme.accent);
|
||||
case EditorType::kPalette:
|
||||
return gui::ConvertColorToImVec4(theme.error);
|
||||
case EditorType::kScreen:
|
||||
return gui::ConvertColorToImVec4(theme.info);
|
||||
case EditorType::kAssembly:
|
||||
return gui::ConvertColorToImVec4(theme.text_secondary);
|
||||
case EditorType::kHex:
|
||||
return gui::ConvertColorToImVec4(theme.success);
|
||||
case EditorType::kEmulator:
|
||||
return gui::ConvertColorToImVec4(theme.info);
|
||||
case EditorType::kAgent:
|
||||
return gui::ConvertColorToImVec4(theme.accent);
|
||||
case EditorType::kSettings:
|
||||
case EditorType::kUnknown:
|
||||
default:
|
||||
return gui::ConvertColorToImVec4(theme.text_primary);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DashboardPanel::DashboardPanel(EditorManager* editor_manager)
|
||||
: editor_manager_(editor_manager),
|
||||
window_("Dashboard", ICON_MD_DASHBOARD) {
|
||||
window_.SetDefaultSize(950, 650);
|
||||
window_.SetPosition(gui::PanelWindow::Position::Center);
|
||||
|
||||
// Initialize editor list with colors matching EditorSelectionDialog
|
||||
// Use platform-aware shortcut strings (Cmd on macOS, Ctrl elsewhere)
|
||||
const char* ctrl = gui::GetCtrlDisplayName();
|
||||
editors_ = {
|
||||
{"Overworld", ICON_MD_MAP, "Edit overworld maps, entrances, and properties",
|
||||
absl::StrFormat("%s+1", ctrl), EditorType::kOverworld,
|
||||
ImVec4(0.133f, 0.545f, 0.133f, 1.0f)}, // Hyrule green
|
||||
{"Dungeon", ICON_MD_CASTLE, "Design dungeon rooms, layouts, and mechanics",
|
||||
absl::StrFormat("%s+2", ctrl), EditorType::kDungeon,
|
||||
ImVec4(0.502f, 0.0f, 0.502f, 1.0f)}, // Ganon purple
|
||||
{"Graphics", ICON_MD_PALETTE, "Modify tiles, palettes, and graphics sets",
|
||||
absl::StrFormat("%s+3", ctrl), EditorType::kGraphics,
|
||||
ImVec4(1.0f, 0.843f, 0.0f, 1.0f)}, // Triforce gold
|
||||
{"Sprites", ICON_MD_EMOJI_EMOTIONS, "Edit sprite graphics and properties",
|
||||
absl::StrFormat("%s+4", ctrl), EditorType::kSprite,
|
||||
ImVec4(1.0f, 0.647f, 0.0f, 1.0f)}, // Spirit orange
|
||||
{"Overworld", ICON_MD_MAP,
|
||||
"Edit overworld maps, entrances, and properties",
|
||||
absl::StrFormat("%s+1", ctrl), EditorType::kOverworld},
|
||||
{"Dungeon", ICON_MD_CASTLE,
|
||||
"Design dungeon rooms, layouts, and mechanics",
|
||||
absl::StrFormat("%s+2", ctrl), EditorType::kDungeon},
|
||||
{"Graphics", ICON_MD_PALETTE,
|
||||
"Modify tiles, palettes, and graphics sets",
|
||||
absl::StrFormat("%s+3", ctrl), EditorType::kGraphics},
|
||||
{"Sprites", ICON_MD_EMOJI_EMOTIONS,
|
||||
"Edit sprite graphics and properties",
|
||||
absl::StrFormat("%s+4", ctrl), EditorType::kSprite},
|
||||
{"Messages", ICON_MD_CHAT_BUBBLE, "Edit dialogue, signs, and text",
|
||||
absl::StrFormat("%s+5", ctrl), EditorType::kMessage,
|
||||
ImVec4(0.196f, 0.6f, 0.8f, 1.0f)}, // Master sword blue
|
||||
absl::StrFormat("%s+5", ctrl), EditorType::kMessage},
|
||||
{"Music", ICON_MD_MUSIC_NOTE, "Configure music and sound effects",
|
||||
absl::StrFormat("%s+6", ctrl), EditorType::kMusic,
|
||||
ImVec4(0.416f, 0.353f, 0.804f, 1.0f)}, // Shadow purple
|
||||
{"Palettes", ICON_MD_COLOR_LENS, "Edit color palettes and animations",
|
||||
absl::StrFormat("%s+7", ctrl), EditorType::kPalette,
|
||||
ImVec4(0.863f, 0.078f, 0.235f, 1.0f)}, // Heart red
|
||||
absl::StrFormat("%s+6", ctrl), EditorType::kMusic},
|
||||
{"Palettes", ICON_MD_COLOR_LENS,
|
||||
"Edit color palettes and animations",
|
||||
absl::StrFormat("%s+7", ctrl), EditorType::kPalette},
|
||||
{"Screens", ICON_MD_TV, "Edit title screen and ending screens",
|
||||
absl::StrFormat("%s+8", ctrl), EditorType::kScreen,
|
||||
ImVec4(0.4f, 0.8f, 1.0f, 1.0f)}, // Sky blue
|
||||
absl::StrFormat("%s+8", ctrl), EditorType::kScreen},
|
||||
{"Assembly", ICON_MD_CODE, "Write and edit assembly code",
|
||||
absl::StrFormat("%s+9", ctrl), EditorType::kAssembly,
|
||||
ImVec4(0.8f, 0.8f, 0.8f, 1.0f)}, // Silver
|
||||
{"Hex Editor", ICON_MD_DATA_ARRAY, "Direct ROM memory editing and comparison",
|
||||
absl::StrFormat("%s+0", ctrl), EditorType::kHex,
|
||||
ImVec4(0.2f, 0.8f, 0.4f, 1.0f)}, // Matrix green
|
||||
{"Emulator", ICON_MD_VIDEOGAME_ASSET, "Test and debug your ROM in real-time",
|
||||
absl::StrFormat("%s+Shift+E", ctrl), EditorType::kEmulator,
|
||||
ImVec4(0.2f, 0.6f, 1.0f, 1.0f)}, // Emulator blue
|
||||
{"AI Agent", ICON_MD_SMART_TOY, "Configure AI agent, collaboration, and automation",
|
||||
absl::StrFormat("%s+Shift+A", ctrl), EditorType::kAgent,
|
||||
ImVec4(0.8f, 0.4f, 1.0f, 1.0f)}, // Purple/magenta
|
||||
absl::StrFormat("%s+9", ctrl), EditorType::kAssembly},
|
||||
{"Hex Editor", ICON_MD_DATA_ARRAY,
|
||||
"Direct ROM memory editing and comparison",
|
||||
absl::StrFormat("%s+0", ctrl), EditorType::kHex},
|
||||
{"Emulator", ICON_MD_VIDEOGAME_ASSET,
|
||||
"Test and debug your ROM in real-time",
|
||||
absl::StrFormat("%s+Shift+E", ctrl), EditorType::kEmulator},
|
||||
{"AI Agent", ICON_MD_SMART_TOY,
|
||||
"Configure AI agent, collaboration, and automation",
|
||||
absl::StrFormat("%s+Shift+A", ctrl), EditorType::kAgent},
|
||||
};
|
||||
|
||||
LoadRecentEditors();
|
||||
@@ -81,25 +194,27 @@ void DashboardPanel::Draw() {
|
||||
DrawWelcomeHeader();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
|
||||
DrawRecentEditors();
|
||||
if (!recent_editors_.empty()) {
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
DrawEditorGrid();
|
||||
}
|
||||
window_.End();
|
||||
}
|
||||
|
||||
void DashboardPanel::DrawWelcomeHeader() {
|
||||
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
||||
const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
|
||||
const ImVec4 text_secondary = gui::ConvertColorToImVec4(theme.text_secondary);
|
||||
|
||||
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font
|
||||
ImVec4 title_color = ImVec4(1.0f, 0.843f, 0.0f, 1.0f); // Triforce gold
|
||||
ImGui::TextColored(title_color, ICON_MD_EDIT " Select an Editor");
|
||||
ImGui::TextColored(accent, ICON_MD_EDIT " Select an Editor");
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f),
|
||||
ImGui::TextColored(text_secondary,
|
||||
"Choose an editor to begin working on your ROM. "
|
||||
"You can open multiple editors simultaneously.");
|
||||
}
|
||||
@@ -107,29 +222,65 @@ void DashboardPanel::DrawWelcomeHeader() {
|
||||
void DashboardPanel::DrawRecentEditors() {
|
||||
if (recent_editors_.empty()) return;
|
||||
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
|
||||
ICON_MD_HISTORY " Recently Used");
|
||||
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
||||
const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
|
||||
ImGui::TextColored(accent, ICON_MD_HISTORY " Recently Used");
|
||||
ImGui::Spacing();
|
||||
|
||||
for (EditorType type : recent_editors_) {
|
||||
// Find editor info
|
||||
auto it = std::find_if(
|
||||
editors_.begin(), editors_.end(),
|
||||
[type](const EditorInfo& info) { return info.type == type; });
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
const float avail_width = ImGui::GetContentRegionAvail().x;
|
||||
const float min_width = kDashboardRecentBaseWidth;
|
||||
const float max_width = kDashboardRecentBaseWidth *
|
||||
kDashboardRecentWidthMaxFactor;
|
||||
const float height = std::max(kDashboardRecentBaseHeight,
|
||||
ImGui::GetFrameHeight());
|
||||
const float spacing = style.ItemSpacing.x;
|
||||
const bool stack_items = avail_width < min_width * 1.6f;
|
||||
FlowLayout row_layout{};
|
||||
if (stack_items) {
|
||||
row_layout.columns = 1;
|
||||
row_layout.item_width = avail_width;
|
||||
row_layout.item_height = height;
|
||||
row_layout.spacing = spacing;
|
||||
} else {
|
||||
row_layout = ComputeFlowLayout(
|
||||
avail_width, min_width, max_width, height, height,
|
||||
height / std::max(min_width, 1.0f), spacing, kDashboardMaxRecentColumns,
|
||||
static_cast<int>(recent_editors_.size()));
|
||||
}
|
||||
|
||||
if (it != editors_.end()) {
|
||||
// Use editor's theme color for button
|
||||
ImVec4 color = it->color;
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_Button,
|
||||
ImVec4(color.x * 0.5f, color.y * 0.5f, color.z * 0.5f, 0.7f));
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_ButtonHovered,
|
||||
ImVec4(color.x * 0.7f, color.y * 0.7f, color.z * 0.7f, 0.9f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
|
||||
ImGuiTableFlags table_flags = ImGuiTableFlags_SizingFixedFit |
|
||||
ImGuiTableFlags_NoPadOuterX;
|
||||
const ImVec2 cell_padding(row_layout.spacing * 0.5f,
|
||||
style.ItemSpacing.y * 0.4f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cell_padding);
|
||||
if (ImGui::BeginTable("DashboardRecentGrid", row_layout.columns,
|
||||
table_flags)) {
|
||||
for (EditorType type : recent_editors_) {
|
||||
// Find editor info
|
||||
auto it = std::find_if(
|
||||
editors_.begin(), editors_.end(),
|
||||
[type](const EditorInfo& info) { return info.type == type; });
|
||||
|
||||
if (it == editors_.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
const ImVec4 base_color = GetEditorAccentColor(it->type, theme);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,
|
||||
ScaleColor(base_color, 0.5f, 0.7f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||
ScaleColor(base_color, 0.7f, 0.9f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||
WithAlpha(base_color, 1.0f));
|
||||
|
||||
ImVec2 button_size(stack_items ? avail_width : row_layout.item_width,
|
||||
row_layout.item_height > 0.0f ? row_layout.item_height
|
||||
: height);
|
||||
if (ImGui::Button(absl::StrCat(it->icon, " ", it->name).c_str(),
|
||||
ImVec2(150, 35))) {
|
||||
button_size)) {
|
||||
if (editor_manager_) {
|
||||
MarkRecentlyUsed(type);
|
||||
editor_manager_->SwitchToEditor(type);
|
||||
@@ -142,151 +293,212 @@ void DashboardPanel::DrawRecentEditors() {
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s", it->description.c_str());
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
void DashboardPanel::DrawEditorGrid() {
|
||||
ImGui::Text(ICON_MD_APPS " All Editors");
|
||||
ImGui::Spacing();
|
||||
|
||||
const float card_width = 180.0f;
|
||||
const float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||
const float window_width = ImGui::GetContentRegionAvail().x;
|
||||
int columns = static_cast<int>(window_width / (card_width + spacing));
|
||||
columns = std::max(columns, 1);
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
const float avail_width = ImGui::GetContentRegionAvail().x;
|
||||
const float min_width = kDashboardCardBaseWidth * kDashboardCardMinWidthFactor;
|
||||
const float max_width =
|
||||
kDashboardCardBaseWidth * kDashboardCardWidthMaxFactor;
|
||||
const float min_height =
|
||||
std::max(kDashboardCardBaseHeight * kDashboardCardMinHeightFactor,
|
||||
ImGui::GetFrameHeight() * 3.2f);
|
||||
const float max_height =
|
||||
kDashboardCardBaseHeight * kDashboardCardHeightMaxFactor;
|
||||
const float aspect_ratio =
|
||||
kDashboardCardBaseHeight / std::max(kDashboardCardBaseWidth, 1.0f);
|
||||
const float spacing = style.ItemSpacing.x;
|
||||
|
||||
if (ImGui::BeginTable("EditorGrid", columns)) {
|
||||
FlowLayout layout = ComputeFlowLayout(
|
||||
avail_width, min_width, max_width, min_height, max_height,
|
||||
aspect_ratio, spacing, kDashboardMaxColumns,
|
||||
static_cast<int>(editors_.size()));
|
||||
|
||||
ImGuiTableFlags table_flags = ImGuiTableFlags_SizingFixedFit |
|
||||
ImGuiTableFlags_NoPadOuterX;
|
||||
const ImVec2 cell_padding(layout.spacing * 0.5f,
|
||||
style.ItemSpacing.y * 0.5f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cell_padding);
|
||||
if (ImGui::BeginTable("DashboardEditorGrid", layout.columns, table_flags)) {
|
||||
for (size_t i = 0; i < editors_.size(); ++i) {
|
||||
ImGui::TableNextColumn();
|
||||
DrawEditorPanel(editors_[i], static_cast<int>(i));
|
||||
DrawEditorPanel(editors_[i], static_cast<int>(i),
|
||||
ImVec2(layout.item_width, layout.item_height));
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
void DashboardPanel::DrawEditorPanel(const EditorInfo& info, int index) {
|
||||
void DashboardPanel::DrawEditorPanel(const EditorInfo& info, int index,
|
||||
const ImVec2& card_size) {
|
||||
ImGui::PushID(index);
|
||||
|
||||
ImVec2 button_size(180, 120);
|
||||
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
|
||||
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
||||
const ImVec4 base_color = GetEditorAccentColor(info.type, theme);
|
||||
const ImVec4 text_primary = gui::ConvertColorToImVec4(theme.text_primary);
|
||||
const ImVec4 text_secondary = gui::ConvertColorToImVec4(theme.text_secondary);
|
||||
const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
|
||||
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
const float line_height = ImGui::GetTextLineHeight();
|
||||
const float padding_x = std::max(style.FramePadding.x, card_size.x * 0.06f);
|
||||
const float padding_y = std::max(style.FramePadding.y, card_size.y * 0.08f);
|
||||
|
||||
const float footer_height = info.shortcut.empty() ? 0.0f : line_height;
|
||||
const float footer_spacing = info.shortcut.empty() ? 0.0f : style.ItemSpacing.y;
|
||||
const float available_icon_height =
|
||||
card_size.y - padding_y * 2.0f - line_height - footer_height - footer_spacing;
|
||||
const float min_icon_radius = line_height * 0.9f;
|
||||
float max_icon_radius = card_size.y * 0.24f;
|
||||
max_icon_radius = std::max(max_icon_radius, min_icon_radius);
|
||||
const float icon_radius =
|
||||
std::clamp(available_icon_height * 0.5f, min_icon_radius, max_icon_radius);
|
||||
|
||||
const ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
const ImVec2 icon_center(cursor_pos.x + card_size.x * 0.5f,
|
||||
cursor_pos.y + padding_y + icon_radius);
|
||||
float title_y = icon_center.y + icon_radius + style.ItemSpacing.y;
|
||||
const float footer_y = cursor_pos.y + card_size.y - padding_y - footer_height;
|
||||
if (title_y + line_height > footer_y - style.ItemSpacing.y) {
|
||||
title_y = footer_y - line_height - style.ItemSpacing.y;
|
||||
}
|
||||
|
||||
bool is_recent = std::find(recent_editors_.begin(), recent_editors_.end(),
|
||||
info.type) != recent_editors_.end();
|
||||
|
||||
// Create gradient background
|
||||
ImVec4 base_color = info.color;
|
||||
ImU32 color_top = ImGui::GetColorU32(ImVec4(
|
||||
base_color.x * 0.4f, base_color.y * 0.4f, base_color.z * 0.4f, 0.8f));
|
||||
ImU32 color_bottom = ImGui::GetColorU32(ImVec4(
|
||||
base_color.x * 0.2f, base_color.y * 0.2f, base_color.z * 0.2f, 0.9f));
|
||||
ImU32 color_top = ImGui::GetColorU32(ScaleColor(base_color, 0.4f, 0.85f));
|
||||
ImU32 color_bottom =
|
||||
ImGui::GetColorU32(ScaleColor(base_color, 0.2f, 0.9f));
|
||||
|
||||
// Draw gradient card background
|
||||
draw_list->AddRectFilledMultiColor(
|
||||
cursor_pos,
|
||||
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y),
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
color_top, color_top, color_bottom, color_bottom);
|
||||
|
||||
// Colored border
|
||||
ImU32 border_color =
|
||||
is_recent
|
||||
? ImGui::GetColorU32(
|
||||
ImVec4(base_color.x, base_color.y, base_color.z, 1.0f))
|
||||
: ImGui::GetColorU32(ImVec4(base_color.x * 0.6f, base_color.y * 0.6f,
|
||||
base_color.z * 0.6f, 0.7f));
|
||||
WithAlpha(base_color, 1.0f))
|
||||
: ImGui::GetColorU32(ScaleColor(base_color, 0.6f, 0.7f));
|
||||
const float rounding = std::max(style.FrameRounding, card_size.y * 0.05f);
|
||||
const float border_thickness =
|
||||
is_recent ? std::max(2.0f, style.FrameBorderSize + 1.0f)
|
||||
: std::max(1.0f, style.FrameBorderSize);
|
||||
draw_list->AddRect(
|
||||
cursor_pos,
|
||||
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y),
|
||||
border_color, 4.0f, 0, is_recent ? 3.0f : 2.0f);
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
border_color, rounding, 0, border_thickness);
|
||||
|
||||
// Recent indicator badge
|
||||
if (is_recent) {
|
||||
ImVec2 badge_pos(cursor_pos.x + button_size.x - 25, cursor_pos.y + 5);
|
||||
draw_list->AddCircleFilled(badge_pos, 12, ImGui::GetColorU32(base_color),
|
||||
16);
|
||||
ImGui::SetCursorScreenPos(ImVec2(badge_pos.x - 6, badge_pos.y - 8));
|
||||
ImGui::TextColored(ImVec4(1, 1, 1, 1), ICON_MD_STAR);
|
||||
const float badge_radius =
|
||||
std::clamp(line_height * 0.6f, line_height * 0.4f, line_height);
|
||||
ImVec2 badge_pos(cursor_pos.x + card_size.x - padding_x - badge_radius,
|
||||
cursor_pos.y + padding_y + badge_radius);
|
||||
draw_list->AddCircleFilled(badge_pos, badge_radius,
|
||||
ImGui::GetColorU32(base_color), 16);
|
||||
ImVec2 star_size = ImGui::CalcTextSize(ICON_MD_STAR);
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(badge_pos.x - star_size.x * 0.5f,
|
||||
badge_pos.y - star_size.y * 0.5f));
|
||||
ImGui::TextColored(text_primary, ICON_MD_STAR);
|
||||
}
|
||||
|
||||
// Make button transparent (we draw our own background)
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
ImVec4 button_bg = ImGui::GetStyleColorVec4(ImGuiCol_Button);
|
||||
button_bg.w = 0.0f;
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, button_bg);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||
ImVec4(base_color.x * 0.3f, base_color.y * 0.3f,
|
||||
base_color.z * 0.3f, 0.5f));
|
||||
ScaleColor(base_color, 0.3f, 0.5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||
ImVec4(base_color.x * 0.5f, base_color.y * 0.5f,
|
||||
base_color.z * 0.5f, 0.7f));
|
||||
ScaleColor(base_color, 0.5f, 0.7f));
|
||||
|
||||
ImGui::SetCursorScreenPos(cursor_pos);
|
||||
bool clicked =
|
||||
ImGui::Button(absl::StrCat("##", info.name).c_str(), button_size);
|
||||
ImGui::Button(absl::StrCat("##", info.name).c_str(), card_size);
|
||||
bool is_hovered = ImGui::IsItemHovered();
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
// Draw icon with colored background circle
|
||||
ImVec2 icon_center(cursor_pos.x + button_size.x / 2, cursor_pos.y + 30);
|
||||
ImU32 icon_bg = ImGui::GetColorU32(base_color);
|
||||
draw_list->AddCircleFilled(icon_center, 22, icon_bg, 32);
|
||||
draw_list->AddCircleFilled(icon_center, icon_radius, icon_bg, 32);
|
||||
|
||||
// Draw icon
|
||||
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Larger font for icon
|
||||
ImFont* icon_font = ImGui::GetFont();
|
||||
if (ImGui::GetIO().Fonts->Fonts.size() > 2 &&
|
||||
card_size.y >= kDashboardCardBaseHeight) {
|
||||
icon_font = ImGui::GetIO().Fonts->Fonts[2];
|
||||
} else if (ImGui::GetIO().Fonts->Fonts.size() > 1) {
|
||||
icon_font = ImGui::GetIO().Fonts->Fonts[1];
|
||||
}
|
||||
ImGui::PushFont(icon_font);
|
||||
ImVec2 icon_size = ImGui::CalcTextSize(info.icon.c_str());
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2));
|
||||
ImGui::TextColored(ImVec4(1, 1, 1, 1), "%s", info.icon.c_str());
|
||||
ImGui::TextColored(text_primary, "%s", info.icon.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
// Draw name
|
||||
ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + 65));
|
||||
ImGui::PushTextWrapPos(cursor_pos.x + button_size.x - 10);
|
||||
ImVec2 name_size = ImGui::CalcTextSize(info.name.c_str());
|
||||
const float name_wrap_width = card_size.x - padding_x * 2.0f;
|
||||
ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - padding_x);
|
||||
ImVec2 name_size =
|
||||
ImGui::CalcTextSize(info.name.c_str(), nullptr, false, name_wrap_width);
|
||||
ImGui::SetCursorScreenPos(ImVec2(
|
||||
cursor_pos.x + (button_size.x - name_size.x) / 2, cursor_pos.y + 65));
|
||||
cursor_pos.x + (card_size.x - name_size.x) / 2.0f, title_y));
|
||||
ImGui::TextColored(base_color, "%s", info.name.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// Draw shortcut hint if available
|
||||
if (!info.shortcut.empty()) {
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(cursor_pos.x + 10, cursor_pos.y + button_size.y - 20));
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", info.shortcut.c_str());
|
||||
ImVec2(cursor_pos.x + padding_x, footer_y));
|
||||
ImGui::TextColored(text_secondary, "%s", info.shortcut.c_str());
|
||||
}
|
||||
|
||||
// Hover glow effect
|
||||
if (is_hovered) {
|
||||
ImU32 glow_color = ImGui::GetColorU32(
|
||||
ImVec4(base_color.x, base_color.y, base_color.z, 0.2f));
|
||||
ImU32 glow_color =
|
||||
ImGui::GetColorU32(ScaleColor(base_color, 1.0f, 0.18f));
|
||||
draw_list->AddRectFilled(
|
||||
cursor_pos,
|
||||
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y),
|
||||
glow_color, 4.0f);
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
glow_color, rounding);
|
||||
}
|
||||
|
||||
// Enhanced tooltip
|
||||
if (is_hovered) {
|
||||
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always);
|
||||
const float tooltip_width = std::clamp(card_size.x * 1.4f, 240.0f, 340.0f);
|
||||
ImGui::SetNextWindowSize(ImVec2(tooltip_width, 0), ImGuiCond_Always);
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font
|
||||
ImGui::TextColored(base_color, "%s %s", info.icon.c_str(), info.name.c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::Separator();
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 280);
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + tooltip_width - 20.0f);
|
||||
ImGui::TextWrapped("%s", info.description.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
if (!info.shortcut.empty()) {
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(base_color, ICON_MD_KEYBOARD " %s", info.shortcut.c_str());
|
||||
ImGui::TextColored(base_color, ICON_MD_KEYBOARD " %s",
|
||||
info.shortcut.c_str());
|
||||
}
|
||||
if (is_recent) {
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
|
||||
ICON_MD_STAR " Recently used");
|
||||
ImGui::TextColored(accent, ICON_MD_STAR " Recently used");
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
@@ -38,14 +38,14 @@ class DashboardPanel {
|
||||
std::string description;
|
||||
std::string shortcut;
|
||||
EditorType type;
|
||||
ImVec4 color;
|
||||
bool recently_used = false;
|
||||
};
|
||||
|
||||
void DrawWelcomeHeader();
|
||||
void DrawRecentEditors();
|
||||
void DrawEditorGrid();
|
||||
void DrawEditorPanel(const EditorInfo& info, int index);
|
||||
void DrawEditorPanel(const EditorInfo& info, int index,
|
||||
const ImVec2& card_size);
|
||||
|
||||
EditorManager* editor_manager_;
|
||||
gui::PanelWindow window_;
|
||||
|
||||
@@ -9,76 +9,177 @@
|
||||
#include "app/gui/core/icons.h"
|
||||
#include "app/gui/core/platform_keys.h"
|
||||
#include "app/gui/core/style.h"
|
||||
#include "app/gui/core/theme_manager.h"
|
||||
#include "app/gui/core/color.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "util/file_util.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kEditorSelectBaseFontSize = 16.0f;
|
||||
constexpr float kEditorSelectCardBaseWidth = 180.0f;
|
||||
constexpr float kEditorSelectCardBaseHeight = 120.0f;
|
||||
constexpr float kEditorSelectCardWidthMaxFactor = 1.35f;
|
||||
constexpr float kEditorSelectCardHeightMaxFactor = 1.35f;
|
||||
constexpr float kEditorSelectRecentBaseWidth = 150.0f;
|
||||
constexpr float kEditorSelectRecentBaseHeight = 35.0f;
|
||||
constexpr float kEditorSelectRecentWidthMaxFactor = 1.3f;
|
||||
|
||||
struct GridLayout {
|
||||
int columns = 1;
|
||||
float item_width = 0.0f;
|
||||
float item_height = 0.0f;
|
||||
float spacing = 0.0f;
|
||||
float row_start_x = 0.0f;
|
||||
};
|
||||
|
||||
float GetEditorSelectScale() {
|
||||
const float font_size = ImGui::GetFontSize();
|
||||
if (font_size <= 0.0f) {
|
||||
return 1.0f;
|
||||
}
|
||||
return font_size / kEditorSelectBaseFontSize;
|
||||
}
|
||||
|
||||
GridLayout ComputeGridLayout(float avail_width, float min_width,
|
||||
float max_width, float min_height,
|
||||
float max_height, float preferred_width,
|
||||
float aspect_ratio, float spacing) {
|
||||
GridLayout layout;
|
||||
layout.spacing = spacing;
|
||||
const auto width_for_columns = [avail_width, spacing](int columns) {
|
||||
return (avail_width - spacing * static_cast<float>(columns - 1)) /
|
||||
static_cast<float>(columns);
|
||||
};
|
||||
|
||||
layout.columns =
|
||||
std::max(1, static_cast<int>((avail_width + spacing) /
|
||||
(preferred_width + spacing)));
|
||||
|
||||
layout.item_width = width_for_columns(layout.columns);
|
||||
while (layout.columns > 1 && layout.item_width < min_width) {
|
||||
layout.columns -= 1;
|
||||
layout.item_width = width_for_columns(layout.columns);
|
||||
}
|
||||
|
||||
layout.item_width = std::min(layout.item_width, max_width);
|
||||
layout.item_width = std::min(layout.item_width, avail_width);
|
||||
layout.item_height =
|
||||
std::clamp(layout.item_width * aspect_ratio, min_height, max_height);
|
||||
|
||||
const float row_width =
|
||||
layout.item_width * static_cast<float>(layout.columns) +
|
||||
spacing * static_cast<float>(layout.columns - 1);
|
||||
layout.row_start_x = ImGui::GetCursorPosX();
|
||||
if (row_width < avail_width) {
|
||||
layout.row_start_x += (avail_width - row_width) * 0.5f;
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
ImVec4 ScaleColor(const ImVec4& color, float scale, float alpha) {
|
||||
return ImVec4(color.x * scale, color.y * scale, color.z * scale, alpha);
|
||||
}
|
||||
|
||||
ImVec4 ScaleColor(const ImVec4& color, float scale) {
|
||||
return ScaleColor(color, scale, color.w);
|
||||
}
|
||||
|
||||
ImVec4 WithAlpha(ImVec4 color, float alpha) {
|
||||
color.w = alpha;
|
||||
return color;
|
||||
}
|
||||
|
||||
ImVec4 GetEditorAccentColor(EditorType type, const gui::Theme& theme) {
|
||||
switch (type) {
|
||||
case EditorType::kOverworld:
|
||||
return gui::ConvertColorToImVec4(theme.success);
|
||||
case EditorType::kDungeon:
|
||||
return gui::ConvertColorToImVec4(theme.secondary);
|
||||
case EditorType::kGraphics:
|
||||
return gui::ConvertColorToImVec4(theme.warning);
|
||||
case EditorType::kSprite:
|
||||
return gui::ConvertColorToImVec4(theme.info);
|
||||
case EditorType::kMessage:
|
||||
return gui::ConvertColorToImVec4(theme.primary);
|
||||
case EditorType::kMusic:
|
||||
return gui::ConvertColorToImVec4(theme.accent);
|
||||
case EditorType::kPalette:
|
||||
return gui::ConvertColorToImVec4(theme.error);
|
||||
case EditorType::kScreen:
|
||||
return gui::ConvertColorToImVec4(theme.info);
|
||||
case EditorType::kAssembly:
|
||||
return gui::ConvertColorToImVec4(theme.text_secondary);
|
||||
case EditorType::kHex:
|
||||
return gui::ConvertColorToImVec4(theme.success);
|
||||
case EditorType::kEmulator:
|
||||
return gui::ConvertColorToImVec4(theme.info);
|
||||
case EditorType::kAgent:
|
||||
return gui::ConvertColorToImVec4(theme.accent);
|
||||
case EditorType::kSettings:
|
||||
case EditorType::kUnknown:
|
||||
default:
|
||||
return gui::ConvertColorToImVec4(theme.text_primary);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EditorSelectionDialog::EditorSelectionDialog() {
|
||||
// Initialize editor metadata with distinct colors
|
||||
// Use platform-aware shortcut strings (Cmd on macOS, Ctrl elsewhere)
|
||||
const char* ctrl = gui::GetCtrlDisplayName();
|
||||
editors_ = {
|
||||
{EditorType::kOverworld, "Overworld", ICON_MD_MAP,
|
||||
"Edit overworld maps, entrances, and properties",
|
||||
absl::StrFormat("%s+1", ctrl), false, true,
|
||||
ImVec4(0.133f, 0.545f, 0.133f, 1.0f)}, // Hyrule green
|
||||
absl::StrFormat("%s+1", ctrl), false, true},
|
||||
|
||||
{EditorType::kDungeon, "Dungeon", ICON_MD_CASTLE,
|
||||
"Design dungeon rooms, layouts, and mechanics",
|
||||
absl::StrFormat("%s+2", ctrl), false, true,
|
||||
ImVec4(0.502f, 0.0f, 0.502f, 1.0f)}, // Ganon purple
|
||||
absl::StrFormat("%s+2", ctrl), false, true},
|
||||
|
||||
{EditorType::kGraphics, "Graphics", ICON_MD_PALETTE,
|
||||
"Modify tiles, palettes, and graphics sets",
|
||||
absl::StrFormat("%s+3", ctrl), false, true,
|
||||
ImVec4(1.0f, 0.843f, 0.0f, 1.0f)}, // Triforce gold
|
||||
absl::StrFormat("%s+3", ctrl), false, true},
|
||||
|
||||
{EditorType::kSprite, "Sprites", ICON_MD_EMOJI_EMOTIONS,
|
||||
"Edit sprite graphics and properties",
|
||||
absl::StrFormat("%s+4", ctrl), false, true,
|
||||
ImVec4(1.0f, 0.647f, 0.0f, 1.0f)}, // Spirit orange
|
||||
absl::StrFormat("%s+4", ctrl), false, true},
|
||||
|
||||
{EditorType::kMessage, "Messages", ICON_MD_CHAT_BUBBLE,
|
||||
"Edit dialogue, signs, and text",
|
||||
absl::StrFormat("%s+5", ctrl), false, true,
|
||||
ImVec4(0.196f, 0.6f, 0.8f, 1.0f)}, // Master sword blue
|
||||
absl::StrFormat("%s+5", ctrl), false, true},
|
||||
|
||||
{EditorType::kMusic, "Music", ICON_MD_MUSIC_NOTE,
|
||||
"Configure music and sound effects",
|
||||
absl::StrFormat("%s+6", ctrl), false, true,
|
||||
ImVec4(0.416f, 0.353f, 0.804f, 1.0f)}, // Shadow purple
|
||||
absl::StrFormat("%s+6", ctrl), false, true},
|
||||
|
||||
{EditorType::kPalette, "Palettes", ICON_MD_COLOR_LENS,
|
||||
"Edit color palettes and animations",
|
||||
absl::StrFormat("%s+7", ctrl), false, true,
|
||||
ImVec4(0.863f, 0.078f, 0.235f, 1.0f)}, // Heart red
|
||||
absl::StrFormat("%s+7", ctrl), false, true},
|
||||
|
||||
{EditorType::kScreen, "Screens", ICON_MD_TV,
|
||||
"Edit title screen and ending screens",
|
||||
absl::StrFormat("%s+8", ctrl), false, true,
|
||||
ImVec4(0.4f, 0.8f, 1.0f, 1.0f)}, // Sky blue
|
||||
absl::StrFormat("%s+8", ctrl), false, true},
|
||||
|
||||
{EditorType::kAssembly, "Assembly", ICON_MD_CODE,
|
||||
"Write and edit assembly code",
|
||||
absl::StrFormat("%s+9", ctrl), false, false,
|
||||
ImVec4(0.8f, 0.8f, 0.8f, 1.0f)}, // Silver
|
||||
absl::StrFormat("%s+9", ctrl), false, false},
|
||||
|
||||
{EditorType::kHex, "Hex Editor", ICON_MD_DATA_ARRAY,
|
||||
"Direct ROM memory editing and comparison",
|
||||
absl::StrFormat("%s+0", ctrl), false, true,
|
||||
ImVec4(0.2f, 0.8f, 0.4f, 1.0f)}, // Matrix green
|
||||
absl::StrFormat("%s+0", ctrl), false, true},
|
||||
|
||||
{EditorType::kEmulator, "Emulator", ICON_MD_VIDEOGAME_ASSET,
|
||||
"Test and debug your ROM in real-time with live debugging",
|
||||
absl::StrFormat("%s+Shift+E", ctrl), false, true,
|
||||
ImVec4(0.2f, 0.6f, 1.0f, 1.0f)}, // Emulator blue
|
||||
absl::StrFormat("%s+Shift+E", ctrl), false, true},
|
||||
|
||||
{EditorType::kAgent, "AI Agent", ICON_MD_SMART_TOY,
|
||||
"Configure AI agent, collaboration, and automation",
|
||||
absl::StrFormat("%s+Shift+A", ctrl), false, false,
|
||||
ImVec4(0.8f, 0.4f, 1.0f, 1.0f)}, // Purple/magenta
|
||||
absl::StrFormat("%s+Shift+A", ctrl), false, false},
|
||||
};
|
||||
|
||||
LoadRecentEditors();
|
||||
@@ -125,33 +226,55 @@ bool EditorSelectionDialog::Show(bool* p_open) {
|
||||
ImGui::Text(ICON_MD_APPS " All Editors");
|
||||
ImGui::Spacing();
|
||||
|
||||
float button_size = 200.0f;
|
||||
int columns =
|
||||
static_cast<int>(ImGui::GetContentRegionAvail().x / button_size);
|
||||
columns = std::max(columns, 1);
|
||||
const float scale = GetEditorSelectScale();
|
||||
const float min_width = kEditorSelectCardBaseWidth * scale;
|
||||
const float max_width =
|
||||
kEditorSelectCardBaseWidth * kEditorSelectCardWidthMaxFactor * scale;
|
||||
const float min_height = kEditorSelectCardBaseHeight * scale;
|
||||
const float max_height =
|
||||
kEditorSelectCardBaseHeight * kEditorSelectCardHeightMaxFactor * scale;
|
||||
const float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||
const float aspect_ratio = min_height / std::max(min_width, 1.0f);
|
||||
GridLayout layout = ComputeGridLayout(ImGui::GetContentRegionAvail().x,
|
||||
min_width, max_width, min_height,
|
||||
max_height, min_width, aspect_ratio,
|
||||
spacing);
|
||||
|
||||
if (ImGui::BeginTable("##EditorGrid", columns, ImGuiTableFlags_None)) {
|
||||
for (size_t i = 0; i < editors_.size(); ++i) {
|
||||
ImGui::TableNextColumn();
|
||||
int column = 0;
|
||||
for (size_t i = 0; i < editors_.size(); ++i) {
|
||||
if (column == 0) {
|
||||
ImGui::SetCursorPosX(layout.row_start_x);
|
||||
}
|
||||
|
||||
EditorType prev_selection = selected_editor_;
|
||||
DrawEditorPanel(editors_[i], static_cast<int>(i));
|
||||
EditorType prev_selection = selected_editor_;
|
||||
DrawEditorPanel(editors_[i], static_cast<int>(i),
|
||||
ImVec2(layout.item_width, layout.item_height));
|
||||
|
||||
// Check if an editor was just selected
|
||||
if (selected_editor_ != prev_selection) {
|
||||
editor_selected = true;
|
||||
MarkRecentlyUsed(selected_editor_);
|
||||
if (selection_callback_) {
|
||||
selection_callback_(selected_editor_);
|
||||
}
|
||||
// Auto-dismiss after selection
|
||||
is_open_ = false;
|
||||
if (p_open) {
|
||||
*p_open = false;
|
||||
}
|
||||
// Check if an editor was just selected
|
||||
if (selected_editor_ != prev_selection) {
|
||||
editor_selected = true;
|
||||
MarkRecentlyUsed(selected_editor_);
|
||||
if (selection_callback_) {
|
||||
selection_callback_(selected_editor_);
|
||||
}
|
||||
// Auto-dismiss after selection
|
||||
is_open_ = false;
|
||||
if (p_open) {
|
||||
*p_open = false;
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
|
||||
column += 1;
|
||||
if (column < layout.columns) {
|
||||
ImGui::SameLine(0.0f, layout.spacing);
|
||||
} else {
|
||||
column = 0;
|
||||
ImGui::Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
if (column != 0) {
|
||||
ImGui::NewLine();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
@@ -168,29 +291,38 @@ bool EditorSelectionDialog::Show(bool* p_open) {
|
||||
}
|
||||
|
||||
void EditorSelectionDialog::DrawWelcomeHeader() {
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 header_start = ImGui::GetCursorScreenPos();
|
||||
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
||||
const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
|
||||
const ImVec4 text_secondary = gui::ConvertColorToImVec4(theme.text_secondary);
|
||||
|
||||
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font
|
||||
|
||||
// Colorful gradient title
|
||||
ImVec4 title_color = ImVec4(1.0f, 0.843f, 0.0f, 1.0f); // Triforce gold
|
||||
ImGui::TextColored(title_color, ICON_MD_EDIT " Select an Editor");
|
||||
ImGui::TextColored(accent, ICON_MD_EDIT " Select an Editor");
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
// Subtitle with gradient separator
|
||||
ImVec2 subtitle_pos = ImGui::GetCursorScreenPos();
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f),
|
||||
ImGui::TextColored(text_secondary,
|
||||
"Choose an editor to begin working on your ROM. "
|
||||
"You can open multiple editors simultaneously.");
|
||||
}
|
||||
|
||||
void EditorSelectionDialog::DrawQuickAccessButtons() {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
|
||||
ICON_MD_HISTORY " Recently Used");
|
||||
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
||||
const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
|
||||
ImGui::TextColored(accent, ICON_MD_HISTORY " Recently Used");
|
||||
ImGui::Spacing();
|
||||
|
||||
const float scale = GetEditorSelectScale();
|
||||
const float min_width = kEditorSelectRecentBaseWidth * scale;
|
||||
const float max_width =
|
||||
kEditorSelectRecentBaseWidth * kEditorSelectRecentWidthMaxFactor * scale;
|
||||
const float height = kEditorSelectRecentBaseHeight * scale;
|
||||
const float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||
GridLayout layout = ComputeGridLayout(
|
||||
ImGui::GetContentRegionAvail().x, min_width, max_width, height, height,
|
||||
min_width, height / std::max(min_width, 1.0f), spacing);
|
||||
|
||||
int column = 0;
|
||||
for (EditorType type : recent_editors_) {
|
||||
// Find editor info
|
||||
auto it = std::find_if(
|
||||
@@ -198,18 +330,21 @@ void EditorSelectionDialog::DrawQuickAccessButtons() {
|
||||
[type](const EditorInfo& info) { return info.type == type; });
|
||||
|
||||
if (it != editors_.end()) {
|
||||
// Use editor's theme color for button
|
||||
ImVec4 color = it->color;
|
||||
if (column == 0) {
|
||||
ImGui::SetCursorPosX(layout.row_start_x);
|
||||
}
|
||||
|
||||
const ImVec4 base_color = GetEditorAccentColor(it->type, theme);
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_Button,
|
||||
ImVec4(color.x * 0.5f, color.y * 0.5f, color.z * 0.5f, 0.7f));
|
||||
ScaleColor(base_color, 0.5f, 0.7f));
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_ButtonHovered,
|
||||
ImVec4(color.x * 0.7f, color.y * 0.7f, color.z * 0.7f, 0.9f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
|
||||
ScaleColor(base_color, 0.7f, 0.9f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, WithAlpha(base_color, 1.0f));
|
||||
|
||||
if (ImGui::Button(absl::StrCat(it->icon, " ", it->name).c_str(),
|
||||
ImVec2(150, 35))) {
|
||||
ImVec2(layout.item_width, layout.item_height))) {
|
||||
selected_editor_ = type;
|
||||
}
|
||||
|
||||
@@ -219,123 +354,166 @@ void EditorSelectionDialog::DrawQuickAccessButtons() {
|
||||
ImGui::SetTooltip("%s", it->description);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
column += 1;
|
||||
if (column < layout.columns) {
|
||||
ImGui::SameLine(0.0f, layout.spacing);
|
||||
} else {
|
||||
column = 0;
|
||||
ImGui::Spacing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
if (column != 0) {
|
||||
ImGui::NewLine();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSelectionDialog::DrawEditorPanel(const EditorInfo& info, int index) {
|
||||
void EditorSelectionDialog::DrawEditorPanel(const EditorInfo& info, int index,
|
||||
const ImVec2& card_size) {
|
||||
ImGui::PushID(index);
|
||||
|
||||
ImVec2 button_size(180, 120);
|
||||
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
|
||||
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
||||
const ImVec4 base_color = GetEditorAccentColor(info.type, theme);
|
||||
const ImVec4 text_primary = gui::ConvertColorToImVec4(theme.text_primary);
|
||||
const ImVec4 text_secondary = gui::ConvertColorToImVec4(theme.text_secondary);
|
||||
const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
|
||||
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
const float line_height = ImGui::GetTextLineHeight();
|
||||
const float padding_x = std::max(style.FramePadding.x, card_size.x * 0.06f);
|
||||
const float padding_y = std::max(style.FramePadding.y, card_size.y * 0.08f);
|
||||
|
||||
const float footer_height = info.shortcut.empty() ? 0.0f : line_height;
|
||||
const float footer_spacing = info.shortcut.empty() ? 0.0f : style.ItemSpacing.y;
|
||||
const float available_icon_height =
|
||||
card_size.y - padding_y * 2.0f - line_height - footer_height - footer_spacing;
|
||||
const float min_icon_radius = line_height * 0.9f;
|
||||
float max_icon_radius = card_size.y * 0.24f;
|
||||
max_icon_radius = std::max(max_icon_radius, min_icon_radius);
|
||||
const float icon_radius =
|
||||
std::clamp(available_icon_height * 0.5f, min_icon_radius, max_icon_radius);
|
||||
|
||||
const ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
const ImVec2 icon_center(cursor_pos.x + card_size.x * 0.5f,
|
||||
cursor_pos.y + padding_y + icon_radius);
|
||||
float title_y = icon_center.y + icon_radius + style.ItemSpacing.y;
|
||||
const float footer_y = cursor_pos.y + card_size.y - padding_y - footer_height;
|
||||
if (title_y + line_height > footer_y - style.ItemSpacing.y) {
|
||||
title_y = footer_y - line_height - style.ItemSpacing.y;
|
||||
}
|
||||
|
||||
// Panel styling with gradients
|
||||
bool is_recent = std::find(recent_editors_.begin(), recent_editors_.end(),
|
||||
info.type) != recent_editors_.end();
|
||||
|
||||
// Create gradient background
|
||||
ImVec4 base_color = info.color;
|
||||
ImU32 color_top = ImGui::GetColorU32(ImVec4(
|
||||
base_color.x * 0.4f, base_color.y * 0.4f, base_color.z * 0.4f, 0.8f));
|
||||
ImU32 color_bottom = ImGui::GetColorU32(ImVec4(
|
||||
base_color.x * 0.2f, base_color.y * 0.2f, base_color.z * 0.2f, 0.9f));
|
||||
ImU32 color_top = ImGui::GetColorU32(ScaleColor(base_color, 0.4f, 0.85f));
|
||||
ImU32 color_bottom =
|
||||
ImGui::GetColorU32(ScaleColor(base_color, 0.2f, 0.9f));
|
||||
|
||||
// Draw gradient card background
|
||||
draw_list->AddRectFilledMultiColor(
|
||||
cursor_pos,
|
||||
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y),
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
color_top, color_top, color_bottom, color_bottom);
|
||||
|
||||
// Colored border
|
||||
ImU32 border_color =
|
||||
is_recent
|
||||
? ImGui::GetColorU32(
|
||||
ImVec4(base_color.x, base_color.y, base_color.z, 1.0f))
|
||||
: ImGui::GetColorU32(ImVec4(base_color.x * 0.6f, base_color.y * 0.6f,
|
||||
base_color.z * 0.6f, 0.7f));
|
||||
WithAlpha(base_color, 1.0f))
|
||||
: ImGui::GetColorU32(ScaleColor(base_color, 0.6f, 0.7f));
|
||||
const float rounding = std::max(style.FrameRounding, card_size.y * 0.05f);
|
||||
const float border_thickness =
|
||||
is_recent ? std::max(2.0f, style.FrameBorderSize + 1.0f)
|
||||
: std::max(1.0f, style.FrameBorderSize);
|
||||
draw_list->AddRect(
|
||||
cursor_pos,
|
||||
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y),
|
||||
border_color, 4.0f, 0, is_recent ? 3.0f : 2.0f);
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
border_color, rounding, 0, border_thickness);
|
||||
|
||||
// Recent indicator badge
|
||||
if (is_recent) {
|
||||
ImVec2 badge_pos(cursor_pos.x + button_size.x - 25, cursor_pos.y + 5);
|
||||
draw_list->AddCircleFilled(badge_pos, 12, ImGui::GetColorU32(base_color),
|
||||
16);
|
||||
ImGui::SetCursorScreenPos(ImVec2(badge_pos.x - 6, badge_pos.y - 8));
|
||||
ImGui::TextColored(ImVec4(1, 1, 1, 1), ICON_MD_STAR);
|
||||
const float badge_radius =
|
||||
std::clamp(line_height * 0.6f, line_height * 0.4f, line_height);
|
||||
ImVec2 badge_pos(cursor_pos.x + card_size.x - padding_x - badge_radius,
|
||||
cursor_pos.y + padding_y + badge_radius);
|
||||
draw_list->AddCircleFilled(badge_pos, badge_radius,
|
||||
ImGui::GetColorU32(base_color), 16);
|
||||
ImVec2 star_size = ImGui::CalcTextSize(ICON_MD_STAR);
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(badge_pos.x - star_size.x * 0.5f,
|
||||
badge_pos.y - star_size.y * 0.5f));
|
||||
ImGui::TextColored(text_primary, ICON_MD_STAR);
|
||||
}
|
||||
|
||||
// Make button transparent (we draw our own background)
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
ImVec4 button_bg = ImGui::GetStyleColorVec4(ImGuiCol_Button);
|
||||
button_bg.w = 0.0f;
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, button_bg);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||
ImVec4(base_color.x * 0.3f, base_color.y * 0.3f,
|
||||
base_color.z * 0.3f, 0.5f));
|
||||
ScaleColor(base_color, 0.3f, 0.5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||
ImVec4(base_color.x * 0.5f, base_color.y * 0.5f,
|
||||
base_color.z * 0.5f, 0.7f));
|
||||
ScaleColor(base_color, 0.5f, 0.7f));
|
||||
|
||||
ImGui::SetCursorScreenPos(cursor_pos);
|
||||
bool clicked =
|
||||
ImGui::Button(absl::StrCat("##", info.name).c_str(), button_size);
|
||||
ImGui::Button(absl::StrCat("##", info.name).c_str(), card_size);
|
||||
bool is_hovered = ImGui::IsItemHovered();
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
// Draw icon with colored background circle
|
||||
ImVec2 icon_center(cursor_pos.x + button_size.x / 2, cursor_pos.y + 30);
|
||||
ImU32 icon_bg = ImGui::GetColorU32(base_color);
|
||||
draw_list->AddCircleFilled(icon_center, 22, icon_bg, 32);
|
||||
draw_list->AddCircleFilled(icon_center, icon_radius, icon_bg, 32);
|
||||
|
||||
// Draw icon
|
||||
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Larger font for icon
|
||||
ImVec2 icon_size = ImGui::CalcTextSize(info.icon);
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2));
|
||||
ImGui::TextColored(ImVec4(1, 1, 1, 1), "%s", info.icon);
|
||||
ImGui::TextColored(text_primary, "%s", info.icon);
|
||||
ImGui::PopFont();
|
||||
|
||||
// Draw name
|
||||
ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + 65));
|
||||
ImGui::PushTextWrapPos(cursor_pos.x + button_size.x - 10);
|
||||
ImVec2 name_size = ImGui::CalcTextSize(info.name);
|
||||
const float name_wrap_width = card_size.x - padding_x * 2.0f;
|
||||
ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - padding_x);
|
||||
ImVec2 name_size =
|
||||
ImGui::CalcTextSize(info.name, nullptr, false, name_wrap_width);
|
||||
ImGui::SetCursorScreenPos(ImVec2(
|
||||
cursor_pos.x + (button_size.x - name_size.x) / 2, cursor_pos.y + 65));
|
||||
cursor_pos.x + (card_size.x - name_size.x) / 2.0f, title_y));
|
||||
ImGui::TextColored(base_color, "%s", info.name);
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// Draw shortcut hint if available
|
||||
if (!info.shortcut.empty()) {
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(cursor_pos.x + 10, cursor_pos.y + button_size.y - 20));
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", info.shortcut.c_str());
|
||||
ImVec2(cursor_pos.x + padding_x, footer_y));
|
||||
ImGui::TextColored(text_secondary, "%s", info.shortcut.c_str());
|
||||
}
|
||||
|
||||
// Hover glow effect
|
||||
if (is_hovered) {
|
||||
ImU32 glow_color = ImGui::GetColorU32(
|
||||
ImVec4(base_color.x, base_color.y, base_color.z, 0.2f));
|
||||
ImU32 glow_color =
|
||||
ImGui::GetColorU32(ScaleColor(base_color, 1.0f, 0.18f));
|
||||
draw_list->AddRectFilled(
|
||||
cursor_pos,
|
||||
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y),
|
||||
glow_color, 4.0f);
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
glow_color, rounding);
|
||||
}
|
||||
|
||||
// Enhanced tooltip with fixed sizing
|
||||
if (is_hovered) {
|
||||
// Force tooltip to have a fixed max width to prevent flickering
|
||||
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always);
|
||||
const float tooltip_width = std::clamp(card_size.x * 1.4f, 240.0f, 340.0f);
|
||||
ImGui::SetNextWindowSize(ImVec2(tooltip_width, 0), ImGuiCond_Always);
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font
|
||||
ImGui::TextColored(base_color, "%s %s", info.icon, info.name);
|
||||
ImGui::PopFont();
|
||||
ImGui::Separator();
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 280);
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + tooltip_width - 20.0f);
|
||||
ImGui::TextWrapped("%s", info.description);
|
||||
ImGui::PopTextWrapPos();
|
||||
if (!info.shortcut.empty()) {
|
||||
@@ -344,8 +522,7 @@ void EditorSelectionDialog::DrawEditorPanel(const EditorInfo& info, int index) {
|
||||
}
|
||||
if (is_recent) {
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
|
||||
ICON_MD_STAR " Recently used");
|
||||
ImGui::TextColored(accent, ICON_MD_STAR " Recently used");
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ struct EditorInfo {
|
||||
std::string shortcut; // Platform-aware shortcut string
|
||||
bool recently_used = false;
|
||||
bool requires_rom = true;
|
||||
ImVec4 color = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); // Theme color for this editor
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -94,7 +93,8 @@ class EditorSelectionDialog {
|
||||
}
|
||||
|
||||
private:
|
||||
void DrawEditorPanel(const EditorInfo& info, int index);
|
||||
void DrawEditorPanel(const EditorInfo& info, int index,
|
||||
const ImVec2& card_size);
|
||||
void DrawWelcomeHeader();
|
||||
void DrawQuickAccessButtons();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "app/gui/core/icons.h"
|
||||
#include "app/gui/core/style.h"
|
||||
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/hex.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -351,7 +352,8 @@ void PopupManager::DrawNewProjectPopup() {
|
||||
|
||||
if (Button(absl::StrFormat("%s ROM File", ICON_MD_VIDEOGAME_ASSET).c_str(),
|
||||
gui::kDefaultModalSize)) {
|
||||
rom_filename = util::FileDialogWrapper::ShowOpenFileDialog();
|
||||
rom_filename = util::FileDialogWrapper::ShowOpenFileDialog(
|
||||
util::MakeRomFileDialogOptions(false));
|
||||
}
|
||||
SameLine();
|
||||
Text("%s", rom_filename.empty() ? "(Not set)" : rom_filename.c_str());
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
#if defined(__APPLE__) && TARGET_OS_IOS == 1
|
||||
#include "app/platform/ios/ios_platform_state.h"
|
||||
#endif
|
||||
#include "app/editor/editor.h"
|
||||
#include "app/editor/editor_manager.h"
|
||||
#include "app/editor/system/editor_registry.h"
|
||||
@@ -74,10 +80,13 @@ UICoordinator::UICoordinator(
|
||||
toast_manager_.Show(
|
||||
absl::StrFormat("Failed to load ROM: %s", status.message()),
|
||||
ToastType::kError);
|
||||
} else {
|
||||
}
|
||||
#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
|
||||
else {
|
||||
// Transition to Dashboard on successful ROM load
|
||||
SetStartupSurface(StartupSurface::kDashboard);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
});
|
||||
@@ -188,6 +197,147 @@ void UICoordinator::DrawAllUI() {
|
||||
// Draw popups and toasts
|
||||
DrawAllPopups();
|
||||
toast_manager_.Draw();
|
||||
DrawMobileNavigation();
|
||||
}
|
||||
|
||||
bool UICoordinator::IsCompactLayout() const {
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
if (!viewport) {
|
||||
return false;
|
||||
}
|
||||
const float width = viewport->WorkSize.x;
|
||||
#if defined(__APPLE__) && TARGET_OS_IOS == 1
|
||||
return true;
|
||||
#else
|
||||
return width < 900.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
void UICoordinator::DrawMobileNavigation() {
|
||||
if (!IsCompactLayout()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
if (!viewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImVec2 safe = style.DisplaySafeAreaPadding;
|
||||
#if defined(__APPLE__) && TARGET_OS_IOS == 1
|
||||
const auto safe_area = ::yaze::platform::ios::GetSafeAreaInsets();
|
||||
if (safe_area.left != 0.0f || safe_area.right != 0.0f ||
|
||||
safe_area.top != 0.0f || safe_area.bottom != 0.0f) {
|
||||
safe = ImVec2(safe_area.right, safe_area.bottom);
|
||||
}
|
||||
#endif
|
||||
const float button_size = std::max(44.0f, ImGui::GetFontSize() * 2.1f);
|
||||
const float padding = style.WindowPadding.x;
|
||||
const ImVec2 pos(viewport->WorkPos.x + viewport->WorkSize.x - safe.x - padding -
|
||||
button_size,
|
||||
viewport->WorkPos.y + viewport->WorkSize.y - safe.y - padding -
|
||||
button_size);
|
||||
|
||||
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
||||
const ImVec4 button_color = gui::ConvertColorToImVec4(theme.button);
|
||||
const ImVec4 button_hovered = gui::ConvertColorToImVec4(theme.button_hovered);
|
||||
const ImVec4 button_active = gui::ConvertColorToImVec4(theme.button_active);
|
||||
const ImVec4 button_text = gui::ConvertColorToImVec4(theme.accent);
|
||||
|
||||
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(button_size, button_size), ImGuiCond_Always);
|
||||
|
||||
ImGuiWindowFlags flags =
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoBackground;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
if (ImGui::Begin("##MobileNavButton", nullptr, flags)) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, button_color);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_hovered);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_active);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, button_text);
|
||||
|
||||
if (ImGui::Button(ICON_MD_APPS, ImVec2(button_size, button_size))) {
|
||||
ImGui::OpenPopup("##MobileNavPopup");
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Navigation");
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(4);
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg,
|
||||
gui::ConvertColorToImVec4(theme.surface));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f, 10.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 6.0f));
|
||||
|
||||
if (ImGui::BeginPopup("##MobileNavPopup")) {
|
||||
bool has_rom = false;
|
||||
if (editor_manager_) {
|
||||
auto* current_rom = editor_manager_->GetCurrentRom();
|
||||
has_rom = current_rom && current_rom->is_loaded();
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem(ICON_MD_FOLDER_OPEN " Open ROM")) {
|
||||
if (editor_manager_) {
|
||||
auto status = editor_manager_->LoadRom();
|
||||
if (!status.ok()) {
|
||||
toast_manager_.Show(
|
||||
absl::StrFormat("Failed to load ROM: %s", status.message()),
|
||||
ToastType::kError);
|
||||
}
|
||||
#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
|
||||
else {
|
||||
SetStartupSurface(StartupSurface::kDashboard);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (has_rom) {
|
||||
if (ImGui::MenuItem(ICON_MD_DASHBOARD " Dashboard", nullptr,
|
||||
current_startup_surface_ ==
|
||||
StartupSurface::kDashboard)) {
|
||||
SetStartupSurface(StartupSurface::kDashboard);
|
||||
}
|
||||
if (ImGui::MenuItem(
|
||||
ICON_MD_EDIT " Editor", nullptr,
|
||||
current_startup_surface_ == StartupSurface::kEditor)) {
|
||||
SetStartupSurface(StartupSurface::kEditor);
|
||||
}
|
||||
}
|
||||
|
||||
const bool sidebar_visible = panel_manager_.IsSidebarVisible();
|
||||
if (ImGui::MenuItem(ICON_MD_VIEW_SIDEBAR " Toggle Sidebar", nullptr,
|
||||
sidebar_visible)) {
|
||||
TogglePanelSidebar();
|
||||
}
|
||||
|
||||
if (editor_manager_) {
|
||||
auto* right_panel = editor_manager_->right_panel_manager();
|
||||
if (right_panel) {
|
||||
const bool settings_active = right_panel->IsPanelActive(
|
||||
RightPanelManager::PanelType::kSettings);
|
||||
if (ImGui::MenuItem(ICON_MD_SETTINGS " Settings", nullptr,
|
||||
settings_active)) {
|
||||
right_panel->TogglePanel(RightPanelManager::PanelType::kSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -273,6 +273,10 @@ class UICoordinator {
|
||||
const ImVec4& color, std::function<void()> callback,
|
||||
bool enabled = true);
|
||||
|
||||
// Mobile helpers
|
||||
bool IsCompactLayout() const;
|
||||
void DrawMobileNavigation();
|
||||
|
||||
// Layout and positioning helpers
|
||||
void CenterWindow(const std::string& window_name);
|
||||
void PositionWindow(const std::string& window_name, float x, float y);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string_view>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/time/clock.h"
|
||||
@@ -31,21 +32,33 @@ ImVec4 GetThemedColor(const char* color_name, const ImVec4& fallback) {
|
||||
auto& theme_mgr = gui::ThemeManager::Get();
|
||||
const auto& theme = theme_mgr.GetCurrentTheme();
|
||||
|
||||
// TODO: Fix this
|
||||
// Map color names to theme colors
|
||||
// if (strcmp(color_name, "triforce_gold") == 0) {
|
||||
// return theme.accent.to_im_vec4();
|
||||
// } else if (strcmp(color_name, "hyrule_green") == 0) {
|
||||
// return theme.success.to_im_vec4();
|
||||
// } else if (strcmp(color_name, "master_sword_blue") == 0) {
|
||||
// return theme.info.to_im_vec4();
|
||||
// } else if (strcmp(color_name, "ganon_purple") == 0) {
|
||||
// return theme.secondary.to_im_vec4();
|
||||
// } else if (strcmp(color_name, "heart_red") == 0) {
|
||||
// return theme.error.to_im_vec4();
|
||||
// } else if (strcmp(color_name, "spirit_orange") == 0) {
|
||||
// return theme.warning.to_im_vec4();
|
||||
// }
|
||||
if (!color_name) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const std::string_view name(color_name);
|
||||
if (name == "triforce_gold") {
|
||||
return gui::ConvertColorToImVec4(theme.accent);
|
||||
}
|
||||
if (name == "hyrule_green") {
|
||||
return gui::ConvertColorToImVec4(theme.success);
|
||||
}
|
||||
if (name == "master_sword_blue") {
|
||||
return gui::ConvertColorToImVec4(theme.info);
|
||||
}
|
||||
if (name == "ganon_purple") {
|
||||
return gui::ConvertColorToImVec4(theme.secondary);
|
||||
}
|
||||
if (name == "heart_red") {
|
||||
return gui::ConvertColorToImVec4(theme.error);
|
||||
}
|
||||
if (name == "spirit_orange") {
|
||||
return gui::ConvertColorToImVec4(theme.warning);
|
||||
}
|
||||
if (name == "shadow_purple") {
|
||||
return ImLerp(gui::ConvertColorToImVec4(theme.secondary),
|
||||
gui::GetSurfaceVec4(), 0.4f);
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
@@ -59,6 +72,11 @@ const ImVec4 kHeartRedFallback = ImVec4(0.863f, 0.078f, 0.235f, 1.0f);
|
||||
const ImVec4 kSpiritOrangeFallback = ImVec4(1.0f, 0.647f, 0.0f, 1.0f);
|
||||
const ImVec4 kShadowPurpleFallback = ImVec4(0.416f, 0.353f, 0.804f, 1.0f);
|
||||
|
||||
constexpr float kRecentCardBaseWidth = 220.0f;
|
||||
constexpr float kRecentCardBaseHeight = 95.0f;
|
||||
constexpr float kRecentCardWidthMaxFactor = 1.25f;
|
||||
constexpr float kRecentCardHeightMaxFactor = 1.25f;
|
||||
|
||||
// Active colors (updated each frame from theme)
|
||||
ImVec4 kTriforceGold = kTriforceGoldFallback;
|
||||
ImVec4 kHyruleGreen = kHyruleGreenFallback;
|
||||
@@ -116,7 +134,9 @@ void DrawTriforceBackground(ImDrawList* draw_list, ImVec2 pos, float size,
|
||||
draw_list->AddTriangleFilled(p1, p2, p3, color);
|
||||
};
|
||||
|
||||
ImU32 gold = ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha));
|
||||
ImVec4 gold_color = kTriforceGold;
|
||||
gold_color.w = alpha;
|
||||
ImU32 gold = ImGui::GetColorU32(gold_color);
|
||||
|
||||
// Proper triforce layout with three triangles
|
||||
float small_size = size / 2.0f;
|
||||
@@ -134,6 +154,51 @@ void DrawTriforceBackground(ImDrawList* draw_list, ImVec2 pos, float size,
|
||||
gold);
|
||||
}
|
||||
|
||||
struct GridLayout {
|
||||
int columns = 1;
|
||||
float item_width = 0.0f;
|
||||
float item_height = 0.0f;
|
||||
float spacing = 0.0f;
|
||||
float row_start_x = 0.0f;
|
||||
};
|
||||
|
||||
GridLayout ComputeGridLayout(float avail_width, float min_width,
|
||||
float max_width, float min_height,
|
||||
float max_height, float preferred_width,
|
||||
float aspect_ratio, float spacing) {
|
||||
GridLayout layout;
|
||||
layout.spacing = spacing;
|
||||
const auto width_for_columns = [avail_width, spacing](int columns) {
|
||||
return (avail_width - spacing * static_cast<float>(columns - 1)) /
|
||||
static_cast<float>(columns);
|
||||
};
|
||||
|
||||
layout.columns =
|
||||
std::max(1, static_cast<int>((avail_width + spacing) /
|
||||
(preferred_width + spacing)));
|
||||
|
||||
layout.item_width = width_for_columns(layout.columns);
|
||||
while (layout.columns > 1 && layout.item_width < min_width) {
|
||||
layout.columns -= 1;
|
||||
layout.item_width = width_for_columns(layout.columns);
|
||||
}
|
||||
|
||||
layout.item_width = std::min(layout.item_width, max_width);
|
||||
layout.item_width = std::min(layout.item_width, avail_width);
|
||||
layout.item_height =
|
||||
std::clamp(layout.item_width * aspect_ratio, min_height, max_height);
|
||||
|
||||
const float row_width =
|
||||
layout.item_width * static_cast<float>(layout.columns) +
|
||||
spacing * static_cast<float>(layout.columns - 1);
|
||||
layout.row_start_x = ImGui::GetCursorPosX();
|
||||
if (row_width < avail_width) {
|
||||
layout.row_start_x += (avail_width - row_width) * 0.5f;
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WelcomeScreen::WelcomeScreen() {
|
||||
@@ -380,46 +445,83 @@ bool WelcomeScreen::Show(bool* p_open) {
|
||||
ImGui::Dummy(ImVec2(0, 10));
|
||||
|
||||
ImGui::BeginChild("WelcomeContent", ImVec2(0, -60), false);
|
||||
const float content_width = ImGui::GetContentRegionAvail().x;
|
||||
const float content_height = ImGui::GetContentRegionAvail().y;
|
||||
const bool narrow_layout = content_width < 900.0f;
|
||||
|
||||
// Left side - Quick Actions & Templates
|
||||
ImGui::BeginChild("LeftPanel",
|
||||
ImVec2(ImGui::GetContentRegionAvail().x * 0.3f, 0), true,
|
||||
ImGuiWindowFlags_NoScrollbar);
|
||||
DrawQuickActions();
|
||||
ImGui::Spacing();
|
||||
if (narrow_layout) {
|
||||
float left_height = std::clamp(content_height * 0.45f, 260.0f, content_height);
|
||||
ImGui::BeginChild("LeftPanel", ImVec2(0, left_height), true,
|
||||
ImGuiWindowFlags_NoScrollbar);
|
||||
DrawQuickActions();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Subtle separator
|
||||
ImVec2 sep_start = ImGui::GetCursorScreenPos();
|
||||
draw_list->AddLine(
|
||||
sep_start,
|
||||
ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
|
||||
ImGui::GetColorU32(
|
||||
ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.2f)),
|
||||
1.0f);
|
||||
ImVec2 sep_start = ImGui::GetCursorScreenPos();
|
||||
draw_list->AddLine(
|
||||
sep_start,
|
||||
ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
|
||||
ImGui::GetColorU32(
|
||||
ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.2f)),
|
||||
1.0f);
|
||||
|
||||
ImGui::Dummy(ImVec2(0, 5));
|
||||
DrawTemplatesSection();
|
||||
ImGui::EndChild();
|
||||
ImGui::Dummy(ImVec2(0, 5));
|
||||
DrawTemplatesSection();
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Right side - Recent Projects & What's New
|
||||
ImGui::BeginChild("RightPanel", ImVec2(0, 0), true);
|
||||
DrawRecentProjects();
|
||||
ImGui::Spacing();
|
||||
ImGui::BeginChild("RightPanel", ImVec2(0, 0), true);
|
||||
DrawRecentProjects();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Subtle separator
|
||||
sep_start = ImGui::GetCursorScreenPos();
|
||||
draw_list->AddLine(
|
||||
sep_start,
|
||||
ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
|
||||
ImGui::GetColorU32(ImVec4(kMasterSwordBlue.x, kMasterSwordBlue.y,
|
||||
kMasterSwordBlue.z, 0.2f)),
|
||||
1.0f);
|
||||
sep_start = ImGui::GetCursorScreenPos();
|
||||
draw_list->AddLine(
|
||||
sep_start,
|
||||
ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
|
||||
ImGui::GetColorU32(ImVec4(kMasterSwordBlue.x, kMasterSwordBlue.y,
|
||||
kMasterSwordBlue.z, 0.2f)),
|
||||
1.0f);
|
||||
|
||||
ImGui::Dummy(ImVec2(0, 5));
|
||||
DrawWhatsNew();
|
||||
ImGui::EndChild();
|
||||
ImGui::Dummy(ImVec2(0, 5));
|
||||
DrawWhatsNew();
|
||||
ImGui::EndChild();
|
||||
} else {
|
||||
ImGui::BeginChild("LeftPanel",
|
||||
ImVec2(ImGui::GetContentRegionAvail().x * 0.3f, 0), true,
|
||||
ImGuiWindowFlags_NoScrollbar);
|
||||
DrawQuickActions();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImVec2 sep_start = ImGui::GetCursorScreenPos();
|
||||
draw_list->AddLine(
|
||||
sep_start,
|
||||
ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
|
||||
ImGui::GetColorU32(
|
||||
ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.2f)),
|
||||
1.0f);
|
||||
|
||||
ImGui::Dummy(ImVec2(0, 5));
|
||||
DrawTemplatesSection();
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginChild("RightPanel", ImVec2(0, 0), true);
|
||||
DrawRecentProjects();
|
||||
ImGui::Spacing();
|
||||
|
||||
sep_start = ImGui::GetCursorScreenPos();
|
||||
draw_list->AddLine(
|
||||
sep_start,
|
||||
ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
|
||||
ImGui::GetColorU32(ImVec4(kMasterSwordBlue.x, kMasterSwordBlue.y,
|
||||
kMasterSwordBlue.z, 0.2f)),
|
||||
1.0f);
|
||||
|
||||
ImGui::Dummy(ImVec2(0, 5));
|
||||
DrawWhatsNew();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -629,7 +731,8 @@ void WelcomeScreen::DrawRecentProjects() {
|
||||
|
||||
if (recent_projects_.empty()) {
|
||||
// Simple empty state
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f));
|
||||
const ImVec4 text_secondary = gui::GetTextSecondaryVec4();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, text_secondary);
|
||||
|
||||
ImVec2 cursor = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPosX(cursor.x + ImGui::GetContentRegionAvail().x * 0.3f);
|
||||
@@ -644,47 +747,89 @@ void WelcomeScreen::DrawRecentProjects() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Grid layout for project cards (compact)
|
||||
float card_width = 220.0f; // Reduced for compactness
|
||||
float card_height = 95.0f; // Reduced for less scrolling
|
||||
int columns =
|
||||
std::max(1, (int)(ImGui::GetContentRegionAvail().x / (card_width + 12)));
|
||||
const float scale = ImGui::GetFontSize() / 16.0f;
|
||||
const float min_width = kRecentCardBaseWidth * scale;
|
||||
const float max_width = kRecentCardBaseWidth * kRecentCardWidthMaxFactor * scale;
|
||||
const float min_height = kRecentCardBaseHeight * scale;
|
||||
const float max_height = kRecentCardBaseHeight * kRecentCardHeightMaxFactor * scale;
|
||||
const float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||
const float aspect_ratio = min_height / std::max(min_width, 1.0f);
|
||||
|
||||
GridLayout layout = ComputeGridLayout(ImGui::GetContentRegionAvail().x,
|
||||
min_width, max_width, min_height,
|
||||
max_height, min_width, aspect_ratio,
|
||||
spacing);
|
||||
|
||||
int column = 0;
|
||||
for (size_t i = 0; i < recent_projects_.size(); ++i) {
|
||||
if (i % columns != 0) {
|
||||
ImGui::SameLine();
|
||||
if (column == 0) {
|
||||
ImGui::SetCursorPosX(layout.row_start_x);
|
||||
}
|
||||
DrawProjectPanel(recent_projects_[i], i);
|
||||
|
||||
DrawProjectPanel(recent_projects_[i], static_cast<int>(i),
|
||||
ImVec2(layout.item_width, layout.item_height));
|
||||
|
||||
column += 1;
|
||||
if (column < layout.columns) {
|
||||
ImGui::SameLine(0.0f, layout.spacing);
|
||||
} else {
|
||||
column = 0;
|
||||
ImGui::Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
if (column != 0) {
|
||||
ImGui::NewLine();
|
||||
}
|
||||
}
|
||||
|
||||
void WelcomeScreen::DrawProjectPanel(const RecentProject& project, int index) {
|
||||
void WelcomeScreen::DrawProjectPanel(const RecentProject& project, int index,
|
||||
const ImVec2& card_size) {
|
||||
ImGui::BeginGroup();
|
||||
|
||||
ImVec2 card_size(200, 95); // Compact size
|
||||
const ImVec4 surface = gui::GetSurfaceVec4();
|
||||
const ImVec4 surface_variant = gui::GetSurfaceVariantVec4();
|
||||
const ImVec4 text_primary = gui::GetOnSurfaceVec4();
|
||||
const ImVec4 text_secondary = gui::GetTextSecondaryVec4();
|
||||
const ImVec4 text_disabled = gui::GetTextDisabledVec4();
|
||||
|
||||
ImVec2 resolved_card_size = card_size;
|
||||
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
|
||||
|
||||
// Subtle hover scale (only on actual hover, no animation)
|
||||
float scale = card_hover_scale_[index];
|
||||
if (scale != 1.0f) {
|
||||
ImVec2 center(cursor_pos.x + card_size.x / 2,
|
||||
cursor_pos.y + card_size.y / 2);
|
||||
cursor_pos.x = center.x - (card_size.x * scale) / 2;
|
||||
cursor_pos.y = center.y - (card_size.y * scale) / 2;
|
||||
card_size.x *= scale;
|
||||
card_size.y *= scale;
|
||||
float hover_scale = card_hover_scale_[index];
|
||||
if (hover_scale != 1.0f) {
|
||||
ImVec2 center(cursor_pos.x + resolved_card_size.x / 2,
|
||||
cursor_pos.y + resolved_card_size.y / 2);
|
||||
cursor_pos.x = center.x - (resolved_card_size.x * hover_scale) / 2;
|
||||
cursor_pos.y = center.y - (resolved_card_size.y * hover_scale) / 2;
|
||||
resolved_card_size.x *= hover_scale;
|
||||
resolved_card_size.y *= hover_scale;
|
||||
}
|
||||
|
||||
const float layout_scale = resolved_card_size.y / kRecentCardBaseHeight;
|
||||
const float padding = 8.0f * layout_scale;
|
||||
const float icon_radius = 15.0f * layout_scale;
|
||||
const float icon_offset = 13.0f * layout_scale;
|
||||
const float text_offset = 32.0f * layout_scale;
|
||||
const float name_offset_y = 8.0f * layout_scale;
|
||||
const float rom_offset_y = 35.0f * layout_scale;
|
||||
const float path_offset_y = 58.0f * layout_scale;
|
||||
|
||||
// Draw card background with subtle gradient
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
// Gradient background
|
||||
ImU32 color_top = ImGui::GetColorU32(ImVec4(0.15f, 0.20f, 0.25f, 1.0f));
|
||||
ImU32 color_bottom = ImGui::GetColorU32(ImVec4(0.10f, 0.15f, 0.20f, 1.0f));
|
||||
ImVec4 color_top = ImLerp(surface_variant, surface, 0.7f);
|
||||
ImVec4 color_bottom = ImLerp(surface_variant, surface, 0.3f);
|
||||
ImU32 color_top_u32 = ImGui::GetColorU32(color_top);
|
||||
ImU32 color_bottom_u32 = ImGui::GetColorU32(color_bottom);
|
||||
draw_list->AddRectFilledMultiColor(
|
||||
cursor_pos,
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), color_top,
|
||||
color_top, color_bottom, color_bottom);
|
||||
ImVec2(cursor_pos.x + resolved_card_size.x,
|
||||
cursor_pos.y + resolved_card_size.y),
|
||||
color_top_u32,
|
||||
color_top_u32, color_bottom_u32, color_bottom_u32);
|
||||
|
||||
// Static themed border
|
||||
ImVec4 border_color_base = (index % 3 == 0) ? kHyruleGreen
|
||||
@@ -695,13 +840,14 @@ void WelcomeScreen::DrawProjectPanel(const RecentProject& project, int index) {
|
||||
|
||||
draw_list->AddRect(
|
||||
cursor_pos,
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
ImVec2(cursor_pos.x + resolved_card_size.x,
|
||||
cursor_pos.y + resolved_card_size.y),
|
||||
border_color, 6.0f, 0, 2.0f);
|
||||
|
||||
// Make the card clickable
|
||||
ImGui::SetCursorScreenPos(cursor_pos);
|
||||
ImGui::InvisibleButton(absl::StrFormat("ProjectPanel_%d", index).c_str(),
|
||||
card_size);
|
||||
resolved_card_size);
|
||||
bool is_hovered = ImGui::IsItemHovered();
|
||||
bool is_clicked = ImGui::IsItemClicked();
|
||||
|
||||
@@ -714,50 +860,64 @@ void WelcomeScreen::DrawProjectPanel(const RecentProject& project, int index) {
|
||||
ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f));
|
||||
draw_list->AddRectFilled(
|
||||
cursor_pos,
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
ImVec2(cursor_pos.x + resolved_card_size.x,
|
||||
cursor_pos.y + resolved_card_size.y),
|
||||
hover_color, 6.0f);
|
||||
}
|
||||
|
||||
// Draw content (tighter layout)
|
||||
ImVec2 content_pos(cursor_pos.x + 8, cursor_pos.y + 8);
|
||||
ImVec2 content_pos(cursor_pos.x + padding, cursor_pos.y + padding);
|
||||
|
||||
// Icon with colored background circle (compact)
|
||||
ImVec2 icon_center(content_pos.x + 13, content_pos.y + 13);
|
||||
ImVec2 icon_center(content_pos.x + icon_offset, content_pos.y + icon_offset);
|
||||
ImU32 icon_bg = ImGui::GetColorU32(border_color_base);
|
||||
draw_list->AddCircleFilled(icon_center, 15, icon_bg, 24);
|
||||
draw_list->AddCircleFilled(icon_center, icon_radius, icon_bg, 24);
|
||||
|
||||
// Center the icon properly
|
||||
ImVec2 icon_size = ImGui::CalcTextSize(ICON_MD_VIDEOGAME_ASSET);
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, text_primary);
|
||||
ImGui::Text(ICON_MD_VIDEOGAME_ASSET);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// Project name (compact, shorten if too long)
|
||||
ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 32, content_pos.y + 8));
|
||||
ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - 8);
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(content_pos.x + text_offset, content_pos.y + name_offset_y));
|
||||
ImGui::PushTextWrapPos(cursor_pos.x + resolved_card_size.x - padding);
|
||||
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Default font
|
||||
std::string short_name = project.name;
|
||||
if (short_name.length() > 22) {
|
||||
short_name = short_name.substr(0, 19) + "...";
|
||||
const float approx_char_width = ImGui::CalcTextSize("A").x;
|
||||
const float name_max_width =
|
||||
std::max(120.0f, resolved_card_size.x - text_offset - padding);
|
||||
size_t max_chars = static_cast<size_t>(
|
||||
std::max(12.0f, name_max_width / std::max(approx_char_width, 1.0f)));
|
||||
if (short_name.length() > max_chars) {
|
||||
short_name = short_name.substr(0, max_chars - 3) + "...";
|
||||
}
|
||||
ImGui::TextColored(kTriforceGold, "%s", short_name.c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// ROM title (compact)
|
||||
ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 35));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.65f, 0.65f, 0.65f, 1.0f));
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(content_pos.x + padding * 0.5f, content_pos.y + rom_offset_y));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, text_secondary);
|
||||
ImGui::Text(ICON_MD_GAMEPAD " %s", project.rom_title.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// Path in card (compact)
|
||||
ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 58));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(content_pos.x + padding * 0.5f, content_pos.y + path_offset_y));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, text_disabled);
|
||||
std::string short_path = project.filepath;
|
||||
if (short_path.length() > 26) {
|
||||
short_path = "..." + short_path.substr(short_path.length() - 23);
|
||||
const float path_max_width =
|
||||
std::max(120.0f, resolved_card_size.x - padding * 2.0f);
|
||||
size_t max_path_chars = static_cast<size_t>(
|
||||
std::max(18.0f, path_max_width / std::max(approx_char_width, 1.0f)));
|
||||
if (short_path.length() > max_path_chars) {
|
||||
short_path =
|
||||
"..." + short_path.substr(short_path.length() - (max_path_chars - 3));
|
||||
}
|
||||
ImGui::Text(ICON_MD_FOLDER " %s", short_path.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
@@ -1003,4 +1163,4 @@ void WelcomeScreen::DrawWhatsNew() {
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
} // namespace yaze
|
||||
|
||||
@@ -114,7 +114,8 @@ class WelcomeScreen {
|
||||
void DrawHeader();
|
||||
void DrawQuickActions();
|
||||
void DrawRecentProjects();
|
||||
void DrawProjectPanel(const RecentProject& project, int index);
|
||||
void DrawProjectPanel(const RecentProject& project, int index,
|
||||
const ImVec2& card_size);
|
||||
void DrawTemplatesSection();
|
||||
void DrawTipsSection();
|
||||
void DrawWhatsNew();
|
||||
|
||||
Reference in New Issue
Block a user