|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include <SDL.h> |
| 4 | +#include <string> |
| 5 | +#include <functional> |
| 6 | +#include <string_view> |
| 7 | +#include <optional> |
| 8 | +#include <memory> |
| 9 | +#include <chrono> |
| 10 | + |
| 11 | +#include "TestElements.h" |
| 12 | + |
| 13 | +namespace RobotTest { |
| 14 | + |
| 15 | +// Extended button that tracks double clicks |
| 16 | +class DoubleClickButton : public TestElement { |
| 17 | +public: |
| 18 | + DoubleClickButton(SDL_Rect rect, Color color, std::string name) |
| 19 | + : rect_(rect), color_(color), name_(std::move(name)), |
| 20 | + clicked_(false), doubleClicked_(false), |
| 21 | + lastClickTime_(std::chrono::steady_clock::now() - std::chrono::seconds(10)) {} |
| 22 | + |
| 23 | + void draw(SDL_Renderer* renderer) const override { |
| 24 | + // Set color based on state |
| 25 | + Color drawColor = doubleClicked_ ? color_ : |
| 26 | + clicked_ ? color_.darken(0.3f) : color_.darken(0.6f); |
| 27 | + |
| 28 | + SDL_SetRenderDrawColor(renderer, drawColor.r, drawColor.g, drawColor.b, drawColor.a); |
| 29 | + SDL_RenderFillRect(renderer, &rect_); |
| 30 | + |
| 31 | + // Draw border |
| 32 | + SDL_SetRenderDrawColor(renderer, Color::White().r, Color::White().g, Color::White().b, Color::White().a); |
| 33 | + SDL_RenderDrawRect(renderer, &rect_); |
| 34 | + } |
| 35 | + |
| 36 | + [[nodiscard]] bool isInside(int x, int y) const override { |
| 37 | + return (x >= rect_.x && x < rect_.x + rect_.w && |
| 38 | + y >= rect_.y && y < rect_.y + rect_.h); |
| 39 | + } |
| 40 | + |
| 41 | + void handleClick() { |
| 42 | + auto now = std::chrono::steady_clock::now(); |
| 43 | + auto timeSinceLastClick = std::chrono::duration_cast<std::chrono::milliseconds>( |
| 44 | + now - lastClickTime_).count(); |
| 45 | + |
| 46 | + if (timeSinceLastClick < 300) { // Double-click threshold |
| 47 | + doubleClicked_ = true; |
| 48 | + } else { |
| 49 | + clicked_ = true; |
| 50 | + doubleClicked_ = false; |
| 51 | + } |
| 52 | + |
| 53 | + lastClickTime_ = now; |
| 54 | + } |
| 55 | + |
| 56 | + [[nodiscard]] bool wasClicked() const { |
| 57 | + return clicked_; |
| 58 | + } |
| 59 | + |
| 60 | + [[nodiscard]] bool wasDoubleClicked() const { |
| 61 | + return doubleClicked_; |
| 62 | + } |
| 63 | + |
| 64 | + void reset() override { |
| 65 | + clicked_ = false; |
| 66 | + doubleClicked_ = false; |
| 67 | + lastClickTime_ = std::chrono::steady_clock::now() - std::chrono::seconds(10); |
| 68 | + } |
| 69 | + |
| 70 | + [[nodiscard]] SDL_Rect getRect() const override { |
| 71 | + return rect_; |
| 72 | + } |
| 73 | + |
| 74 | + [[nodiscard]] std::string_view getName() const override { |
| 75 | + return name_; |
| 76 | + } |
| 77 | + |
| 78 | +private: |
| 79 | + SDL_Rect rect_; |
| 80 | + Color color_; |
| 81 | + std::string name_; |
| 82 | + bool clicked_; |
| 83 | + bool doubleClicked_; |
| 84 | + std::chrono::time_point<std::chrono::steady_clock> lastClickTime_; |
| 85 | +}; |
| 86 | + |
| 87 | +// Button that responds to right clicks |
| 88 | +class RightClickButton : public TestElement { |
| 89 | +public: |
| 90 | + RightClickButton(SDL_Rect rect, Color color, std::string name) |
| 91 | + : rect_(rect), color_(color), name_(std::move(name)), rightClicked_(false) {} |
| 92 | + |
| 93 | + void draw(SDL_Renderer* renderer) const override { |
| 94 | + // Set color based on state |
| 95 | + Color drawColor = rightClicked_ ? color_ : color_.darken(0.5f); |
| 96 | + |
| 97 | + SDL_SetRenderDrawColor(renderer, drawColor.r, drawColor.g, drawColor.b, drawColor.a); |
| 98 | + SDL_RenderFillRect(renderer, &rect_); |
| 99 | + |
| 100 | + // Draw border |
| 101 | + SDL_SetRenderDrawColor(renderer, Color::White().r, Color::White().g, Color::White().b, Color::White().a); |
| 102 | + SDL_RenderDrawRect(renderer, &rect_); |
| 103 | + } |
| 104 | + |
| 105 | + [[nodiscard]] bool isInside(int x, int y) const override { |
| 106 | + return (x >= rect_.x && x < rect_.x + rect_.w && |
| 107 | + y >= rect_.y && y < rect_.y + rect_.h); |
| 108 | + } |
| 109 | + |
| 110 | + void handleRightClick() { |
| 111 | + rightClicked_ = true; |
| 112 | + } |
| 113 | + |
| 114 | + [[nodiscard]] bool wasRightClicked() const { |
| 115 | + return rightClicked_; |
| 116 | + } |
| 117 | + |
| 118 | + void reset() override { |
| 119 | + rightClicked_ = false; |
| 120 | + } |
| 121 | + |
| 122 | + [[nodiscard]] SDL_Rect getRect() const override { |
| 123 | + return rect_; |
| 124 | + } |
| 125 | + |
| 126 | + [[nodiscard]] std::string_view getName() const override { |
| 127 | + return name_; |
| 128 | + } |
| 129 | + |
| 130 | +private: |
| 131 | + SDL_Rect rect_; |
| 132 | + Color color_; |
| 133 | + std::string name_; |
| 134 | + bool rightClicked_; |
| 135 | +}; |
| 136 | + |
| 137 | +// Scrollable area for testing scroll functionality |
| 138 | +class ScrollArea : public TestElement { |
| 139 | +public: |
| 140 | + ScrollArea(SDL_Rect rect, Color color, std::string name) |
| 141 | + : rect_(rect), color_(color), name_(std::move(name)), scrollY_(0), |
| 142 | + contentHeight_(500) // Content is taller than visible area |
| 143 | + {} |
| 144 | + |
| 145 | + void draw(SDL_Renderer* renderer) const override { |
| 146 | + // Draw visible area background |
| 147 | + SDL_SetRenderDrawColor(renderer, color_.r, color_.g, color_.b, color_.a); |
| 148 | + SDL_RenderFillRect(renderer, &rect_); |
| 149 | + |
| 150 | + // Draw border |
| 151 | + SDL_SetRenderDrawColor(renderer, Color::Black().r, Color::Black().g, Color::Black().b, Color::Black().a); |
| 152 | + SDL_RenderDrawRect(renderer, &rect_); |
| 153 | + |
| 154 | + // Set up a clipping rectangle for the content area |
| 155 | + SDL_Rect clipRect = rect_; |
| 156 | + SDL_RenderSetClipRect(renderer, &clipRect); |
| 157 | + |
| 158 | + // Draw content (series of colored lines) |
| 159 | + const int lineHeight = 20; |
| 160 | + const int numLines = contentHeight_ / lineHeight; |
| 161 | + |
| 162 | + for (int i = 0; i < numLines; ++i) { |
| 163 | + // Calculate line Y position with scroll offset |
| 164 | + int lineY = rect_.y + (i * lineHeight) - scrollY_; |
| 165 | + |
| 166 | + // Skip if line is outside visible area |
| 167 | + if (lineY + lineHeight < rect_.y || lineY > rect_.y + rect_.h) { |
| 168 | + continue; |
| 169 | + } |
| 170 | + |
| 171 | + // Alternate colors |
| 172 | + Color lineColor = (i % 2 == 0) ? Color::Blue() : Color::Green(); |
| 173 | + SDL_SetRenderDrawColor(renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a); |
| 174 | + |
| 175 | + SDL_Rect lineRect = {rect_.x + 2, lineY, rect_.w - 4, lineHeight}; |
| 176 | + SDL_RenderFillRect(renderer, &lineRect); |
| 177 | + |
| 178 | + // Draw line number |
| 179 | + // Text rendering would go here if we had SDL_ttf |
| 180 | + } |
| 181 | + |
| 182 | + // Draw scrollbar track |
| 183 | + SDL_Rect scrollTrack = { |
| 184 | + rect_.x + rect_.w - 15, |
| 185 | + rect_.y, |
| 186 | + 15, |
| 187 | + rect_.h |
| 188 | + }; |
| 189 | + SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255); |
| 190 | + SDL_RenderFillRect(renderer, &scrollTrack); |
| 191 | + |
| 192 | + // Draw scrollbar thumb |
| 193 | + float visibleRatio = static_cast<float>(rect_.h) / contentHeight_; |
| 194 | + int thumbHeight = static_cast<int>(rect_.h * visibleRatio); |
| 195 | + int thumbY = rect_.y + static_cast<int>((static_cast<float>(scrollY_) / |
| 196 | + (contentHeight_ - rect_.h)) * (rect_.h - thumbHeight)); |
| 197 | + |
| 198 | + SDL_Rect scrollThumb = { |
| 199 | + rect_.x + rect_.w - 15, |
| 200 | + thumbY, |
| 201 | + 15, |
| 202 | + thumbHeight |
| 203 | + }; |
| 204 | + SDL_SetRenderDrawColor(renderer, 150, 150, 150, 255); |
| 205 | + SDL_RenderFillRect(renderer, &scrollThumb); |
| 206 | + |
| 207 | + // Reset clipping rectangle |
| 208 | + SDL_RenderSetClipRect(renderer, nullptr); |
| 209 | + } |
| 210 | + |
| 211 | + [[nodiscard]] bool isInside(int x, int y) const override { |
| 212 | + return (x >= rect_.x && x < rect_.x + rect_.w && |
| 213 | + y >= rect_.y && y < rect_.y + rect_.h); |
| 214 | + } |
| 215 | + |
| 216 | + void handleScroll(int scrollAmount) { |
| 217 | + scrollY_ += scrollAmount * 15; // Scale the scroll amount |
| 218 | + |
| 219 | + // Clamp scrollY to valid range |
| 220 | + if (scrollY_ < 0) { |
| 221 | + scrollY_ = 0; |
| 222 | + } |
| 223 | + |
| 224 | + int maxScroll = contentHeight_ - rect_.h; |
| 225 | + if (maxScroll < 0) { |
| 226 | + maxScroll = 0; |
| 227 | + } |
| 228 | + |
| 229 | + if (scrollY_ > maxScroll) { |
| 230 | + scrollY_ = maxScroll; |
| 231 | + } |
| 232 | + } |
| 233 | + |
| 234 | + [[nodiscard]] int getScrollY() const { |
| 235 | + return scrollY_; |
| 236 | + } |
| 237 | + |
| 238 | + void reset() override { |
| 239 | + scrollY_ = 0; |
| 240 | + } |
| 241 | + |
| 242 | + [[nodiscard]] SDL_Rect getRect() const override { |
| 243 | + return rect_; |
| 244 | + } |
| 245 | + |
| 246 | + [[nodiscard]] std::string_view getName() const override { |
| 247 | + return name_; |
| 248 | + } |
| 249 | + |
| 250 | +private: |
| 251 | + SDL_Rect rect_; |
| 252 | + Color color_; |
| 253 | + std::string name_; |
| 254 | + int scrollY_; |
| 255 | + int contentHeight_; |
| 256 | +}; |
| 257 | + |
| 258 | +// Factory function to create a double-click button |
| 259 | +inline std::unique_ptr<DoubleClickButton> createDoubleClickButton( |
| 260 | + int x, int y, int width, int height, |
| 261 | + Color color, std::string name) |
| 262 | +{ |
| 263 | + return std::make_unique<DoubleClickButton>( |
| 264 | + SDL_Rect{x, y, width, height}, |
| 265 | + color, |
| 266 | + std::move(name) |
| 267 | + ); |
| 268 | +} |
| 269 | + |
| 270 | +// Factory function to create a right-click button |
| 271 | +inline std::unique_ptr<RightClickButton> createRightClickButton( |
| 272 | + int x, int y, int width, int height, |
| 273 | + Color color, std::string name) |
| 274 | +{ |
| 275 | + return std::make_unique<RightClickButton>( |
| 276 | + SDL_Rect{x, y, width, height}, |
| 277 | + color, |
| 278 | + std::move(name) |
| 279 | + ); |
| 280 | +} |
| 281 | + |
| 282 | +// Factory function to create a scroll area |
| 283 | +inline std::unique_ptr<ScrollArea> createScrollArea( |
| 284 | + int x, int y, int width, int height, |
| 285 | + Color color, std::string name) |
| 286 | +{ |
| 287 | + return std::make_unique<ScrollArea>( |
| 288 | + SDL_Rect{x, y, width, height}, |
| 289 | + color, |
| 290 | + std::move(name) |
| 291 | + ); |
| 292 | +} |
| 293 | + |
| 294 | +} // namespace RobotTest |
0 commit comments