diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 1508566..3de1bb2 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3550,4 +3550,13 @@ target_link_libraries(step518_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step519_test tests/step519_test.cpp) +target_include_directories(step519_test PRIVATE src) +target_link_libraries(step519_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/ModifierEdgeNotation.h b/editor/src/ModifierEdgeNotation.h new file mode 100644 index 0000000..61046a0 --- /dev/null +++ b/editor/src/ModifierEdgeNotation.h @@ -0,0 +1,76 @@ +#pragma once +// Step 519: Modifier-Edge Shortcut Notation + +#include +#include +#include + +struct ModifierEdges { + bool left = false; // Ctrl + bool right = false; // Shift + bool top = false; // Super/Win + bool bottom = false; // Alt +}; + +struct ShortcutGlyph { + char key = '?'; + ModifierEdges edges; + std::string fallbackText; +}; + +class ModifierEdgeNotation { +public: + static ModifierEdges fromModifiers(bool ctrl, bool shift, + bool superKey, bool alt) { + return {ctrl, shift, superKey, alt}; + } + + static ShortcutGlyph buildGlyph(char key, + bool ctrl, + bool shift, + bool superKey, + bool alt, + bool glyphEnabled) { + ShortcutGlyph g; + g.key = normalizeKey(key); + g.edges = fromModifiers(ctrl, shift, superKey, alt); + g.fallbackText = fallbackLabel(g); + if (!glyphEnabled) { + g.edges = {}; + } + return g; + } + + static int activeEdgeCount(const ModifierEdges& e) { + return (e.left ? 1 : 0) + (e.right ? 1 : 0) + + (e.top ? 1 : 0) + (e.bottom ? 1 : 0); + } + + static bool compactEnough(const ShortcutGlyph& g) { + // Key plus edge markers should fit compact control regions. + return g.key != '?' && g.fallbackText.size() <= 20; + } + + static std::string fallbackLabel(const ShortcutGlyph& g) { + std::vector parts; + if (g.edges.left) parts.push_back("Ctrl"); + if (g.edges.right) parts.push_back("Shift"); + if (g.edges.top) parts.push_back("Super"); + if (g.edges.bottom) parts.push_back("Alt"); + std::string out; + for (size_t i = 0; i < parts.size(); ++i) { + if (i > 0) out += "+"; + out += parts[i]; + } + if (!out.empty()) out += "+"; + out += std::string(1, g.key); + return out; + } + +private: + static char normalizeKey(char key) { + if (key >= 'a' && key <= 'z') return static_cast(key - 'a' + 'A'); + if ((key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) return key; + return '?'; + } +}; diff --git a/editor/tests/step519_test.cpp b/editor/tests/step519_test.cpp new file mode 100644 index 0000000..9cc0653 --- /dev/null +++ b/editor/tests/step519_test.cpp @@ -0,0 +1,116 @@ +// Step 519: Modifier-Edge Shortcut Notation (12 tests) + +#include "ModifierEdgeNotation.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +void test_ctrl_maps_to_left_edge() { + TEST(ctrl_maps_to_left_edge); + auto e = ModifierEdgeNotation::fromModifiers(true, false, false, false); + CHECK(e.left && !e.right && !e.top && !e.bottom, "ctrl edge mapping invalid"); + PASS(); +} + +void test_shift_maps_to_right_edge() { + TEST(shift_maps_to_right_edge); + auto e = ModifierEdgeNotation::fromModifiers(false, true, false, false); + CHECK(e.right, "shift should map to right edge"); + PASS(); +} + +void test_super_maps_to_top_edge() { + TEST(super_maps_to_top_edge); + auto e = ModifierEdgeNotation::fromModifiers(false, false, true, false); + CHECK(e.top, "super should map to top edge"); + PASS(); +} + +void test_alt_maps_to_bottom_edge() { + TEST(alt_maps_to_bottom_edge); + auto e = ModifierEdgeNotation::fromModifiers(false, false, false, true); + CHECK(e.bottom, "alt should map to bottom edge"); + PASS(); +} + +void test_combined_modifiers_set_multiple_edges() { + TEST(combined_modifiers_set_multiple_edges); + auto e = ModifierEdgeNotation::fromModifiers(true, true, false, true); + CHECK(ModifierEdgeNotation::activeEdgeCount(e) == 3, "expected 3 active edges"); + PASS(); +} + +void test_key_is_normalized_to_uppercase() { + TEST(key_is_normalized_to_uppercase); + auto g = ModifierEdgeNotation::buildGlyph('k', true, false, false, false, true); + CHECK(g.key == 'K', "key should normalize to uppercase"); + PASS(); +} + +void test_invalid_key_becomes_placeholder() { + TEST(invalid_key_becomes_placeholder); + auto g = ModifierEdgeNotation::buildGlyph('%', true, false, false, false, true); + CHECK(g.key == '?', "invalid key should become placeholder"); + PASS(); +} + +void test_fallback_label_contains_modifiers_and_key() { + TEST(fallback_label_contains_modifiers_and_key); + auto g = ModifierEdgeNotation::buildGlyph('P', true, true, false, false, true); + CHECK(g.fallbackText == "Ctrl+Shift+P", "fallback label mismatch"); + PASS(); +} + +void test_glyph_disabled_uses_fallback_no_edges() { + TEST(glyph_disabled_uses_fallback_no_edges); + auto g = ModifierEdgeNotation::buildGlyph('P', true, true, false, false, false); + CHECK(ModifierEdgeNotation::activeEdgeCount(g.edges) == 0, "edges should be cleared"); + CHECK(g.fallbackText == "Ctrl+Shift+P", "fallback should remain available"); + PASS(); +} + +void test_compact_check_passes_for_normal_shortcut() { + TEST(compact_check_passes_for_normal_shortcut); + auto g = ModifierEdgeNotation::buildGlyph('A', true, false, false, false, true); + CHECK(ModifierEdgeNotation::compactEnough(g), "shortcut should be compact"); + PASS(); +} + +void test_active_edge_count_zero_without_modifiers() { + TEST(active_edge_count_zero_without_modifiers); + auto e = ModifierEdgeNotation::fromModifiers(false, false, false, false); + CHECK(ModifierEdgeNotation::activeEdgeCount(e) == 0, "edge count should be zero"); + PASS(); +} + +void test_super_alt_combo_fallback_order_is_stable() { + TEST(super_alt_combo_fallback_order_is_stable); + auto g = ModifierEdgeNotation::buildGlyph('X', false, false, true, true, true); + CHECK(g.fallbackText == "Super+Alt+X", "modifier ordering should be stable"); + PASS(); +} + +int main() { + std::cout << "Step 519: Modifier-Edge Shortcut Notation\n"; + + test_ctrl_maps_to_left_edge(); // 1 + test_shift_maps_to_right_edge(); // 2 + test_super_maps_to_top_edge(); // 3 + test_alt_maps_to_bottom_edge(); // 4 + test_combined_modifiers_set_multiple_edges(); // 5 + test_key_is_normalized_to_uppercase(); // 6 + test_invalid_key_becomes_placeholder(); // 7 + test_fallback_label_contains_modifiers_and_key(); // 8 + test_glyph_disabled_uses_fallback_no_edges(); // 9 + test_compact_check_passes_for_normal_shortcut(); // 10 + test_active_edge_count_zero_without_modifiers(); // 11 + test_super_alt_combo_fallback_order_is_stable(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 22abbba..179e1ce 100644 --- a/progress.md +++ b/progress.md @@ -8351,3 +8351,38 @@ to reduce visual drift between dense and sparse UI regions. - `editor/src/IconTypographyHarmonizer.h` within header-size limit (`60` <= `600`) - `editor/tests/step518_test.cpp` within test-file size guidance (`125` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 519: Modifier-Edge Shortcut Notation +**Status:** PASS (12/12 tests) + +Implements compact modifier-edge shortcut notation with deterministic edge +mapping (`Ctrl`=left, `Shift`=right, `Super`=top, `Alt`=bottom), combined +modifier support, and stable text fallback labels for non-glyph environments. + +**Files added:** +- `editor/src/ModifierEdgeNotation.h` - notation module: + - modifier-to-edge mapping model + - glyph construction with key normalization + - edge-activity counting and compactness checks + - fallback label generation with stable modifier ordering +- `editor/tests/step519_test.cpp` - 12 tests covering: + - per-modifier edge mapping + - combined-edge behavior + - key normalization and invalid-key handling + - fallback-label correctness and ordering + - glyph-disabled fallback behavior + - compactness and edge-count sanity cases + +**Files modified:** +- `editor/CMakeLists.txt` - `step519_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step519_test` - PASS +- `./editor/build-native/step519_test` - PASS (12/12) +- `./editor/build-native/step518_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ModifierEdgeNotation.h` within header-size limit (`76` <= `600`) +- `editor/tests/step519_test.cpp` within test-file size guidance (`116` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`