From 992d2e905d89c9b355ff9a47901481d3999395ea Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 08:59:51 -0700 Subject: [PATCH] Step 517: add control library restyle semantics --- editor/CMakeLists.txt | 9 ++ editor/src/ControlLibraryRestyle.h | 83 +++++++++++++++++++ editor/tests/step517_test.cpp | 127 +++++++++++++++++++++++++++++ progress.md | 34 ++++++++ 4 files changed, 253 insertions(+) create mode 100644 editor/src/ControlLibraryRestyle.h create mode 100644 editor/tests/step517_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 0fdb0f8..19f9862 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3532,4 +3532,13 @@ target_link_libraries(step516_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step517_test tests/step517_test.cpp) +target_include_directories(step517_test PRIVATE src) +target_link_libraries(step517_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/ControlLibraryRestyle.h b/editor/src/ControlLibraryRestyle.h new file mode 100644 index 0000000..75717b0 --- /dev/null +++ b/editor/src/ControlLibraryRestyle.h @@ -0,0 +1,83 @@ +#pragma once +// Step 517: Control Library Restyle +// Shared control style semantics for depth, pressed state clarity, and DPI +// consistency across core widgets. + +#include +#include + +struct ControlStyle { + float cornerRadius = 0.0f; + float borderWidth = 1.0f; + float depthIdle = 0.0f; + float depthPressed = 0.0f; + float labelContrast = 1.0f; + float iconContrast = 1.0f; + float strokePx = 1.0f; +}; + +enum class ControlKind { + Button, + Toggle, + Tab, + Menu, + Dropdown, + Checkbox +}; + +class ControlLibraryRestyle { +public: + static ControlStyle styleFor(ControlKind kind, float dpiScale, bool pressed) { + float s = std::max(0.75f, std::min(3.0f, dpiScale)); + ControlStyle c; + c.cornerRadius = baseRadius(kind) * s; + c.borderWidth = 1.0f * s; + c.depthIdle = baseDepth(kind) * s; + c.depthPressed = (baseDepth(kind) + 1.4f) * s; + c.labelContrast = 6.0f; + c.iconContrast = 4.0f; + c.strokePx = std::max(1.0f, 1.2f * s); + if (pressed) { + c.depthIdle = c.depthPressed; + } + return c; + } + + static bool hasClearPressedSignal(const ControlStyle& idle, + const ControlStyle& pressed) { + return pressed.depthIdle > idle.depthIdle + 0.6f; + } + + static bool validContrast(const ControlStyle& c) { + return c.labelContrast >= 4.5f && c.iconContrast >= 3.0f; + } + + static bool dpiStable(const ControlStyle& lo, const ControlStyle& hi) { + return hi.cornerRadius > lo.cornerRadius && hi.strokePx >= lo.strokePx; + } + +private: + static float baseRadius(ControlKind kind) { + switch (kind) { + case ControlKind::Button: return 4.0f; + case ControlKind::Toggle: return 7.0f; + case ControlKind::Tab: return 3.0f; + case ControlKind::Menu: return 2.0f; + case ControlKind::Dropdown: return 4.0f; + case ControlKind::Checkbox: return 2.0f; + } + return 3.0f; + } + + static float baseDepth(ControlKind kind) { + switch (kind) { + case ControlKind::Button: return 1.2f; + case ControlKind::Toggle: return 1.6f; + case ControlKind::Tab: return 1.0f; + case ControlKind::Menu: return 0.8f; + case ControlKind::Dropdown: return 1.1f; + case ControlKind::Checkbox: return 0.9f; + } + return 1.0f; + } +}; diff --git a/editor/tests/step517_test.cpp b/editor/tests/step517_test.cpp new file mode 100644 index 0000000..d7bea98 --- /dev/null +++ b/editor/tests/step517_test.cpp @@ -0,0 +1,127 @@ +// Step 517: Control Library Restyle (12 tests) + +#include "ControlLibraryRestyle.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_button_style_has_positive_depth() { + TEST(button_style_has_positive_depth); + auto s = ControlLibraryRestyle::styleFor(ControlKind::Button, 1.0f, false); + CHECK(s.depthIdle > 0.0f, "idle depth should be positive"); + PASS(); +} + +void test_pressed_state_increases_depth_signal() { + TEST(pressed_state_increases_depth_signal); + auto idle = ControlLibraryRestyle::styleFor(ControlKind::Button, 1.0f, false); + auto pressed = ControlLibraryRestyle::styleFor(ControlKind::Button, 1.0f, true); + CHECK(ControlLibraryRestyle::hasClearPressedSignal(idle, pressed), + "pressed depth signal not clear"); + PASS(); +} + +void test_toggle_has_larger_radius_than_tab() { + TEST(toggle_has_larger_radius_than_tab); + auto t = ControlLibraryRestyle::styleFor(ControlKind::Toggle, 1.0f, false); + auto tab = ControlLibraryRestyle::styleFor(ControlKind::Tab, 1.0f, false); + CHECK(t.cornerRadius > tab.cornerRadius, "toggle radius should be larger"); + PASS(); +} + +void test_checkbox_has_contrast_compliance() { + TEST(checkbox_has_contrast_compliance); + auto s = ControlLibraryRestyle::styleFor(ControlKind::Checkbox, 1.0f, false); + CHECK(ControlLibraryRestyle::validContrast(s), "checkbox contrast invalid"); + PASS(); +} + +void test_dropdown_has_nonzero_border_width() { + TEST(dropdown_has_nonzero_border_width); + auto s = ControlLibraryRestyle::styleFor(ControlKind::Dropdown, 1.0f, false); + CHECK(s.borderWidth > 0.0f, "dropdown border width should be > 0"); + PASS(); +} + +void test_menu_style_uses_small_radius() { + TEST(menu_style_uses_small_radius); + auto s = ControlLibraryRestyle::styleFor(ControlKind::Menu, 1.0f, false); + CHECK(s.cornerRadius <= 3.0f, "menu radius should be compact"); + PASS(); +} + +void test_dpi_scale_increases_geometry() { + TEST(dpi_scale_increases_geometry); + auto lo = ControlLibraryRestyle::styleFor(ControlKind::Button, 1.0f, false); + auto hi = ControlLibraryRestyle::styleFor(ControlKind::Button, 2.0f, false); + CHECK(ControlLibraryRestyle::dpiStable(lo, hi), "DPI scaling not stable"); + PASS(); +} + +void test_dpi_scale_clamps_lower_bound() { + TEST(dpi_scale_clamps_lower_bound); + auto a = ControlLibraryRestyle::styleFor(ControlKind::Button, 0.2f, false); + auto b = ControlLibraryRestyle::styleFor(ControlKind::Button, 0.75f, false); + CHECK(a.cornerRadius == b.cornerRadius, "low DPI should clamp to floor"); + PASS(); +} + +void test_dpi_scale_clamps_upper_bound() { + TEST(dpi_scale_clamps_upper_bound); + auto a = ControlLibraryRestyle::styleFor(ControlKind::Button, 4.5f, false); + auto b = ControlLibraryRestyle::styleFor(ControlKind::Button, 3.0f, false); + CHECK(a.cornerRadius == b.cornerRadius, "high DPI should clamp to ceiling"); + PASS(); +} + +void test_tab_pressed_signal_exists() { + TEST(tab_pressed_signal_exists); + auto idle = ControlLibraryRestyle::styleFor(ControlKind::Tab, 1.0f, false); + auto pressed = ControlLibraryRestyle::styleFor(ControlKind::Tab, 1.0f, true); + CHECK(ControlLibraryRestyle::hasClearPressedSignal(idle, pressed), + "tab pressed signal should be clear"); + PASS(); +} + +void test_icon_contrast_meets_threshold() { + TEST(icon_contrast_meets_threshold); + auto s = ControlLibraryRestyle::styleFor(ControlKind::Button, 1.0f, false); + CHECK(s.iconContrast >= 3.0f, "icon contrast below threshold"); + PASS(); +} + +void test_all_controls_have_valid_contrast() { + TEST(all_controls_have_valid_contrast); + bool ok = true; + for (int i = 0; i < 6; ++i) { + auto s = ControlLibraryRestyle::styleFor(static_cast(i), 1.0f, false); + if (!ControlLibraryRestyle::validContrast(s)) ok = false; + } + CHECK(ok, "one or more controls fail contrast"); + PASS(); +} + +int main() { + std::cout << "Step 517: Control Library Restyle\n"; + + test_button_style_has_positive_depth(); // 1 + test_pressed_state_increases_depth_signal(); // 2 + test_toggle_has_larger_radius_than_tab(); // 3 + test_checkbox_has_contrast_compliance(); // 4 + test_dropdown_has_nonzero_border_width(); // 5 + test_menu_style_uses_small_radius(); // 6 + test_dpi_scale_increases_geometry(); // 7 + test_dpi_scale_clamps_lower_bound(); // 8 + test_dpi_scale_clamps_upper_bound(); // 9 + test_tab_pressed_signal_exists(); // 10 + test_icon_contrast_meets_threshold(); // 11 + test_all_controls_have_valid_contrast(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 7d4a16a..30c6bf1 100644 --- a/progress.md +++ b/progress.md @@ -8283,3 +8283,37 @@ reveal timing. - `editor/src/SignatureVisualIdentity.h` within header-size limit (`81` <= `600`) - `editor/tests/step516_test.cpp` within test-file size guidance (`123` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 517: Control Library Restyle +**Status:** PASS (12/12 tests) + +Implements shared control styling semantics for core widgets (buttons, toggles, +tabs, menus, dropdowns, checkboxes) with explicit pressed-depth signaling, +contrast thresholds, and DPI-stable geometry scaling. + +**Files added:** +- `editor/src/ControlLibraryRestyle.h` - control style module: + - per-control geometry/depth token synthesis + - pressed-state depth signal validation helper + - contrast validation for labels/icons + - DPI stability checks and scaling clamps +- `editor/tests/step517_test.cpp` - 12 tests covering: + - depth and pressed-signal behavior + - control-specific radius/border expectations + - contrast compliance + - DPI scaling growth and clamp edges + - all-control contrast regression sweep + +**Files modified:** +- `editor/CMakeLists.txt` - `step517_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step517_test` - PASS +- `./editor/build-native/step517_test` - PASS (12/12) +- `./editor/build-native/step516_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ControlLibraryRestyle.h` within header-size limit (`83` <= `600`) +- `editor/tests/step517_test.cpp` within test-file size guidance (`127` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`