diff --git a/CMakeLists.txt b/CMakeLists.txt index 885e8c8..db856ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(quip SHARED src/quip.cpp src/quip.h) -target_link_libraries(quip PUBLIC SDL3 SDL3_ttf) +target_link_libraries(quip PUBLIC SDL3 SDL3_ttf SDL3_image) target_include_directories(quip PUBLIC $ diff --git a/example/dingus.png b/example/dingus.png new file mode 100644 index 0000000..f7fc347 Binary files /dev/null and b/example/dingus.png differ diff --git a/example/example.cpp b/example/example.cpp index 44cada0..de25ee9 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -1,7 +1,33 @@ #include int main() { - Quip::Window window("Quip Window", 800, 600); + bool goingDown = true, goingRight = true; + Quip::Window window("Quip Window", 800, 600, [&window, &goingDown, &goingRight]() { + auto* imagewidget = window.getChild("image"); + auto& image = std::get(*imagewidget); + if (goingDown) { + image.ypos ++; + if (image.ypos + image.height > window.height) { + goingDown = false; + } + } else { + image.ypos --; + if (image.ypos < 0) { + goingDown = true; + } + } + if (goingRight) { + image.xpos ++; + if (image.xpos + image.width > window.width) { + goingRight = false; + } + } else { + image.xpos --; + if (image.xpos < 0) { + goingRight = true; + } + } + }); int buttonPresses = 0; window.addChild("textLabel", Quip::Widget::TextLabel("Hi there!", 10, 10)); window.addChild("button", Quip::Widget::Button("Click me!", [&window, &buttonPresses]() { @@ -11,5 +37,6 @@ int main() { std::get(*widget).text = "You have clicked the button " + std::to_string(buttonPresses) + " times!"; } }, 10, 50)); + window.addChild("image", Quip::Widget::Image("dingus.png", 300, 300, 100, 100)); return window.run(); } diff --git a/src/quip.cpp b/src/quip.cpp index ffd4dd2..b478f06 100644 --- a/src/quip.cpp +++ b/src/quip.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -21,7 +22,27 @@ namespace Quip { SDL_DestroySurface(surface); } - Button::Button(std::string text, std::function callback, int xpos, int ypos) : text(std::move(text)), callback(std::move(callback)), xpos(xpos), ypos(ypos) {} + Image::Image(const std::string& filepath, int xpos, int ypos, int width, int height) : width(width), height(height), xpos(xpos), ypos(ypos) { + surface = IMG_Load(filepath.c_str()); + if (surface == nullptr) { + Window().error(4, "Couldn't load image " + filepath); + } + if (width < 0) { + this->width = surface->w; + } + if (height < 0) { + this->height = surface->h; + } + } + + void Image::render(const Window &window) const { + SDL_Texture* texture = SDL_CreateTextureFromSurface(window.renderer, surface); + SDL_FRect destRect = {static_cast(xpos), static_cast(ypos), static_cast(width), static_cast(height)}; + SDL_RenderTexture(window.renderer, texture, nullptr, &destRect); + SDL_DestroyTexture(texture); + } + + Button::Button(std::string text, std::function callback, int xpos, int ypos) : text(std::move(text)), onCLick(std::move(callback)), xpos(xpos), ypos(ypos) {} void Button::render(const Window &window) const { SDL_SetRenderDrawColor(window.renderer, 40, 60, 80, 255); // Dark gray background SDL_FRect backgroundRect = {static_cast(xpos), static_cast(ypos), static_cast(width), static_cast(height)}; @@ -38,7 +59,7 @@ namespace Quip { } Window::Window() = default; - Window::Window(std::string title, int width, int height) : title(std::move(title)), width(width), height(height) { + Window::Window(std::string title, int width, int height, const std::function& onFrame) : title(std::move(title)), width(width), height(height), onFrame(onFrame) { windowCount ++; if (width <= 0 || height <= 0) { error(2, "width is " + std::to_string(width) + " and height is " + std::to_string(height)); @@ -60,7 +81,7 @@ namespace Quip { } } - void Window::error(int errCode, const std::string& context) { + void Window::error(int errCode, const std::string& context) const { SDL_DestroyWindow(window); SDL_DestroyRenderer(renderer); SDL_Quit(); @@ -79,6 +100,9 @@ namespace Quip { case 3: std::cerr << "Widget name error" << std::endl; break; + case 4: + std::cerr << "File error" << std::endl; + break; } if (!context.empty()) { std::cerr << "Context: " << context << std::endl; @@ -97,6 +121,8 @@ namespace Quip { case 3: throw std::runtime_error("Quip::Window() Widget name error"); break; + case 4: + throw std::runtime_error("Quip::Window() File error"); } } @@ -117,6 +143,7 @@ namespace Quip { int Window::run() { while (true) { + onFrame(); while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_QUIT: @@ -133,8 +160,8 @@ namespace Quip { // Check if click is inside button bounds if (mouseX >= button.xpos && mouseX <= button.xpos + button.width && mouseY >= button.ypos && mouseY <= button.ypos + button.height) { - if (button.callback) { - button.callback(); + if (button.onCLick) { + button.onCLick(); } } } @@ -149,6 +176,8 @@ namespace Quip { std::get(widget).render(*this); } else if (std::holds_alternative(widget)) { std::get(widget).render(*this); + } else if (std::holds_alternative(widget)) { + std::get(widget).render(*this); } } if (!SDL_RenderPresent(renderer)) { diff --git a/src/quip.h b/src/quip.h index f073644..8e65194 100644 --- a/src/quip.h +++ b/src/quip.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -19,17 +20,25 @@ namespace Quip { explicit TextLabel(std::string text, int xpos, int ypos); TextLabel(); }; + class Image { + SDL_Surface* surface; + public: + int width = 0, height = 0; + int xpos = 0, ypos = 0; + void render(const Window &window) const; + Image(const std::string& filepath, int xpos, int ypos, int width = -1, int height = -1); + }; class Button { public: std::string text; - std::function callback; + std::function onCLick; int xpos = 0, ypos = 0; int width = 100, height = 30; void render(const Window &window) const; explicit Button(std::string text, std::function callback, int xpos, int ypos); }; - typedef std::variant Widget; + typedef std::variant Widget; } class Window { @@ -41,11 +50,12 @@ namespace Quip { std::string title; int width, height; std::map widgets; - void error(int errCode = 0, const std::string& context = ""); + std::function onFrame; + void error(int errCode = 0, const std::string& context = "") const; int run(); void addChild(const std::string& id, const Widget::Widget& widget); Widget::Widget* getChild(const std::string& id); - explicit Window(std::string title, int width = 800, int height = 600); + explicit Window(std::string title, int width = 800, int height = 600, const std::function& onFrame = nullptr); Window(); };