Index: gui/button.cpp
===================================================================
--- gui/button.cpp	(revision e1f88a9873d22257993a07f9c790d1739e9961db)
+++ gui/button.cpp	(revision e1f88a9873d22257993a07f9c790d1739e9961db)
@@ -0,0 +1,119 @@
+#include "button.hpp"
+
+#include <SDL2/SDL_ttf.h>
+#include <SDL2/SDL2_gfxPrimitives.h>
+
+#include "../vulkan-game.hpp"
+
+// TODO: Figure out a good way to return errors instead of just printing them
+// Probably throw an exception
+// Make sure to cleanup anythign that was initialized correctly before the error
+// TODO: Support better positioning options (e.g. align to left, right, top, bottom, center,
+// and offsets from those positions)
+Button::Button(string label, int x, int y, int padding, uint32_t color, uint32_t textColor,
+      VulkanGame& gameInfo, SDL_Renderer& renderer,
+      void (*onMouseClick)(VulkanGame& gameInfo),
+      void (*onMouseEnter)(UIElement& element),
+      void (*onMouseLeave)(UIElement& element)) :
+      UIElement(x, y, 0, 0, renderer, onMouseClick, onMouseEnter, onMouseLeave),
+      color(color),
+      focused(false),
+      gameInfo(gameInfo) {
+
+   SDL_Color sdlTextColor {
+      static_cast<Uint8>((textColor >> 24) & 0xFF),
+      static_cast<Uint8>((textColor >> 16) & 0xFF),
+      static_cast<Uint8>((textColor >> 8) & 0xFF),
+      static_cast<Uint8>(textColor & 0xFF)
+   };
+
+   SDL_Surface* labelSurface = TTF_RenderText_Blended(this->gameInfo.proggyFont, label.c_str(),
+      sdlTextColor);
+   if (labelSurface == nullptr) {
+      cout << "Unable to render text surface! SDL_ttf Error: " << TTF_GetError() << endl;
+   }
+
+   this->labelTexture = SDL_CreateTextureFromSurface(&this->renderer, labelSurface);
+   if (this->labelTexture == nullptr) {
+      cout << "Unable to create texture from rendered text! SDL Error: " << SDL_GetError() << endl;
+      // SDL_FreeSurface(labelSurface);
+   }
+
+   SDL_FreeSurface(labelSurface);
+
+   TTF_SizeText(this->gameInfo.proggyFont, label.c_str(), &this->labelWidth, &this->labelHeight);
+
+   this->width = this->labelWidth + padding;
+   this->height = this->labelHeight + padding;
+
+   uint32_t rgb = color & 0xFFFFFF00;
+   uint32_t a = color & 0xFF;
+
+   this->focusColor = (int)(rgb * 1.6) | a;
+}
+
+Button::~Button() {
+   if (this->labelTexture != nullptr) {
+      SDL_DestroyTexture(this->labelTexture);
+      this->labelTexture = nullptr;
+   }
+}
+
+void Button::init() {
+   this->focused = false;
+}
+
+void Button::render(int x, int y) {
+   uint32_t cur_color = this->focused ? this->focusColor : this->color;
+
+   uint8_t colorR = (cur_color >> 24) & 0xFF;
+   uint8_t colorG = (cur_color >> 16) & 0xFF;
+   uint8_t colorB = (cur_color >> 8) & 0xFF;
+   uint8_t colorA = cur_color & 0xFF;
+
+   boxRGBA(&this->renderer, this->x + x, this->y + y, this->x + this->width, this->y + this->height,
+      colorR, colorG, colorB, colorA);
+
+   SDL_Rect rect = {
+         this->x + (this->width - this->labelWidth) / 2 + x,
+         this->y + (this->height - this->labelHeight) / 2 + y,
+         this->labelWidth,
+         this->labelHeight
+   };
+
+   SDL_RenderCopy(&this->renderer, this->labelTexture, nullptr, &rect);
+}
+
+void Button::handleEvent(UIEvent& e) {
+   switch(e.type) {
+      case UI_EVENT_MOUSEMOTION:
+         if (this->x < e.mouse.x && e.mouse.x < this->x + this->width &&
+               this->y < e.mouse.y && e.mouse.y < this->y + this->height) {
+            if (!this->focused) {
+               this->focused = true;
+               if (this->onMouseEnter != nullptr) {
+                  this->onMouseEnter(*this);
+               }
+            }
+         } else if (this->focused) {
+            this->focused = false;
+            if (this->onMouseLeave != nullptr) {
+               this->onMouseLeave(*this);
+            }
+         }
+         break;
+      case UI_EVENT_MOUSEBUTTONDOWN:
+         break;
+      case UI_EVENT_MOUSEBUTTONUP:
+         if (this->x < e.mouse.x && e.mouse.x < this->x + this->width &&
+               this->y < e.mouse.y && e.mouse.y < this->y + this->height) {
+            if (this->onMouseClick != nullptr) {
+               this->onMouseClick(this->gameInfo);
+            }
+         }
+         break;
+      default:
+         //cout << "Unhandled UI event: " << e.type << endl;
+         break;
+   }
+}
