diff --git a/PROGRESS.md b/PROGRESS.md index 2cfb2be..7708f84 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -521,3 +521,4 @@ Sprint 5 in progress. Step 141 (Emacs daemon with user config) done. Next: Step | 2026-02-10 | Codex | Step 171: Theme engine core enhancements (ThemeColor API, user theme dir, hot reload) with tests. 2/2 tests pass (step171_test, step171_integration_test). file_limits_test 4/4 passes. | | 2026-02-10 | Codex | Step 172: Bundled theme pack renamed to Whetstone Dark/Light and Monokai Pro with tests. 2/2 tests pass (step172_test, step172_integration_test). file_limits_test 4/4 passes. | | 2026-02-10 | Codex | Step 173: Theme gallery UI with hover preview, apply/reset, and swatches. 2/2 tests pass (step173_test, step173_integration_test). file_limits_test 4/4 passes. | +| 2026-02-10 | Codex | Step 174: Added IconSet system with theme-aware, zoom-scaled icons + tests. 2/2 tests pass (step174_test, step174_integration_test). file_limits_test 4/4 passes. | diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 7d3a179..edf278e 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -1002,6 +1002,13 @@ add_executable(step173_integration_test tests/step173_integration_test.cpp) target_include_directories(step173_integration_test PRIVATE src) target_link_libraries(step173_integration_test PRIVATE imgui::imgui) +add_executable(step174_test tests/step174_test.cpp) +target_include_directories(step174_test PRIVATE src) + +add_executable(step174_integration_test tests/step174_integration_test.cpp) +target_include_directories(step174_integration_test PRIVATE src) +target_link_libraries(step174_integration_test PRIVATE imgui::imgui) + find_package(SDL2 CONFIG REQUIRED) find_package(OpenGL REQUIRED) find_package(glad CONFIG REQUIRED) diff --git a/editor/src/IconSet.h b/editor/src/IconSet.h new file mode 100644 index 0000000..923a660 --- /dev/null +++ b/editor/src/IconSet.h @@ -0,0 +1,230 @@ +#pragma once + +#include "imgui.h" +#include "ThemeEngine.h" + +#include + +enum class FileIconType { + Python, + Cpp, + JavaScript, + TypeScript, + Rust, + Go, + Java, + Elisp, + Json, + Text, + Folder +}; + +enum class PanelIconType { + Explorer, + Outline, + Terminal, + Agents, + Dependencies, + Library, + Settings, + Search +}; + +enum class DiagnosticIconType { + Error, + Warning, + Info, + Hint +}; + +enum class AnnotationIconType { + Ownership, + Borrow, + Shared, + Cache, + Compute +}; + +struct IconSet { + static float scaleFromZoom(float fontSize, float baseFontSize) { + if (baseFontSize <= 0.0f) return 1.0f; + return fontSize / baseFontSize; + } + + static ImU32 fileColor(FileIconType type) { + switch (type) { + case FileIconType::Python: + return ThemeEngine::instance().editorColor("icon_python", IM_COL32(53, 114, 165, 255)); + case FileIconType::Cpp: + return ThemeEngine::instance().editorColor("icon_cpp", IM_COL32(86, 156, 214, 255)); + case FileIconType::JavaScript: + return ThemeEngine::instance().editorColor("icon_js", IM_COL32(240, 219, 79, 255)); + case FileIconType::TypeScript: + return ThemeEngine::instance().editorColor("icon_ts", IM_COL32(48, 120, 198, 255)); + case FileIconType::Rust: + return ThemeEngine::instance().editorColor("icon_rust", IM_COL32(222, 165, 132, 255)); + case FileIconType::Go: + return ThemeEngine::instance().editorColor("icon_go", IM_COL32(0, 173, 216, 255)); + case FileIconType::Java: + return ThemeEngine::instance().editorColor("icon_java", IM_COL32(176, 114, 25, 255)); + case FileIconType::Elisp: + return ThemeEngine::instance().editorColor("icon_elisp", IM_COL32(128, 88, 170, 255)); + case FileIconType::Json: + return ThemeEngine::instance().editorColor("icon_json", IM_COL32(200, 180, 120, 255)); + case FileIconType::Folder: + return ThemeEngine::instance().editorColor("icon_folder", IM_COL32(201, 163, 86, 255)); + case FileIconType::Text: + default: + return ThemeEngine::instance().editorColor("icon_text", IM_COL32(180, 180, 180, 255)); + } + } + + static ImU32 panelColor(PanelIconType type) { + ImU32 base = ThemeEngine::instance().editorColor("icon_panel", IM_COL32(150, 150, 150, 255)); + switch (type) { + case PanelIconType::Terminal: + return ThemeEngine::instance().editorColor("icon_terminal", IM_COL32(0, 200, 120, 255)); + case PanelIconType::Agents: + return ThemeEngine::instance().editorColor("icon_agents", IM_COL32(140, 180, 255, 255)); + case PanelIconType::Dependencies: + return ThemeEngine::instance().editorColor("icon_deps", IM_COL32(230, 190, 120, 255)); + default: + return base; + } + } + + static ImU32 diagnosticColor(DiagnosticIconType type) { + switch (type) { + case DiagnosticIconType::Error: + return ThemeEngine::instance().editorColor("diag_error", IM_COL32(220, 80, 80, 255)); + case DiagnosticIconType::Warning: + return ThemeEngine::instance().editorColor("diag_warning", IM_COL32(220, 160, 60, 255)); + case DiagnosticIconType::Info: + return ThemeEngine::instance().editorColor("diag_info", IM_COL32(90, 160, 220, 255)); + case DiagnosticIconType::Hint: + default: + return ThemeEngine::instance().editorColor("diag_hint", IM_COL32(160, 200, 120, 255)); + } + } + + static ImU32 annotationColor(AnnotationIconType type) { + switch (type) { + case AnnotationIconType::Ownership: + return ThemeEngine::instance().editorColor("icon_ownership", IM_COL32(230, 120, 90, 255)); + case AnnotationIconType::Borrow: + return ThemeEngine::instance().editorColor("icon_borrow", IM_COL32(120, 180, 230, 255)); + case AnnotationIconType::Shared: + return ThemeEngine::instance().editorColor("icon_shared", IM_COL32(150, 200, 140, 255)); + case AnnotationIconType::Cache: + return ThemeEngine::instance().editorColor("icon_cache", IM_COL32(210, 190, 120, 255)); + case AnnotationIconType::Compute: + default: + return ThemeEngine::instance().editorColor("icon_compute", IM_COL32(200, 140, 220, 255)); + } + } + + static void drawFileIcon(ImDrawList* draw, ImVec2 center, float size, + FileIconType type, float scale = 1.0f) { + float s = size * scale; + ImU32 color = fileColor(type); + ImVec2 min(center.x - s * 0.5f, center.y - s * 0.6f); + ImVec2 max(center.x + s * 0.5f, center.y + s * 0.6f); + draw->AddRectFilled(min, max, color, 3.0f); + ImVec2 foldA(max.x - s * 0.25f, min.y); + ImVec2 foldB(max.x, min.y + s * 0.25f); + draw->AddTriangleFilled(foldA, ImVec2(max.x, min.y), foldB, + IM_COL32(255, 255, 255, 60)); + } + + static void drawFolderIcon(ImDrawList* draw, ImVec2 center, float size, float scale = 1.0f) { + float s = size * scale; + ImU32 color = fileColor(FileIconType::Folder); + ImVec2 min(center.x - s * 0.6f, center.y - s * 0.35f); + ImVec2 max(center.x + s * 0.6f, center.y + s * 0.35f); + draw->AddRectFilled(min, max, color, 3.0f); + ImVec2 tabMin(min.x + s * 0.1f, min.y - s * 0.2f); + ImVec2 tabMax(min.x + s * 0.5f, min.y); + draw->AddRectFilled(tabMin, tabMax, color, 3.0f); + } + + static void drawPanelIcon(ImDrawList* draw, ImVec2 center, float size, + PanelIconType type, float scale = 1.0f) { + float s = size * scale; + ImU32 color = panelColor(type); + switch (type) { + case PanelIconType::Explorer: { + ImVec2 min(center.x - s * 0.5f, center.y - s * 0.5f); + ImVec2 max(center.x + s * 0.5f, center.y + s * 0.5f); + draw->AddRect(min, max, color, 2.0f, 0, 2.0f); + draw->AddLine(ImVec2(min.x, center.y), ImVec2(max.x, center.y), color, 2.0f); + break; + } + case PanelIconType::Outline: { + draw->AddCircleFilled(center, s * 0.45f, color); + break; + } + case PanelIconType::Terminal: { + ImVec2 min(center.x - s * 0.5f, center.y - s * 0.35f); + ImVec2 max(center.x + s * 0.5f, center.y + s * 0.35f); + draw->AddRect(min, max, color, 2.0f, 0, 2.0f); + draw->AddLine(ImVec2(min.x + s * 0.2f, center.y), + ImVec2(min.x + s * 0.35f, center.y + s * 0.15f), color, 2.0f); + break; + } + default: + draw->AddCircleFilled(center, s * 0.3f, color); + break; + } + } + + static void drawDiagnosticIcon(ImDrawList* draw, ImVec2 center, float size, + DiagnosticIconType type, float scale = 1.0f) { + float s = size * scale; + ImU32 color = diagnosticColor(type); + if (type == DiagnosticIconType::Warning) { + ImVec2 a(center.x, center.y - s * 0.5f); + ImVec2 b(center.x - s * 0.5f, center.y + s * 0.4f); + ImVec2 c(center.x + s * 0.5f, center.y + s * 0.4f); + draw->AddTriangleFilled(a, b, c, color); + } else if (type == DiagnosticIconType::Info) { + draw->AddCircleFilled(center, s * 0.45f, color); + draw->AddLine(ImVec2(center.x, center.y - s * 0.15f), + ImVec2(center.x, center.y + s * 0.2f), IM_COL32(30, 30, 30, 200), 2.0f); + } else { + draw->AddCircleFilled(center, s * 0.45f, color); + } + } + + static void drawAnnotationIcon(ImDrawList* draw, ImVec2 center, float size, + AnnotationIconType type, float scale = 1.0f) { + float s = size * scale; + ImU32 color = annotationColor(type); + switch (type) { + case AnnotationIconType::Ownership: + draw->AddRectFilled(ImVec2(center.x - s * 0.35f, center.y - s * 0.35f), + ImVec2(center.x + s * 0.35f, center.y + s * 0.35f), + color, 2.0f); + break; + case AnnotationIconType::Borrow: + draw->AddCircleFilled(center, s * 0.35f, color); + break; + case AnnotationIconType::Shared: + draw->AddTriangleFilled(ImVec2(center.x, center.y - s * 0.45f), + ImVec2(center.x - s * 0.4f, center.y + s * 0.35f), + ImVec2(center.x + s * 0.4f, center.y + s * 0.35f), + color); + break; + case AnnotationIconType::Cache: + draw->AddRect(ImVec2(center.x - s * 0.4f, center.y - s * 0.4f), + ImVec2(center.x + s * 0.4f, center.y + s * 0.4f), + color, 1.0f, 0, 2.0f); + break; + case AnnotationIconType::Compute: + default: + draw->AddCircleFilled(center, s * 0.15f, color); + draw->AddCircleFilled(ImVec2(center.x - s * 0.35f, center.y), s * 0.15f, color); + draw->AddCircleFilled(ImVec2(center.x + s * 0.35f, center.y), s * 0.15f, color); + break; + } + } +}; diff --git a/editor/tests/step174_integration_test.cpp b/editor/tests/step174_integration_test.cpp new file mode 100644 index 0000000..47fe9ea --- /dev/null +++ b/editor/tests/step174_integration_test.cpp @@ -0,0 +1,17 @@ +// Step 174: Icon system integration checks. + +#include + +#include "IconSet.h" + +int main() { + ImGui::CreateContext(); + ImU32 color = IconSet::fileColor(FileIconType::Python); + ImU32 warn = IconSet::diagnosticColor(DiagnosticIconType::Warning); + assert(color != 0); + assert(warn != 0); + ImGui::DestroyContext(); + + printf("step174_integration_test: all assertions passed\n"); + return 0; +} diff --git a/editor/tests/step174_test.cpp b/editor/tests/step174_test.cpp new file mode 100644 index 0000000..9986d95 --- /dev/null +++ b/editor/tests/step174_test.cpp @@ -0,0 +1,31 @@ +// Step 174: Icon system checks. + +#include +#include +#include +#include + +namespace fs = std::filesystem; + +static std::string readFile(const std::string& path) { + std::ifstream f(path); + if (!f.is_open()) return {}; + return std::string((std::istreambuf_iterator(f)), + std::istreambuf_iterator()); +} + +static void assertContains(const std::string& text, const std::string& needle) { + assert(text.find(needle) != std::string::npos); +} + +int main() { + assert(fs::exists("src/IconSet.h")); + const std::string iconSet = readFile("src/IconSet.h"); + assertContains(iconSet, "FileIconType"); + assertContains(iconSet, "PanelIconType"); + assertContains(iconSet, "DiagnosticIconType"); + assertContains(iconSet, "AnnotationIconType"); + + printf("step174_test: all assertions passed\n"); + return 0; +} diff --git a/sprint6_plan.md b/sprint6_plan.md index cf124e3..8aeeac5 100644 --- a/sprint6_plan.md +++ b/sprint6_plan.md @@ -122,7 +122,7 @@ look professional and feel personal. - Active theme highlighted with checkmark *Modifies:* Settings panel, `ThemeEngine.h` -- [ ] **Step 174: Icon system** +- [x] **Step 174: Icon system** `IconSet.h` — consistent icons rendered via ImGui drawing primitives: - File type icons (Python, C++, JS, Rust, Go, Java, Elisp, JSON, etc.) - Panel icons (explorer, outline, terminal, agents, dependencies, etc.)