Building Interactive Buttons

Changing Button Shape on Interaction

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

Abstract art representing computer programming

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.

Answers to questions are automatically generated and may not have been reviewed.

sdl2-promo.jpg
Part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 53 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved