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:
- Three different shapes: a rectangle for normal state, a circle for hover state, and a star for clicked state.
- Methods to create these shapes using
SDL_Point
vectors. - A
currentState
to track the button's state. - Overridden
HandleMouseEnter()
,HandleMouseExit()
, andHandleLeftClick()
methods to update the current state. - 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.