Changing Button Shape on Interaction

Can I implement a button that changes shape when hovered or clicked?

Absolutely! Implementing a button that changes shape when hovered or clicked can add a dynamic and interactive feel to your UI. We can achieve this by extending our existing Button class and modifying its rendering logic. Let's walk through the process step by step.

First, let's create a ShapeChangingButton class that inherits from our Button class:

#include <SDL.h>
#include <vector>

class ShapeChangingButton : public Button {
public:
  ShapeChangingButton(int x, int y, int w,
                      int h)
    : Button{x, y, w, h} {
    normalShape = CreateRectShape(w, h);
    hoverShape =
        CreateCircleShape(std::min(w, h) / 2);
    clickShape =
        CreateStarShape(std::min(w, h) / 2);
  }

  void Render(SDL_Surface* Surface) override {
    SDL_FillRect(Surface, nullptr,
                 SDL_MapRGB(Surface->format,
                            255, 255, 255));

    const std::vector<SDL_Point>& currentShape =
        GetCurrentShape();
    RenderShape(Surface, currentShape);
  }

protected:
  void HandleMouseEnter() override {
    Button::HandleMouseEnter();
    currentState = ButtonState::Hover;
  }

  void HandleMouseExit() override {
    Button::HandleMouseExit();
    currentState = ButtonState::Normal;
  }

  void HandleLeftClick() override {
    Button::HandleLeftClick();
    currentState = ButtonState::Clicked;
  }

private:
  enum class ButtonState {
    Normal,
    Hover,
    Clicked
  };

  std::vector<SDL_Point>
  CreateRectShape(int w, int h) {
    return {{0, 0}, {w, 0}, {w, h}, {0, h}};
  }

  std::vector<SDL_Point> CreateCircleShape(
      int radius) {
    std::vector<SDL_Point> points;
    for (int i = 0; i < 360; i += 10) {
      int x = static_cast<int>(
        radius * cos(i * M_PI / 180));
      int y = static_cast<int>(
        radius * sin(i * M_PI / 180));
      points.push_back(
          {x + radius, y + radius});
    }
    return points;
  }

  std::vector<SDL_Point> CreateStarShape(
      int radius) {
    std::vector<SDL_Point> points;
    for (int i = 0; i < 10; i++) {
      double r =
          (i % 2 == 0) ? radius : radius / 2;
      int x = static_cast<int>(
        r * cos(i * M_PI / 5));
      int y = static_cast<int>(
        r * sin(i * M_PI / 5));
      points.push_back(
          {x + radius, y + radius});
    }
    return points;
  }

  void RenderShape(
      SDL_Surface* Surface,
      const std::vector<SDL_Point>& shape) {
    SDL_Point center = {Rect.x + Rect.w / 2,
                        Rect.y + Rect.h / 2};
    std::vector<SDL_Point> translatedShape;
    for (const auto& point : shape) {
      translatedShape.push_back(
      {point.x + center.x - Rect.w / 2,
       point.y + center.y - Rect.h / 2});
    }
    SDL_Color color = GetCurrentColor();
    for (size_t i = 0;
         i < translatedShape.size(); i++) {
      SDL_Point start = translatedShape[i];
      SDL_Point end = translatedShape
          [(i + 1) % translatedShape.size()];
      SDL_DrawLine(Surface, start.x, start.y,
                   end.x, end.y, color);
    }
  }

  const std::vector<SDL_Point>&
  GetCurrentShape() const {
    switch (currentState) {
    case ButtonState::Hover:
      return hoverShape;
    case ButtonState::Clicked:
      return clickShape;
    default:
      return normalShape;
    }
  }

  SDL_Color GetCurrentColor() const {
    switch (currentState) {
    case ButtonState::Hover:
      return {255, 0, 0, 255}; // Red
    case ButtonState::Clicked:
      return {0, 255, 0, 255}; // Green
    default:
      return {0, 0, 255, 255}; // Blue
    }
  }

  std::vector<SDL_Point> normalShape;
  std::vector<SDL_Point> hoverShape;
  std::vector<SDL_Point> clickShape;
  ButtonState currentState{ButtonState::Normal};
};

In this ShapeChangingButton class, we've added:

  1. Three different shapes: a rectangle for normal state, a circle for hover state, and a star for clicked state.
  2. Methods to create these shapes using SDL_Point vectors.
  3. A currentState to track the button's state.
  4. Overridden HandleMouseEnter(), HandleMouseExit(), and HandleLeftClick() methods to update the current state.
  5. A custom Render() method that draws the appropriate shape based on the current state.

Now, let's see how we can use this ShapeChangingButton in our main game loop:

#include <SDL.h>

int main(int argc, char* argv[]) {
  SDL_Init(SDL_INIT_VIDEO);

  Window GameWindow;
  ShapeChangingButton MyButton{50, 50, 100,
                               100};

  SDL_Event Event;
  bool shouldQuit{false};
  while (!shouldQuit) {
    while (SDL_PollEvent(&Event)) {
      MyButton.HandleEvent(Event);
      if (Event.type == SDL_QUIT) {
        shouldQuit = true;
      }
    }

    GameWindow.Render();
    MyButton.Render(GameWindow.GetSurface());
    GameWindow.Update();

    SDL_Delay(16); // Cap at ~60 FPS
  }

  SDL_Quit();
  return 0;
}

This main loop creates a ShapeChangingButton and handles its events. The button will now change shape:

  • Rectangle (blue) when in normal state
  • Circle (red) when hovered over
  • Star (green) when clicked

This implementation provides a visually engaging button that responds to user interactions by changing both its shape and color. You can further customize this by adding more shapes, adjusting colors, or even implementing smooth transitions between shapes for an even more dynamic effect.

Remember to include the necessary headers (like <cmath> for trigonometric functions) and implement any missing utility functions (like SDL_DrawLine()) that this code assumes are available.

Creating SDL2 Buttons

Learn to create interactive buttons in SDL2 and manage communication between different UI components.

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Understanding Incomplete Types in C++ Forward Declarations
What is an "incomplete type" in C++ and why does it prevent calling functions in a header file?
C++ Dangling References: Lifetimes and Undefined Behavior
What happens if an object is destroyed before a reference to it, like if UI is destroyed before Button?
How SDL_PushEvent() Works in SDL2
What exactly does SDL_PushEvent() do in SDL2, and where does the event go?
Handling Right and Middle Mouse Clicks in SDL2
How would I handle right-clicks or middle-clicks on an SDL2 button?
The override Keyword in C++ Explained
What does the override keyword do in C++ when used with class methods?
Pointers vs References for Component Communication in C++: Safety and Use Cases
Is passing raw pointers safer or better than references for parent/child communication in C++?
Adding Tooltips to SDL Buttons
Is it possible to add tooltips to SDL buttons when hovering?
Creating Image Buttons in SDL
Can I create a button with an image instead of a solid color?
Animating Button Clicks in SDL
How would I implement a button that triggers an animation when clicked?
Adding Keyboard Shortcuts to SDL Buttons
How can I add keyboard shortcuts to trigger button actions?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant