From 8a33c932c4c84d95851dec6b6efeab5bc902b966 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Wed, 1 Jul 2026 15:40:52 +0200 Subject: [PATCH 1/3] Draw UI with OpenGL, delete Surf_Display Keep small surface per UI component that is drawn with OpenGL Avoids big 4K penalty while keeping change small --- CGame.cpp | 3 -- CGame.h | 6 ++-- CGame_Init.cpp | 5 --- CGame_Render.cpp | 78 +++++++++++++++++++++++++++-------------- CIO/CControlContainer.h | 11 ++++-- CIO/CMinimapWindow.cpp | 23 ++++++++++++ CIO/CMinimapWindow.h | 15 ++++++++ CIO/CWindow.cpp | 20 ++++++----- CIO/CWindow.h | 5 +-- Texture.cpp | 14 ++------ Texture.h | 12 +++++++ callbacks.cpp | 29 +++++++-------- 12 files changed, 143 insertions(+), 78 deletions(-) create mode 100644 CIO/CMinimapWindow.cpp create mode 100644 CIO/CMinimapWindow.h diff --git a/CGame.cpp b/CGame.cpp index 18f90ed..d322c63 100644 --- a/CGame.cpp +++ b/CGame.cpp @@ -77,9 +77,6 @@ int CGame::Execute() void CGame::RenderPresent() { - displayTexture_.upload(Surf_Display->pixels); - displayTexture_.Draw(Rect(0, 0, GameResolution.x, GameResolution.y)); - const auto& cursorImg = Cursor.clicked ? (Cursor.button.right ? cross_ : cursorClicked_) : cursor_; cursorImg.Draw(Cursor.pos); diff --git a/CGame.h b/CGame.h index f4a78e5..249b4ac 100644 --- a/CGame.h +++ b/CGame.h @@ -6,7 +6,6 @@ #pragma once #include "CIO/CFont.h" -#include "SdlSurface.h" #include "Texture.h" #include #include @@ -27,8 +26,6 @@ class CGame bool Running; bool showLoadScreen; - SdlSurface Surf_Display; - Texture displayTexture_; SDL_GLContext glContext_ = nullptr; SdlWindow window_; @@ -52,6 +49,8 @@ class CGame Texture cursor_; Texture cursorClicked_; Texture cross_; + Texture fpsTex_; ///< Texture for the FPS counter + Texture mapTex_; ///< Texture for the map/terrain (Surf_Map may be 8-bit) // structure for mouse cursor struct @@ -111,6 +110,5 @@ class CGame CMap* getMapObj(); void delMapObj(); void enterEditor(const boost::filesystem::path& filepath); - SDL_Surface* getDisplaySurface() const { return Surf_Display.get(); }; auto getRes() const { return GameResolution; } }; diff --git a/CGame_Init.cpp b/CGame_Init.cpp index 7f437db..8d5617c 100644 --- a/CGame_Init.cpp +++ b/CGame_Init.cpp @@ -38,8 +38,6 @@ bool CGame::CreateWindow() SDL_ShowWindow(window_.get()); ApplyWindowChanges(); - if(!displayTexture_.isValid() || !Surf_Display) - return false; SetAppIcon(); @@ -125,9 +123,6 @@ void CGame::UpdateDisplaySize(const Extent& newSize) appliedResolution_ = GameResolution; appliedFullscreen_ = fullscreen; - Surf_Display = makeRGBSurface(GameResolution.x, GameResolution.y, true); - displayTexture_.createEmpty(GameResolution); - setGLViewport(); for(auto& menu : Menus) { diff --git a/CGame_Render.cpp b/CGame_Render.cpp index 93da104..1f5dd34 100644 --- a/CGame_Render.cpp +++ b/CGame_Render.cpp @@ -40,7 +40,6 @@ void CGame::SetAppIcon() void CGame::Render() { glClear(GL_COLOR_BUFFER_BIT); - SDL_FillRect(Surf_Display.get(), nullptr, SDL_MapRGBA(Surf_Display->format, 0, 0, 0, 0)); // if the S2 loading screen is shown, render only this until user clicks a mouse button if(showLoadScreen) @@ -53,33 +52,35 @@ void CGame::Render() // render the map if active if(MapObj && MapObj->isActive()) { - CSurface::Draw(Surf_Display, MapObj->getSurface(), 0, 0); - std::array textBuffer; - // text for x and y of vertex (shown in upper left corner) - std::snprintf(textBuffer.data(), textBuffer.size(), "%d %d", MapObj->getVertexX(), MapObj->getVertexY()); - CFont::writeText(Surf_Display, textBuffer.data(), Position(20, 20)); - // text for MinReduceHeight and MaxRaiseHeight - std::snprintf(textBuffer.data(), textBuffer.size(), - "min. height: %#04x/0x3C max. height: %#04x/0x3C NormalNull: 0x0A", - MapObj->getMinReduceHeight(), MapObj->getMaxRaiseHeight()); - CFont::writeText(Surf_Display, textBuffer.data(), Position(100, 20)); - // text for MovementLocked - if(MapObj->isHorizontalMovementLocked() && MapObj->isVerticalMovementLocked()) - CFont::writeText(Surf_Display, "Movement locked (F9 or F10 to unlock)", Position(20, 40), FontSize::Large, - FontColor::Orange); - else if(MapObj->isHorizontalMovementLocked()) - CFont::writeText(Surf_Display, "Horizontal movement locked (F9 to unlock)", Position(20, 40), - FontSize::Large, FontColor::Orange); - else if(MapObj->isVerticalMovementLocked()) - CFont::writeText(Surf_Display, "Vertical movement locked (F10 to unlock)", Position(20, 40), - FontSize::Large, FontColor::Orange); + if(auto* mapSurf = MapObj->getSurface()) + { + std::array textBuffer; + std::snprintf(textBuffer.data(), textBuffer.size(), "%d %d", MapObj->getVertexX(), MapObj->getVertexY()); + CFont::writeText(mapSurf, textBuffer.data(), 20, 20); + std::snprintf(textBuffer.data(), textBuffer.size(), + "min. height: %#04x/0x3C max. height: %#04x/0x3C NormalNull: 0x0A", + MapObj->getMinReduceHeight(), MapObj->getMaxRaiseHeight()); + CFont::writeText(mapSurf, textBuffer.data(), 100, 20); + if(MapObj->isHorizontalMovementLocked() && MapObj->isVerticalMovementLocked()) + CFont::writeText(mapSurf, "Movement locked (F9 or F10 to unlock)", 20, 40, FontSize::Large, + FontColor::Orange); + else if(MapObj->isHorizontalMovementLocked()) + CFont::writeText(mapSurf, "Horizontal movement locked (F9 to unlock)", 20, 40, FontSize::Large, + FontColor::Orange); + else if(MapObj->isVerticalMovementLocked()) + CFont::writeText(mapSurf, "Vertical movement locked (F10 to unlock)", 20, 40, FontSize::Large, + FontColor::Orange); + + mapTex_.load(mapSurf); + mapTex_.Draw(Rect(0, 0, GameResolution.x, GameResolution.y)); + } } // render active menus for(auto& Menu : Menus) { if(Menu->isActive()) - CSurface::Draw(Surf_Display, Menu->getSurface(), 0, 0); + Menu->getTexture().Draw(Rect(0, 0, GameResolution.x, GameResolution.y)); } // render windows ordered by priority @@ -96,7 +97,7 @@ void CGame::Render() for(auto& Window : Windows) { if(Window->getPriority() == actualPriority) - CSurface::Draw(Surf_Display, Window->getSurface(), Window->getX(), Window->getY()); + Window->getTexture().Draw(Position(Window->getX(), Window->getY())); } } @@ -105,7 +106,6 @@ void CGame::Render() #endif ++framesPassedSinceLastFps; - const auto curTicks = SDL_GetTicks(); const auto diffTicks = curTicks - lastFpsTick; if(diffTicks > 1000) @@ -114,9 +114,35 @@ void CGame::Render() framesPassedSinceLastFps = 0; lastFpsTick = curTicks; } - CSurface::Draw(Surf_Display, lastFps.getSurface(), 0, 0); + { + if(auto* fpsSurf = lastFps.getSurface()) + { + fpsTex_.load(fpsSurf); + glBindTexture(GL_TEXTURE_2D, fpsTex_.getHandle()); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2i(0, 0); + glTexCoord2f(1, 0); + glVertex2i(fpsSurf->w, 0); + glTexCoord2f(1, 1); + glVertex2i(fpsSurf->w, fpsSurf->h); + glTexCoord2f(0, 1); + glVertex2i(0, fpsSurf->h); + glEnd(); + } + } - RenderPresent(); + // ---- 5. Cursor on top of everything ---- + { + const auto& cursorImg = Cursor.clicked ? (Cursor.button.right ? cross_ : cursorClicked_) : cursor_; + cursorImg.Draw(Cursor.pos); + } + + SDL_GL_SwapWindow(window_.get()); + +#ifdef _ADMINMODE + FrameCounter++; +#endif if(msWait) SDL_Delay(msWait); diff --git a/CIO/CControlContainer.h b/CIO/CControlContainer.h index dacc91f..7802e61 100644 --- a/CIO/CControlContainer.h +++ b/CIO/CControlContainer.h @@ -5,6 +5,7 @@ #pragma once +#include "../Texture.h" #include "defines.h" #include #include @@ -45,6 +46,7 @@ class CControlContainer protected: SdlSurface surface; bool needRender = true; + Texture surfaceTex_; ///< Texture for this container's surface (used by CWindow) void renderElements(); auto& getTextFields() { return textfields; } @@ -54,17 +56,20 @@ class CControlContainer public: CControlContainer(int pic_background); CControlContainer(int pic_background, Extent borderBeginSize, Extent borderEndSize); - ~CControlContainer() noexcept; + virtual ~CControlContainer() noexcept; // Access Extent getBorderSize() const { return borderBeginSize + borderEndSize; } void setBackgroundPicture(int pic_background); virtual void setMouseData(SDL_MouseMotionEvent motion); virtual void setMouseData(SDL_MouseButtonEvent button); void setKeyboardData(const SDL_KeyboardEvent& key); - SDL_Surface* getSurface() + /// Get the texture for this container's rendered content. + const Texture& getTexture() { render(); - return surface.get(); + if(surface) + surfaceTex_.load(surface.get()); + return surfaceTex_; } void setWaste() { waste = true; } bool isWaste() const { return waste; } diff --git a/CIO/CMinimapWindow.cpp b/CIO/CMinimapWindow.cpp new file mode 100644 index 0000000..0ecbbd7 --- /dev/null +++ b/CIO/CMinimapWindow.cpp @@ -0,0 +1,23 @@ +// Copyright (C) 2026 - 2026 Settlers Freaks +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "CMinimapWindow.h" +#include "../CGame.h" +#include "../CMap.h" +#include "../globals.h" + +bool CMinimapWindow::render() +{ + // Always re-render: the minimap terrain overlay may have changed + needRender = true; + // Draw window chrome (frame, title, close button, background, child elements) + CWindow::render(); + // Draw minimap terrain overlay on top of the chrome + if(surface) + { + if(auto* map = global::s2->getMapObj()) + map->drawMinimap(surface.get()); + } + return true; +} diff --git a/CIO/CMinimapWindow.h b/CIO/CMinimapWindow.h new file mode 100644 index 0000000..2d16dd6 --- /dev/null +++ b/CIO/CMinimapWindow.h @@ -0,0 +1,15 @@ +// Copyright (C) 2026 - 2026 Settlers Freaks +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "CWindow.h" + +class CMinimapWindow final : public CWindow +{ + bool render() final; + +public: + using CWindow::CWindow; +}; diff --git a/CIO/CWindow.cpp b/CIO/CWindow.cpp index 66146a5..ec4fa36 100644 --- a/CIO/CWindow.cpp +++ b/CIO/CWindow.cpp @@ -42,8 +42,10 @@ CWindow::CWindow(void callback(int), int callbackQuitMessage, Position pos, Exte static Position makePos(WindowPos pos, Extent size) { if(pos == WindowPos::Center) - return Position(global::s2->getDisplaySurface()->w, global::s2->getDisplaySurface()->h) / 2 - size / 2; - else + { + const auto res = global::s2->getRes(); + return Position(res.x / 2, res.y / 2) - size / 2; + } else return {}; } @@ -91,12 +93,14 @@ void CWindow::setMouseData(SDL_MouseMotionEvent motion) // make sure to not move the window outside the display surface if(x_ < 0) x_ = 0; - if(x_ + w_ >= global::s2->getDisplaySurface()->w) //-V807 - x_ = global::s2->getDisplaySurface()->w - w_ - 1; - if(y_ < 0) - y_ = 0; - if(y_ + h_ >= global::s2->getDisplaySurface()->h) - y_ = global::s2->getDisplaySurface()->h - h_ - 1; + { + const auto res = global::s2->getRes(); + const int resW = res.x, resH = res.y; + if(x_ + w_ >= resW) //-V807 + x_ = resW - w_ - 1; + if(y_ + h_ >= resH) + y_ = resH - h_ - 1; + } } // check whats happen to the close button diff --git a/CIO/CWindow.h b/CIO/CWindow.h index db92045..2faf97e 100644 --- a/CIO/CWindow.h +++ b/CIO/CWindow.h @@ -12,7 +12,7 @@ enum class WindowPos Center }; -class CWindow final : public CControlContainer +class CWindow : public CControlContainer { friend class CDebug; @@ -43,7 +43,8 @@ class CWindow final : public CControlContainer void (*callback_)(int); int callbackQuitMessage; - bool render() final; +protected: + bool render() override; public: CWindow(void callback(int), int callbackQuitMessage, Position pos, Extent size, const char* title = nullptr, diff --git a/Texture.cpp b/Texture.cpp index 5ff6a56..5064573 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -98,21 +98,13 @@ bool Texture::load(SDL_Surface* surface, bool filterLinear) return true; } - // 32-bit surface: convert to destination format (BGRA), force full opacity + // 32-bit surface: convert to destination format (BGRA). + // SDL_ConvertSurfaceFormat preserves color keys and alpha, so + // transparent pixels keep alpha=0 and text anti-aliasing is retained. SDL_Surface* converted = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888, 0); if(!converted) return false; - // Force alpha to opaque (LBM palette entries often have alpha=0) - SDL_LockSurface(converted); - for(int y = 0; y < converted->h; y++) - { - auto* row = (Uint32*)((Uint8*)converted->pixels + y * converted->pitch); - for(int x = 0; x < converted->w; x++) - row[x] |= 0xFF000000u; // set alpha bits - } - SDL_UnlockSurface(converted); - load(converted->pixels, Extent(converted->w, converted->h), filterLinear); SDL_FreeSurface(converted); return true; diff --git a/Texture.h b/Texture.h index 5c19b8c..2d6c26c 100644 --- a/Texture.h +++ b/Texture.h @@ -38,6 +38,18 @@ class Texture /// Draw the texture at native size at the given position. void Draw(Position pos) const; + /// Convenience overload for callers using separate x/y. + void Draw(int x, int y) const { Draw(Position(x, y)); } + + /// Returns the raw GL texture name (for use with glBindTexture). + unsigned getHandle() const { return texture_; } + + /// Width in pixels. + int getWidth() const { return size_.x; } + + /// Height in pixels. + int getHeight() const { return size_.y; } + /// Returns true if the texture has been created. bool isValid() const { return texture_ != 0; } diff --git a/callbacks.cpp b/callbacks.cpp index 6e16659..38ec75b 100644 --- a/callbacks.cpp +++ b/callbacks.cpp @@ -10,6 +10,7 @@ #include "CIO/CFile.h" #include "CIO/CFont.h" #include "CIO/CMenu.h" +#include "CIO/CMinimapWindow.h" #include "CIO/CPicture.h" #include "CIO/CSelectBox.h" #include "CIO/CTextfield.h" @@ -19,6 +20,7 @@ #include "globals.h" #include "helpers/format.hpp" #include "s25util/strAlgos.h" +#include #include #include #include @@ -51,9 +53,12 @@ void callback::PleaseWait(int Param) WNDWait->addText("Please wait ...", Position(10, 10), FontSize::Large); // we need to render this window NOW, cause the render loop will do it too late (when the operation // is done and we don't need the "Please wait"-window anymore) - CSurface::Draw(global::s2->getDisplaySurface(), WNDWait->getSurface(), - global::s2->getDisplaySurface()->w / 2 - 106, global::s2->getDisplaySurface()->h / 2 - 35); - global::s2->RenderPresent(); + { + const auto res = global::s2->getRes(); + glClear(GL_COLOR_BUFFER_BIT); + WNDWait->getTexture().Draw(Position(res.x / 2 - 106, res.y / 2 - 35)); + global::s2->RenderPresent(); + } break; case CALL_FROM_GAMELOOP: // This window gives a "Please Wait"-string, so it is shown while there is an intensive @@ -2820,7 +2825,6 @@ void callback::MinimapMenu(int Param) { static CWindow* WNDMinimap = nullptr; static CMap* MapObj = nullptr; - static SDL_Surface* WndSurface = nullptr; static int scaleNum = 1; // only in case INITIALIZING_CALL needed to create the window int width; @@ -2849,22 +2853,16 @@ void callback::MinimapMenu(int Param) height = map->height / scaleNum; //--> 12px is width of left and right window frame and 30px is height of the upper and lower window // frame - if((global::s2->getDisplaySurface()->w - 12 < width) - || (global::s2->getDisplaySurface()->h - 30 < height)) + if((static_cast(global::s2->getRes().x) - 12 < width) + || (static_cast(global::s2->getRes().y) - 30 < height)) break; - WNDMinimap = global::s2->RegisterWindow( - std::make_unique(MinimapMenu, WINDOWQUIT, WindowPos::Center, Extent(width + 12, height + 30), - "Overview", WINDOW_NOTHING, WINDOW_CLOSE | WINDOW_MOVE)); + WNDMinimap = global::s2->RegisterWindow(std::make_unique( + MinimapMenu, WINDOWQUIT, WindowPos::Center, Extent(width + 12, height + 30), "Overview", + WINDOW_NOTHING, WINDOW_CLOSE | WINDOW_MOVE)); global::s2->RegisterCallback(MinimapMenu); - WndSurface = WNDMinimap->getSurface(); } break; - case CALL_FROM_GAMELOOP: - if(MapObj && WndSurface) - MapObj->drawMinimap(WndSurface); - break; - case WINDOW_CLICKED_CALL: if(MapObj) { @@ -2894,7 +2892,6 @@ void callback::MinimapMenu(int Param) WNDMinimap = nullptr; } MapObj = nullptr; - WndSurface = nullptr; global::s2->UnregisterCallback(MinimapMenu); break; From c650bddf363ab2fbd4b8321bc865b5fe32fe61b4 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Fri, 3 Jul 2026 20:39:41 +0200 Subject: [PATCH 2/3] Draw() --- CGame.h | 1 - CGame_Init.cpp | 6 - CGame_Render.cpp | 36 ++--- CIO/CButton.cpp | 215 +++++------------------------- CIO/CButton.h | 16 +-- CIO/CControlContainer.cpp | 40 +++--- CIO/CControlContainer.h | 26 +--- CIO/CFont.cpp | 186 ++++++++++++-------------- CIO/CFont.h | 36 ++--- CIO/CMenu.cpp | 35 ++--- CIO/CMenu.h | 14 +- CIO/CMinimapWindow.cpp | 42 ++++-- CIO/CMinimapWindow.h | 7 +- CIO/CPicture.cpp | 33 ++--- CIO/CPicture.h | 12 +- CIO/CSelectBox.cpp | 101 ++++---------- CIO/CSelectBox.h | 17 +-- CIO/CTextfield.cpp | 223 +++++++------------------------ CIO/CTextfield.h | 12 +- CIO/CWindow.cpp | 267 +++++++++++++++----------------------- CIO/CWindow.h | 14 +- Texture.cpp | 159 ++++++++++++++++++++++- Texture.h | 32 +++++ callbacks.cpp | 3 +- include/defines.h | 2 + 25 files changed, 632 insertions(+), 903 deletions(-) diff --git a/CGame.h b/CGame.h index 249b4ac..d62ea65 100644 --- a/CGame.h +++ b/CGame.h @@ -49,7 +49,6 @@ class CGame Texture cursor_; Texture cursorClicked_; Texture cross_; - Texture fpsTex_; ///< Texture for the FPS counter Texture mapTex_; ///< Texture for the map/terrain (Surf_Map may be 8-bit) // structure for mouse cursor diff --git a/CGame_Init.cpp b/CGame_Init.cpp index 8d5617c..49118a6 100644 --- a/CGame_Init.cpp +++ b/CGame_Init.cpp @@ -124,12 +124,6 @@ void CGame::UpdateDisplaySize(const Extent& newSize) appliedFullscreen_ = fullscreen; setGLViewport(); - for(auto& menu : Menus) - { - menu->resetSurface(); - } - for(auto& wnd : Windows) - wnd->resetSurface(); } bool CGame::Init() diff --git a/CGame_Render.cpp b/CGame_Render.cpp index 1f5dd34..f239089 100644 --- a/CGame_Render.cpp +++ b/CGame_Render.cpp @@ -9,6 +9,7 @@ #include "CIO/CWindow.h" #include "CMap.h" #include "CSurface.h" +#include "Texture.h" #include "globals.h" #include #ifdef _WIN32 @@ -40,6 +41,8 @@ void CGame::SetAppIcon() void CGame::Render() { glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // if the S2 loading screen is shown, render only this until user clicks a mouse button if(showLoadScreen) @@ -76,11 +79,11 @@ void CGame::Render() } } - // render active menus + // render active menus — each draws itself with OpenGL for(auto& Menu : Menus) { if(Menu->isActive()) - Menu->getTexture().Draw(Rect(0, 0, GameResolution.x, GameResolution.y)); + Menu->Draw(Position(0, 0)); } // render windows ordered by priority @@ -97,7 +100,7 @@ void CGame::Render() for(auto& Window : Windows) { if(Window->getPriority() == actualPriority) - Window->getTexture().Draw(Position(Window->getX(), Window->getY())); + Window->Draw(Position(0, 0)); } } @@ -114,29 +117,12 @@ void CGame::Render() framesPassedSinceLastFps = 0; lastFpsTick = curTicks; } - { - if(auto* fpsSurf = lastFps.getSurface()) - { - fpsTex_.load(fpsSurf); - glBindTexture(GL_TEXTURE_2D, fpsTex_.getHandle()); - glBegin(GL_QUADS); - glTexCoord2f(0, 0); - glVertex2i(0, 0); - glTexCoord2f(1, 0); - glVertex2i(fpsSurf->w, 0); - glTexCoord2f(1, 1); - glVertex2i(fpsSurf->w, fpsSurf->h); - glTexCoord2f(0, 1); - glVertex2i(0, fpsSurf->h); - glEnd(); - } - } + // Draw FPS counter directly with OpenGL text rendering + lastFps.Draw(Position(0, 0)); - // ---- 5. Cursor on top of everything ---- - { - const auto& cursorImg = Cursor.clicked ? (Cursor.button.right ? cross_ : cursorClicked_) : cursor_; - cursorImg.Draw(Cursor.pos); - } + // ---- Cursor on top of everything ---- + const auto& cursorImg = Cursor.clicked ? (Cursor.button.right ? cross_ : cursorClicked_) : cursor_; + cursorImg.Draw(Cursor.pos); SDL_GL_SwapWindow(window_.get()); diff --git a/CIO/CButton.cpp b/CIO/CButton.cpp index f5f2c7b..19c494f 100644 --- a/CIO/CButton.cpp +++ b/CIO/CButton.cpp @@ -4,10 +4,11 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "CButton.h" -#include "../CSurface.h" +#include "../Texture.h" #include "../globals.h" #include "CFont.h" #include "CollisionDetection.h" +#include CButton::CButton(void callback(int), int clickedParam, Position pos, Extent size, int color, const char* text, int button_picture) @@ -23,19 +24,16 @@ CButton::CButton(void callback(int), int clickedParam, Position pos, Extent size this->clickedParam = clickedParam; motionEntryParam = -1; motionLeaveParam = -1; - needRender = true; } void CButton::setButtonPicture(int picture) { this->button_picture = picture; - needRender = true; } void CButton::setButtonText(const char* text) { button_text = text; - needRender = true; } void CButton::setColor(int color) @@ -84,14 +82,12 @@ void CButton::setColor(int color) pic_background = BUTTON_GREY_BACKGROUND; break; } - - needRender = true; } void CButton::setMouseData(const SDL_MouseMotionEvent& motion) { // cursor is on the button (and mouse button not pressed while moving on the button) - if(IsPointInRect(Position(motion.x, motion.y), Rect(pos_, size_))) + if(IsPointInRect(motion.x, motion.y, Rect(pos_, size_))) { if(motion.state == SDL_RELEASED) { @@ -106,7 +102,6 @@ void CButton::setMouseData(const SDL_MouseMotionEvent& motion) callback_(motionLeaveParam); marked = false; } - needRender = true; } void CButton::setMouseData(const SDL_MouseButtonEvent& button) @@ -115,7 +110,7 @@ void CButton::setMouseData(const SDL_MouseButtonEvent& button) if(button.button == SDL_BUTTON_LEFT) { // if mouse button is pressed ON the button, set marked=true - if((button.state == SDL_PRESSED) && IsPointInRect(Position(button.x, button.y), Rect(pos_, size_))) + if(button.state == SDL_PRESSED && IsPointInRect(button.x, button.y, Rect(pos_, size_))) { marked = true; clicked = true; @@ -127,191 +122,53 @@ void CButton::setMouseData(const SDL_MouseButtonEvent& button) callback_(clickedParam); } } - needRender = true; } -bool CButton::render() +void CButton::Draw(Position parentOrigin) const { - // position in the Surface 'Surf_Button' - Position pos{0, 0}; - // width and height of the button color source picture - Extent pic{0, 0}; - // foreground of the button --> marked or unmarked, NOT the picture - int foreground; - - // if we don't need to render, all is up to date, return true - if(!needRender) - return true; - needRender = false; - // if we need a new surface - if(!Surf_Button) - { - if((Surf_Button = makeRGBSurface(size_.x, size_.y)) == nullptr) - return false; - } - - // at first completly fill the background (not the fastest way, but simplier) - if(size_.x <= global::bmpArray[pic_background].w) - pic.x = size_.x; - else - pic.x = global::bmpArray[pic_background].w; - - if(size_.y <= global::bmpArray[pic_background].h) - pic.y = size_.y; - else - pic.y = global::bmpArray[pic_background].h; - - while(pos.x + pic.x <= static_cast(Surf_Button->w)) - { - while(pos.y + pic.y <= static_cast(Surf_Button->h)) - { - CSurface::Draw(Surf_Button, global::bmpArray[pic_background].surface, pos, Position(0, 0), pic); - pos.y += pic.y; - } - - if(pos.y < Surf_Button->h) - CSurface::Draw(Surf_Button, global::bmpArray[pic_background].surface, pos, Position(0, 0), - Extent(pic.x, static_cast(Surf_Button->h - pos.y))); + const Position absPos = parentOrigin + pos_; - pos.y = 0; - pos.x += pic.x; - } - - if(pos.x < Surf_Button->w) - { - while(pos.y + pic.y <= static_cast(Surf_Button->h)) - { - CSurface::Draw(Surf_Button, global::bmpArray[pic_background].surface, pos, Position(0, 0), - Extent(static_cast(Surf_Button->w - pos.x), pic.y)); - pos.y += pic.y; - } + // 1. Draw background (tiled) + drawTiledBmp(pic_background, Rect(absPos, size_)); - if(pos.y < Surf_Button->h) - CSurface::Draw( - Surf_Button, global::bmpArray[pic_background].surface, pos, Position(0, 0), - Extent(static_cast(Surf_Button->w - pos.x), static_cast(Surf_Button->h - pos.y))); - } - - // draw partial black frame + // 2. Draw black frame (2px) using filled rectangles if(clicked) { - // black frame is left and up - // draw vertical line - pos.x = 0; - for(unsigned y = 0; y < size_.y; y++) - CSurface::DrawPixel_RGB(Surf_Button, Position(pos.x, y), 0, 0, 0); - - // draw vertical line - pos.x = 1; - for(unsigned y = 0; y < size_.y - 1; y++) - CSurface::DrawPixel_RGB(Surf_Button, Position(pos.x, y), 0, 0, 0); - - // draw horizontal line - pos.y = 0; - for(unsigned x = 0; x < size_.x; x++) - CSurface::DrawPixel_RGB(Surf_Button, Position(x, pos.y), 0, 0, 0); - - // draw horizontal line - pos.y = 1; - for(unsigned x = 0; x < size_.x - 1; x++) - CSurface::DrawPixel_RGB(Surf_Button, Position(x, pos.y), 0, 0, 0); + // Left border (2px wide, full height) + DrawRect(Rect(absPos.x, absPos.y, 2, size_.y), 0, 0, 0); + // Top border (2px tall, full width) + DrawRect(Rect(absPos.x, absPos.y, size_.x, 2), 0, 0, 0); } else { - // black frame is right and down - // draw vertical line - pos.x = size_.x - 1; - for(unsigned y = 0; y < size_.y; y++) - CSurface::DrawPixel_RGB(Surf_Button, Position(pos.x, y), 0, 0, 0); - - // draw vertical line - pos.x = size_.x - 2; - for(unsigned y = 1; y < size_.y; y++) - CSurface::DrawPixel_RGB(Surf_Button, Position(pos.x, y), 0, 0, 0); - - // draw horizontal line - pos.y = size_.y - 1; - for(unsigned x = 0; x < size_.x; x++) - CSurface::DrawPixel_RGB(Surf_Button, Position(x, pos.y), 0, 0, 0); - - // draw horizontal line - pos.y = size_.y - 2; - for(unsigned x = 1; x < size_.x; x++) - CSurface::DrawPixel_RGB(Surf_Button, Position(x, pos.y), 0, 0, 0); - } - - // draw the foreground --> at first the color (marked or unmarked) and then the picture or text - if(size_.x <= global::bmpArray[pic_normal].w) - pic.x = size_.x; - else - pic.x = global::bmpArray[pic_normal].w; - - if(size_.y <= global::bmpArray[pic_normal].h) - pic.y = size_.y; - else - pic.y = global::bmpArray[pic_normal].h; - - // beware overdrawing the left and upper frame - pos.x = 2; - pos.y = 2; - - // decide if button lights or not - if(marked && !clicked) - foreground = pic_marked; - else - foreground = pic_normal; - - // '-2' follows a few times, this means: beware overdrawing the right and lower frame - while(pos.x + pic.x <= static_cast(Surf_Button->w - 2)) - { - while(pos.y + pic.y <= static_cast(Surf_Button->h - 2)) - { - CSurface::Draw(Surf_Button, global::bmpArray[foreground].surface, pos, Position(0, 0), pic); - pos.y += pic.y; - } - - if(pos.y + 2 < Surf_Button->h) - CSurface::Draw(Surf_Button, global::bmpArray[foreground].surface, pos, Position(0, 0), - Extent(pic.x, static_cast(Surf_Button->h - pos.y))); - - pos.y = 2; - pos.x += pic.x; + // Right border (2px wide, full height) + DrawRect(Rect(absPos.x + static_cast(size_.x) - 2, absPos.y, 2, size_.y), 0, 0, 0); + // Bottom border (2px tall, full width) + DrawRect(Rect(absPos.x, absPos.y + static_cast(size_.y) - 2, size_.x, 2), 0, 0, 0); } - if(pos.x + 2 < Surf_Button->w) - { - while(pos.y + pic.y <= static_cast(Surf_Button->h - 2)) - { - CSurface::Draw(Surf_Button, global::bmpArray[foreground].surface, pos, Position(0, 0), - Extent(static_cast(Surf_Button->w - 2 - pos.x), pic.y)); - pos.y += pic.y; - } + // 3. Draw foreground (tiled, inset by 2px for the black frame) + const int foreground = (marked && !clicked) ? pic_marked : pic_normal; + const Rect fgRect(absPos + Position(2, 2), size_ - Extent(4, 4)); + drawTiledBmp(foreground, fgRect); - if(pos.y + 2 < Surf_Button->h) - CSurface::Draw(Surf_Button, global::bmpArray[foreground].surface, pos, Position(0, 0), - Extent(static_cast(Surf_Button->w - 2 - pos.x), - static_cast(Surf_Button->h - 2 - pos.y))); - } - - // positioning the picture or write text + // 4. Draw picture or text centered inside the button if(button_picture >= 0) { - // picture may not be bigger than the button - if(size_.x <= static_cast(Surf_Button->w) && size_.y <= static_cast(Surf_Button->h)) - { - // get coordinates of the left upper corner where to positionate the picture - Position leftup = Position(Surf_Button->w, Surf_Button->h) / 2 - - Position(global::bmpArray[button_picture].w, global::bmpArray[button_picture].h) / 2; - // blit it - CSurface::Draw(Surf_Button, global::bmpArray[button_picture].surface, leftup); - } else + auto& picTex = getBmpTexture(button_picture); + if(picTex.isValid()) { - button_picture = -1; - button_text = "PIC"; + const auto& picBmp = global::bmpArray[button_picture]; + const Position picPos = + absPos + Position(size_) / 2 - Position(static_cast(picBmp.w), static_cast(picBmp.h)) / 2; + picTex.Draw(picPos); } } else if(button_text) - CFont::writeText(Surf_Button, button_text, - Position(static_cast(size_.x / 2), static_cast((size_.y - 11) / 2)), - FontSize::Medium, button_text_color, FontAlign::Middle); - - return true; + { + // Draw text centered (using native-size texture drawing for each character) + const unsigned textW = CFont::getTextWidth(button_text, FontSize::Medium); + const unsigned textH = static_cast(FontSize::Medium); + const Position textPos = + absPos + Position(static_cast(size_.x / 2 - textW / 2), static_cast((size_.y - textH) / 2)); + CFont::Draw(button_text, textPos, FontSize::Medium, button_text_color, FontAlign::Left); + } } diff --git a/CIO/CButton.h b/CIO/CButton.h index d9b54e7..034d93d 100644 --- a/CIO/CButton.h +++ b/CIO/CButton.h @@ -5,7 +5,6 @@ #pragma once -#include "SdlSurface.h" #include "defines.h" class CButton @@ -13,8 +12,6 @@ class CButton friend class CDebug; private: - SdlSurface Surf_Button; - bool needRender; Position pos_; Extent size_; int pic_normal; @@ -44,18 +41,9 @@ class CButton void setButtonText(const char* text); void setMouseData(const SDL_MouseMotionEvent& motion); void setMouseData(const SDL_MouseButtonEvent& button); - bool render(); - SDL_Surface* getSurface() - { - render(); - return Surf_Button.get(); - }; + void Draw(Position parentOrigin) const; void setColor(int color); - void setTextColor(FontColor color) - { - button_text_color = color; - needRender = true; - }; + void setTextColor(FontColor color) { button_text_color = color; }; void setMotionParams(int entry, int leave) { motionEntryParam = entry; diff --git a/CIO/CControlContainer.cpp b/CIO/CControlContainer.cpp index e839f38..d404370 100644 --- a/CIO/CControlContainer.cpp +++ b/CIO/CControlContainer.cpp @@ -4,7 +4,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "CControlContainer.h" -#include "../CSurface.h" +#include "../Texture.h" #include "../globals.h" #include "CButton.h" #include "CFont.h" @@ -25,7 +25,6 @@ CControlContainer::~CControlContainer() noexcept = default; void CControlContainer::setBackgroundPicture(int pic_background) { this->pic_background = pic_background; - needRender = true; } void CControlContainer::setMouseData(const SDL_MouseMotionEvent motion) @@ -42,7 +41,6 @@ void CControlContainer::setMouseData(const SDL_MouseMotionEvent motion) { selectbox->setMouseData(motion); } - needRender = true; } void CControlContainer::setMouseData(const SDL_MouseButtonEvent button) @@ -63,7 +61,6 @@ void CControlContainer::setMouseData(const SDL_MouseButtonEvent button) { selectbox->setMouseData(button); } - needRender = true; } void CControlContainer::setKeyboardData(const SDL_KeyboardEvent& key) @@ -81,7 +78,6 @@ bool CControlContainer::eraseElement(T& collection, const U* element) if(it != collection.end()) { collection.erase(it); - needRender = true; return true; } return false; @@ -93,7 +89,6 @@ CButton* CControlContainer::addButton(void callback(int), int clickedParam, Posi pos = pos + borderBeginSize; buttons.emplace_back(std::make_unique(callback, clickedParam, pos, size, color, text, picture)); - needRender = true; return buttons.back().get(); } @@ -107,7 +102,6 @@ CFont* CControlContainer::addText(std::string string, Position pos, FontSize fon pos = pos + borderBeginSize; texts.emplace_back(std::make_unique(std::move(string), pos, fontsize, color)); - needRender = true; return texts.back().get(); } @@ -121,7 +115,6 @@ CPicture* CControlContainer::addPicture(void callback(int), int clickedParam, Po pos = pos + borderBeginSize; pictures.emplace_back(std::make_unique(callback, clickedParam, pos, picture)); - needRender = true; return pictures.back().get(); } @@ -138,7 +131,6 @@ int CControlContainer::addStaticPicture(Position pos, int picture) unsigned id = static_pictures.empty() ? 0u : static_pictures.back().id + 1u; static_pictures.emplace_back(Picture{pos, picture, id}); - needRender = true; return id; } @@ -151,7 +143,6 @@ bool CControlContainer::delStaticPicture(int picId) if(it != static_pictures.end()) { static_pictures.erase(it); - needRender = true; return true; } return false; @@ -164,7 +155,6 @@ CTextfield* CControlContainer::addTextfield(Position pos, Uint16 cols, Uint16 ro textfields.emplace_back( std::make_unique(pos, cols, rows, fontsize, text_color, bg_color, button_style)); - needRender = true; return textfields.back().get(); } @@ -179,7 +169,6 @@ CSelectBox* CControlContainer::addSelectBox(Position pos, Extent size, FontSize pos += Position(borderBeginSize); selectboxes.emplace_back(std::make_unique(pos, size, fontsize, text_color, bg_color)); - needRender = true; return selectboxes.back().get(); } @@ -188,18 +177,31 @@ bool CControlContainer::delSelectBox(CSelectBox* SelectBoxToDelete) return eraseElement(selectboxes, SelectBoxToDelete); } -void CControlContainer::renderElements() +// --------------------------------------------------------------------------- +// Draw & DrawChildren +// --------------------------------------------------------------------------- + +void CControlContainer::Draw(Position parentOrigin) +{ + DrawChildren(parentOrigin); +} + +void CControlContainer::DrawChildren(Position origin) { for(const auto& picture : pictures) - CSurface::Draw(surface, picture->getSurface(), picture->getX(), picture->getY()); + picture->Draw(origin); for(const auto& text : texts) - CSurface::Draw(surface, text->getSurface(), text->getX(), text->getY()); + text->Draw(origin); for(const auto& textfield : textfields) - CSurface::Draw(surface, textfield->getSurface(), textfield->getX(), textfield->getY()); + textfield->Draw(origin); for(const auto& selectbox : selectboxes) - CSurface::Draw(surface, selectbox->getSurface(), selectbox->getPos()); + selectbox->Draw(origin); for(const auto& button : buttons) - CSurface::Draw(surface, button->getSurface(), button->getX(), button->getY()); + button->Draw(origin); for(const auto& static_picture : static_pictures) - CSurface::Draw(surface, global::bmpArray[static_picture.pic].surface, static_picture.pos); + { + auto& tex = getBmpTexture(static_picture.pic); + if(tex.isValid()) + tex.Draw(origin + static_picture.pos); + } } diff --git a/CIO/CControlContainer.h b/CIO/CControlContainer.h index 7802e61..31304cb 100644 --- a/CIO/CControlContainer.h +++ b/CIO/CControlContainer.h @@ -5,7 +5,6 @@ #pragma once -#include "../Texture.h" #include "defines.h" #include #include @@ -41,14 +40,14 @@ class CControlContainer template bool eraseElement(T& collection, const U* element); - virtual bool render() = 0; protected: - SdlSurface surface; - bool needRender = true; - Texture surfaceTex_; ///< Texture for this container's surface (used by CWindow) + /// Draw the container's background and child elements using OpenGL. + /// @param parentOrigin Absolute position of the parent container. + virtual void Draw(Position parentOrigin); + /// Draw children at the given origin (calls each child's Draw). + void DrawChildren(Position origin); - void renderElements(); auto& getTextFields() { return textfields; } const auto& getTextFields() const { return textfields; } int getBackground() const { return pic_background; } @@ -59,25 +58,14 @@ class CControlContainer virtual ~CControlContainer() noexcept; // Access Extent getBorderSize() const { return borderBeginSize + borderEndSize; } + Extent getBorderBegin() const { return borderBeginSize; } + Extent getBorderEnd() const { return borderEndSize; } void setBackgroundPicture(int pic_background); virtual void setMouseData(SDL_MouseMotionEvent motion); virtual void setMouseData(SDL_MouseButtonEvent button); void setKeyboardData(const SDL_KeyboardEvent& key); - /// Get the texture for this container's rendered content. - const Texture& getTexture() - { - render(); - if(surface) - surfaceTex_.load(surface.get()); - return surfaceTex_; - } void setWaste() { waste = true; } bool isWaste() const { return waste; } - void resetSurface() - { - surface.reset(); - needRender = true; - } // Methods CButton* addButton(void callback(int), int clickedParam, Position pos = {0, 0}, Extent size = {20, 20}, int color = BUTTON_GREY, const char* text = nullptr, int picture = -1); diff --git a/CIO/CFont.cpp b/CIO/CFont.cpp index d7b421a..73ebe18 100644 --- a/CIO/CFont.cpp +++ b/CIO/CFont.cpp @@ -5,46 +5,37 @@ #include "CFont.h" #include "../CSurface.h" +#include "../Texture.h" #include "../globals.h" +#include "CollisionDetection.h" #include CFont::CFont(std::string text, Position pos, FontSize fontsize, FontColor color) - : x_(pos.x), y_(pos.y), string_(std::move(text)), fontsize_(fontsize), color_(color), initialColor_(color), - clickedParam(0) -{} + : pos_(pos), string_(std::move(text)), fontsize_(fontsize), color_(color), initialColor_(color), clickedParam(0) +{ + size_ = Extent(getTextWidth(string_, fontsize_), getLineHeight(fontsize_)); +} void CFont::setPos(Position pos) { - if(pos != Position(x_, y_)) - { - x_ = pos.x; - y_ = pos.y; - Surf_Font.reset(); - } + pos_ = pos; } void CFont::setFontsize(FontSize fontsize) { - if(fontsize != fontsize_) - { - fontsize_ = fontsize; - Surf_Font.reset(); - } + fontsize_ = fontsize; + size_ = Extent(getTextWidth(string_, fontsize_), getLineHeight(fontsize_)); } void CFont::setColor(FontColor color) { - if(color != color_) - Surf_Font.reset(); initialColor_ = color_ = color; } void CFont::setText(std::string text) { - if(text == string_) - return; - Surf_Font.reset(); this->string_ = std::move(text); + size_ = Extent(getTextWidth(string_, fontsize_), getLineHeight(fontsize_)); } void CFont::setMouseData(SDL_MouseButtonEvent button) @@ -54,10 +45,10 @@ void CFont::setMouseData(SDL_MouseButtonEvent button) // left button is pressed if(button.button == SDL_BUTTON_LEFT) { - if((button.x >= x_) && (button.x < x_ + w) && (button.y >= y_) && (button.y < y_ + h)) + if(IsPointInRect(button.x, button.y, Rect(pos_, size_))) { // if mouse button is pressed ON the text - if((button.state == SDL_PRESSED) && getColor() == initialColor_) + if(button.state == SDL_PRESSED && getColor() == initialColor_) { const auto tmpInitialColor = initialColor_; setColor(FontColor::Orange); @@ -71,19 +62,14 @@ void CFont::setMouseData(SDL_MouseButtonEvent button) } } -SDL_Surface* CFont::getSurface() -{ - if(!Surf_Font) - writeText(); - return Surf_Font.get(); -} +// --------------------------------------------------------------------------- +// Character-index helpers (shared by SDL and GL paths) +// --------------------------------------------------------------------------- namespace { unsigned getIndexForChar(uint8_t c) { // subtract 32 shows that we start by spacebar as 'zero-position' - // subtract another value after subtracting 32 means the skipped chiffres in ansi in compare to our enumeration - // (cause we dont have all ansi-values as pictures) if(c >= 32 && c <= 90) return c - 32; /* \ */ @@ -197,100 +183,82 @@ unsigned getIndexForChar(uint8_t c, FontSize fontsize, FontColor color) } unsigned getCharWidth(uint8_t c, FontSize fontsize, FontColor color) -{ // NOTE: there is a bug in the ansi 236 'ì' at fontsize 9, the width is 39, this is not useable, we will use the width - // of ansi 237 - // 'í' instead +{ + // NOTE: there is a bug in the ansi 236 'ì' at fontsize 9, the width is 39, this is not useable, we will use the + // width of ansi 237 'í' instead if(fontsize == FontSize::Small && c == 236) c = 109; return global::bmpArray[getIndexForChar(c, fontsize, color)].w; } } // namespace -void CFont::writeText() +// --------------------------------------------------------------------------- +// OpenGL Draw methods +// --------------------------------------------------------------------------- + +void CFont::Draw(Position parentOrigin) const { if(string_.empty()) return; - // data for counting pixels to create the surface - unsigned pixel_ctr_w = 0; - unsigned pixel_ctr_w_tmp = 0; - const unsigned lineHeight = getLineHeight(fontsize_); - auto pixel_ctr_h = static_cast(fontsize_); - bool pixel_count_loop = true; - // counter for the drawed pixels (cause we dont want to draw outside of the surface) - Position pos{0, 0}; + Draw(string_, parentOrigin + pos_, fontsize_, color_, FontAlign::Left); +} - // now lets draw the chiffres - auto chiffre = string_.begin(); - while(chiffre != string_.end()) - { - const auto charW = getCharWidth(*chiffre, fontsize_, color_); - // if we only count pixels in this round - if(pixel_count_loop) - { - if(*chiffre == '\n') - { - pixel_ctr_h += lineHeight; - if(pixel_ctr_w_tmp > pixel_ctr_w) - pixel_ctr_w = pixel_ctr_w_tmp; - pixel_ctr_w_tmp = 0; - ++chiffre; - } else - { - pixel_ctr_w_tmp += charW; - ++chiffre; - } +void CFont::Draw(const std::string& string, Position pos, FontSize fontsize, FontColor color, FontAlign align) +{ + if(string.empty()) + return; - // if this was the last chiffre setup width, create surface and go in normal mode to write text to the - // surface - if(chiffre == string_.end()) - { - if(pixel_ctr_w_tmp > pixel_ctr_w) - pixel_ctr_w = pixel_ctr_w_tmp; - w = pixel_ctr_w; - h = pixel_ctr_h; - Surf_Font = makeRGBSurface(w, h); - if(!Surf_Font) - return; - SDL_SetColorKey(Surf_Font.get(), SDL_TRUE, SDL_MapRGB(Surf_Font->format, 0, 0, 0)); - chiffre = string_.begin(); - pixel_count_loop = false; - continue; - } else - continue; - } + // Measure text width for alignment + const unsigned lineHeight = getLineHeight(fontsize); + unsigned totalWidth = 0; + for(unsigned char c : string) + { + if(c == '\n') + break; + totalWidth += getCharWidth(c, fontsize, color); + } - // now we have our index and can use global::bmpArray[chiffre_index] to get the picture + // Apply alignment + switch(align) + { + case FontAlign::Middle: pos.x -= static_cast(totalWidth / 2); break; + case FontAlign::Right: pos.x -= static_cast(totalWidth); break; + case FontAlign::Left: break; // no adjustment + } - // test for new line - if(*chiffre == '\n') + // Draw each character as a textured quad + Position curPos = pos; + for(unsigned char c : string) + { + if(c == '\n') { - pos.x = 0; - pos.y += lineHeight; - ++chiffre; + curPos.x = pos.x; + curPos.y += lineHeight; continue; } - // if right end of surface is reached, stop drawing chiffres - if(Surf_Font->w < static_cast(pos.x + charW)) - break; - - const auto chiffre_index = getIndexForChar(*chiffre, fontsize_, color_); - - // if lower end of surface is reached, stop drawing chiffres - if(Surf_Font->h < static_cast(pos.y + global::bmpArray[chiffre_index].h)) - break; - - // draw the chiffre to the destination - CSurface::Draw(Surf_Font, global::bmpArray[chiffre_index].surface, pos); + const unsigned idx = getIndexForChar(c, fontsize, color); + if(idx >= global::bmpArray.size()) + continue; - // set position for next chiffre - pos.x += charW; + auto& tex = getBmpTexture(idx); + if(!tex.isValid()) + { + curPos.x += getCharWidth(c, fontsize, color); + continue; + } - // go to next chiffre - ++chiffre; + const auto& bmp = global::bmpArray[idx]; + const unsigned charW = bmp.w; + tex.Draw(Rect(curPos.x, curPos.y, charW, bmp.h)); + curPos.x += charW; } } +// --------------------------------------------------------------------------- +// SDL surface writeText (for terrain / minimap rendering) +// --------------------------------------------------------------------------- + bool CFont::writeText(SDL_Surface* Surf_Dest, const std::string& string, unsigned x, unsigned y, FontSize fontsize, FontColor color, FontAlign align) { @@ -362,3 +330,19 @@ bool CFont::writeText(SDL_Surface* Surf_Dest, const std::string& string, unsigne return true; } + +// --------------------------------------------------------------------------- +// getTextWidth +// --------------------------------------------------------------------------- + +unsigned CFont::getTextWidth(const std::string& string, FontSize fontsize) +{ + unsigned w = 0; + for(unsigned char c : string) + { + if(c == '\n') + break; + w += getCharWidth(c, fontsize, FontColor::Yellow); // width is same for all colors + } + return w; +} diff --git a/CIO/CFont.h b/CIO/CFont.h index e73e480..53c841f 100644 --- a/CIO/CFont.h +++ b/CIO/CFont.h @@ -16,28 +16,25 @@ class CFont friend class CDebug; private: - SdlSurface Surf_Font; - int x_; - int y_; - Uint16 w; - Uint16 h; + Position pos_; ///< Position of the text (top-left) + Extent size_; ///< Pixel extent of the rendered text std::string string_; FontSize fontsize_; FontColor color_, initialColor_; std::function callback; int clickedParam; - void writeText(); - public: CFont(std::string text, Position pos = {0, 0}, FontSize fontsize = FontSize::Small, FontColor color = FontColor::Yellow); // Access - int getX() const { return x_; }; - int getY() const { return y_; }; + int getX() const { return pos_.x; } + int getY() const { return pos_.y; } + const Position& getPos() const { return pos_; } + const Extent& getSize() const { return size_; } + unsigned getW() const { return size_.x; } + unsigned getH() const { return static_cast(fontsize_); } void setPos(Position pos); - unsigned getW() const { return w; }; - unsigned getH() const { return static_cast(fontsize_); }; void setFontsize(FontSize fontsize); void setColor(FontColor color); FontColor getColor() const { return color_; } @@ -53,10 +50,16 @@ class CFont clickedParam = 0; } void setMouseData(SDL_MouseButtonEvent button); - SDL_Surface* getSurface(); - // Methods - // fontsize can be 9, 11 or 14 (otherwise it will be set to 9) ---- '\n' is possible - // this function can be used as CFont::writeText to write text directly to a surface without creating an object + + /// Draw this font's text at the given absolute position using OpenGL. + void Draw(Position parentOrigin) const; + + /// Static helpers for drawing text with OpenGL directly. + /// @param pos Absolute position (top-left of the text, adjusted for alignment). + static void Draw(const std::string& string, Position pos, FontSize fontsize = FontSize::Small, + FontColor color = FontColor::Yellow, FontAlign align = FontAlign::Left); + + /// Static helper: draw text onto an SDL surface (for terrain / minimap rendering). static bool writeText(SDL_Surface* Surf_Dest, const std::string& string, unsigned x = 0, unsigned y = 0, FontSize fontsize = FontSize::Small, FontColor color = FontColor::Yellow, FontAlign align = FontAlign::Left); @@ -66,4 +69,7 @@ class CFont { return writeText(Surf_Dest.get(), string, pos.x, pos.y, fontsize, color, align); } + + /// Compute the pixel width of a string without drawing it. + static unsigned getTextWidth(const std::string& string, FontSize fontsize); }; diff --git a/CIO/CMenu.cpp b/CIO/CMenu.cpp index af68a28..4591948 100644 --- a/CIO/CMenu.cpp +++ b/CIO/CMenu.cpp @@ -10,35 +10,20 @@ CMenu::CMenu(int pic_background) : CControlContainer(pic_background) {} -bool CMenu::render() +void CMenu::Draw(Position /*parentOrigin*/) { - if(getBackground() < 0) - return false; - - if(!bgTexture_) + // Draw full-screen background texture + const int picIdx = getBackground(); + if(picIdx >= 0 && picIdx < static_cast(global::bmpArray.size())) { - const int picIdx = getBackground(); - if(picIdx >= 0 && picIdx < static_cast(global::bmpArray.size()) && global::bmpArray[picIdx].surface) + auto& tex = getBmpTexture(picIdx, true); + if(tex.isValid()) { - bgTexture_ = std::make_unique(); - bgTexture_->load(global::bmpArray[picIdx].surface.get(), true); + const auto res = global::s2->getRes(); + tex.Draw(Rect(0, 0, res.x, res.y)); } } - if(bgTexture_) - bgTexture_->Draw(Rect(0, 0, global::s2->getRes().x, global::s2->getRes().y)); - - if(!needRender) - return true; - needRender = false; - // if we need a new surface - if(!surface) - { - surface = makeRGBSurface(global::s2->getRes().x, global::s2->getRes().y, true); - if(!surface) - return false; - } - - renderElements(); - return true; + // Draw children + DrawChildren(Position(0, 0)); } diff --git a/CIO/CMenu.h b/CIO/CMenu.h index 59d8400..487e658 100644 --- a/CIO/CMenu.h +++ b/CIO/CMenu.h @@ -6,21 +6,17 @@ #pragma once #include "CControlContainer.h" -#include - -class Texture; class CMenu final : public CControlContainer { // if active is false, the menu will not be render within the game loop bool active = true; - mutable std::unique_ptr bgTexture_; - - bool render() final; public: CMenu(int pic_background); - void setActive() { active = true; }; - void setInactive() { active = false; }; - bool isActive() const { return active; }; + void setActive() { active = true; } + void setInactive() { active = false; } + bool isActive() const { return active; } + + void Draw(Position parentOrigin) override; }; diff --git a/CIO/CMinimapWindow.cpp b/CIO/CMinimapWindow.cpp index 0ecbbd7..a91eb0f 100644 --- a/CIO/CMinimapWindow.cpp +++ b/CIO/CMinimapWindow.cpp @@ -5,19 +5,43 @@ #include "CMinimapWindow.h" #include "../CGame.h" #include "../CMap.h" +#include "../Texture.h" #include "../globals.h" -bool CMinimapWindow::render() +void CMinimapWindow::Draw(Position /*parentOrigin*/) { - // Always re-render: the minimap terrain overlay may have changed - needRender = true; // Draw window chrome (frame, title, close button, background, child elements) - CWindow::render(); - // Draw minimap terrain overlay on top of the chrome - if(surface) + CWindow::Draw(Position(x_, y_)); + + // Compute content area (inside the frames) + const auto borderBegin = getBorderBegin(); + const auto borderEnd = getBorderEnd(); + const int contentX = x_ + static_cast(borderBegin.x); + const int contentY = y_ + static_cast(borderBegin.y); + const int contentW = static_cast(w_) - static_cast(borderBegin.x) - static_cast(borderEnd.x); + const int contentH = static_cast(h_) - static_cast(borderBegin.y) - static_cast(borderEnd.y); + + if(contentW <= 0 || contentH <= 0) + return; + + // Draw minimap terrain overlay onto a temporary SDL surface, then upload to texture + if(auto* map = global::s2->getMapObj()) { - if(auto* map = global::s2->getMapObj()) - map->drawMinimap(surface.get()); + // Create or resize the minimap surface + if(!minimapSurface_ || minimapSurface_->w != contentW || minimapSurface_->h != contentH) + minimapSurface_ = makeRGBSurface(static_cast(contentW), static_cast(contentH), true); + + if(minimapSurface_) + { + // Clear with transparency + SDL_FillRect(minimapSurface_.get(), nullptr, SDL_MapRGBA(minimapSurface_->format, 0, 0, 0, 0)); + + // Draw minimap onto the temporary surface + map->drawMinimap(minimapSurface_.get()); + + // Upload to texture and draw + minimapTex_.load(minimapSurface_.get()); + minimapTex_.Draw(Rect(contentX, contentY, contentW, contentH)); + } } - return true; } diff --git a/CIO/CMinimapWindow.h b/CIO/CMinimapWindow.h index 2d16dd6..7817e06 100644 --- a/CIO/CMinimapWindow.h +++ b/CIO/CMinimapWindow.h @@ -4,11 +4,16 @@ #pragma once +#include "../Texture.h" #include "CWindow.h" class CMinimapWindow final : public CWindow { - bool render() final; + /// Temporary SDL surface for minimap terrain overlay (kept until terrain is also OpenGL) + SdlSurface minimapSurface_; + Texture minimapTex_; + + void Draw(Position parentOrigin) override; public: using CWindow::CWindow; diff --git a/CIO/CPicture.cpp b/CIO/CPicture.cpp index 7130a1c..b75863e 100644 --- a/CIO/CPicture.cpp +++ b/CIO/CPicture.cpp @@ -4,7 +4,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "CPicture.h" -#include "../CSurface.h" +#include "../Texture.h" #include "../globals.h" #include "CollisionDetection.h" @@ -22,13 +22,12 @@ CPicture::CPicture(void callback(int), int clickedParam, Position pos, int pictu this->clickedParam = clickedParam; motionEntryParam = -1; motionLeaveParam = -1; - needRender = true; } void CPicture::setMouseData(const SDL_MouseMotionEvent& motion) { // cursor is on the picture - if(IsPointInRect(Position(motion.x, motion.y), Rect(pos_, size_))) + if(IsPointInRect(motion.x, motion.y, Rect(pos_, size_))) { if(motion.state == SDL_RELEASED) { @@ -43,7 +42,6 @@ void CPicture::setMouseData(const SDL_MouseMotionEvent& motion) callback(motionLeaveParam); marked = false; } - needRender = true; } void CPicture::setMouseData(const SDL_MouseButtonEvent& button) @@ -52,7 +50,7 @@ void CPicture::setMouseData(const SDL_MouseButtonEvent& button) if(button.button == SDL_BUTTON_LEFT) { // if mouse button is pressed ON the button, set marked=true - if((button.state == SDL_PRESSED) && IsPointInRect(Position(button.x, button.y), Rect(pos_, size_))) + if(button.state == SDL_PRESSED && IsPointInRect(button.x, button.y, Rect(pos_, size_))) { marked = true; clicked = true; @@ -64,25 +62,14 @@ void CPicture::setMouseData(const SDL_MouseButtonEvent& button) callback(clickedParam); } } - needRender = true; } -bool CPicture::render() +void CPicture::Draw(Position parentOrigin) const { - // if we don't need to render, all is up to date, return true - if(!needRender) - return true; - needRender = false; - // if we need a new surface - if(!Surf_Picture) - { - Surf_Picture = makeRGBSurface(size_.x, size_.y); - if(!Surf_Picture) - return false; - SDL_SetColorKey(Surf_Picture.get(), SDL_TRUE, SDL_MapRGB(Surf_Picture->format, 0, 0, 0)); - } - - CSurface::Draw(Surf_Picture, global::bmpArray[picture_].surface); - - return true; + if(picture_ < 0 || picture_ >= static_cast(global::bmpArray.size())) + return; + auto& tex = getBmpTexture(picture_); + if(!tex.isValid()) + return; + tex.Draw(parentOrigin + pos_); } diff --git a/CIO/CPicture.h b/CIO/CPicture.h index 6b648c2..797998e 100644 --- a/CIO/CPicture.h +++ b/CIO/CPicture.h @@ -6,15 +6,13 @@ #pragma once #include "Point.h" -#include "SdlSurface.h" +#include "defines.h" class CPicture { friend class CDebug; private: - SdlSurface Surf_Picture; - bool needRender; Position pos_; Extent size_; int picture_; @@ -30,17 +28,13 @@ class CPicture // Access int getX() const { return pos_.x; }; int getY() const { return pos_.y; }; + const Position& getPos() const { return pos_; } const Extent& getSize() const { return size_; }; void setX(int x) { pos_.x = x; }; void setY(int y) { pos_.y = y; }; void setMouseData(const SDL_MouseMotionEvent& motion); void setMouseData(const SDL_MouseButtonEvent& button); - bool render(); - SDL_Surface* getSurface() - { - render(); - return Surf_Picture.get(); - }; + void Draw(Position parentOrigin) const; void setMotionParams(int entry, int leave) { motionEntryParam = entry; diff --git a/CIO/CSelectBox.cpp b/CIO/CSelectBox.cpp index 04da9b4..ceda380 100644 --- a/CIO/CSelectBox.cpp +++ b/CIO/CSelectBox.cpp @@ -4,10 +4,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "CSelectBox.h" -#include "../CSurface.h" +#include "../CGame.h" +#include "../Texture.h" #include "../globals.h" #include "CButton.h" #include "CFont.h" +#include CSelectBox::CSelectBox(Position pos, Extent size, FontSize fontsize, FontColor text_color, int bg_color) : pos_(pos), size_(size), fontsize(fontsize), text_color(text_color) @@ -82,8 +84,6 @@ void CSelectBox::setColor(int color) pic_background = -1; break; } - - needRender = true; } void CSelectBox::setMouseData(SDL_MouseMotionEvent motion) @@ -94,7 +94,6 @@ void CSelectBox::setMouseData(SDL_MouseMotionEvent motion) motion.y -= pos_.y; ScrollUpButton->setMouseData(motion); ScrollDownButton->setMouseData(motion); - needRender = true; } void CSelectBox::setMouseData(SDL_MouseButtonEvent button) @@ -199,8 +198,6 @@ void CSelectBox::setMouseData(SDL_MouseButtonEvent button) ScrollUpButton->setMouseData(button); ScrollDownButton->setMouseData(button); } - - needRender = true; } void CSelectBox::setSize(Extent size) @@ -208,8 +205,6 @@ void CSelectBox::setSize(Extent size) if(size_ != size) { size_ = size; - Surf_SelectBox.reset(); - needRender = true; // update scroll down button position ScrollDownButton->setY(size_.y - 1 - 20); } @@ -218,84 +213,40 @@ void CSelectBox::setSize(Extent size) void CSelectBox::setPos(Position pos) { pos_ = pos; - needRender = true; } -bool CSelectBox::render() +void CSelectBox::Draw(Position parentOrigin) { - // position in the Surface 'Surf_SelectBox' - Position pos{0, 0}; - // width and height of the button color source picture - Extent pic{0, 0}; + const Position absPos = parentOrigin + pos_; + const Rect area(absPos, size_); - // if we don't need to render, all is up to date, return true - if(!needRender) - return true; - needRender = false; - // if we need a new surface - if(!Surf_SelectBox) - { - if((Surf_SelectBox = makeRGBSurface(size_.x, size_.y)) == nullptr) - return false; - } - - // draw the pictures for background and foreground or, if not set, fill with black color + // Draw background if(pic_background >= 0 && pic_foreground >= 0) { - // at first completly fill the background (not the fastest way, but simplier) - if(size_.x <= global::bmpArray[pic_foreground].w) - pic.x = size_.x; - else - pic.x = global::bmpArray[pic_foreground].w; - - if(size_.y <= global::bmpArray[pic_foreground].h) - pic.y = size_.y; - else - pic.y = global::bmpArray[pic_foreground].h; - - while(pos.x + pic.x <= static_cast(Surf_SelectBox->w)) - { - while(pos.y + pic.y <= static_cast(Surf_SelectBox->h)) - { - CSurface::Draw(Surf_SelectBox, global::bmpArray[pic_foreground].surface, pos, Position(0, 0), pic); - pos.y += pic.y; - } - - if(pos.y < Surf_SelectBox->h) - CSurface::Draw(Surf_SelectBox, global::bmpArray[pic_foreground].surface, pos.x, pos.y, 0, 0, pic.x, - static_cast(Surf_SelectBox->h - pos.y)); - - pos.y = 0; - pos.x += pic.x; - } - - if(pos.x < Surf_SelectBox->w) - { - while(pos.y + pic.y <= static_cast(Surf_SelectBox->h)) - { - CSurface::Draw(Surf_SelectBox, global::bmpArray[pic_foreground].surface, pos.x, pos.y, 0, 0, - static_cast(Surf_SelectBox->w - pos.x), pic.y); - pos.y += pic.y; - } - - if(pos.y < Surf_SelectBox->h) - CSurface::Draw(Surf_SelectBox, global::bmpArray[pic_foreground].surface, pos.x, pos.y, 0, 0, - static_cast(Surf_SelectBox->w - pos.x), - static_cast(Surf_SelectBox->h - pos.y)); - } + drawTiledBmp(pic_foreground, area); } else - SDL_FillRect(Surf_SelectBox.get(), nullptr, SDL_MapRGB(Surf_SelectBox->format, 0, 0, 0)); + { + // Fill with black + DrawRect(area, 0, 0, 0, 255); + } - for(auto& entry : Entries) + // Clip entries to the select box area + const auto viewH = global::s2->getRes().y; + glEnable(GL_SCISSOR_TEST); + glScissor(area.left, viewH - (area.top + static_cast(size_.y)), static_cast(size_.x), + static_cast(size_.y)); + + // Draw entries + for(const auto& entry : Entries) { - CSurface::Draw(Surf_SelectBox, entry->getSurface(), entry->getX(), entry->getY()); + entry->Draw(absPos); } - CSurface::Draw(Surf_SelectBox, ScrollUpButton->getSurface(), static_cast(size_.x) - 1 - 20, 0); - CSurface::Draw(Surf_SelectBox, ScrollDownButton->getSurface(), static_cast(size_.x) - 1 - 20, - static_cast(size_.y) - 1 - 20); + glDisable(GL_SCISSOR_TEST); - rendered = true; + // Draw scroll buttons (on top, within the select box) + ScrollUpButton->Draw(absPos); + ScrollDownButton->Draw(absPos); - return true; + rendered = true; } diff --git a/CIO/CSelectBox.h b/CIO/CSelectBox.h index 8a62d13..88e277b 100644 --- a/CIO/CSelectBox.h +++ b/CIO/CSelectBox.h @@ -5,7 +5,6 @@ #pragma once -#include "SdlSurface.h" #include "defines.h" #include #include @@ -19,7 +18,6 @@ class CSelectBox friend class CDebug; private: - SdlSurface Surf_SelectBox; std::vector> Entries; Position pos_; Extent size_; @@ -30,9 +28,7 @@ class CSelectBox std::unique_ptr ScrollUpButton; std::unique_ptr ScrollDownButton; Uint16 last_text_pos_y = 10; - // we need this to say the window if it needs to render, otherwise no chiffre are shown bool rendered = false; - bool needRender = true; public: CSelectBox(Position pos, Extent size, FontSize fontsize = FontSize::Large, FontColor text_color = FontColor::Yellow, @@ -42,18 +38,9 @@ class CSelectBox bool hasRendered(); void setMouseData(SDL_MouseButtonEvent button); void setMouseData(SDL_MouseMotionEvent motion); - bool render(); - SdlSurface& getSurface() - { - render(); - return Surf_SelectBox; - } + void Draw(Position parentOrigin); void setColor(int color); - void setTextColor(FontColor color) - { - text_color = color; - needRender = true; - } + void setTextColor(FontColor color) { text_color = color; } void addOption(const std::string& string, std::function callback = nullptr, int param = 0); void setSize(Extent size); void setPos(Position pos); diff --git a/CIO/CTextfield.cpp b/CIO/CTextfield.cpp index 81a7c49..1c7e2fa 100644 --- a/CIO/CTextfield.cpp +++ b/CIO/CTextfield.cpp @@ -4,10 +4,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "CTextfield.h" -#include "../CSurface.h" +#include "../Texture.h" #include "../globals.h" #include "CFont.h" #include "CollisionDetection.h" +#include +#include CTextfield::CTextfield(Position pos, Uint16 cols, Uint16 rows, FontSize fontsize, FontColor text_color, int bg_color, bool button_style) @@ -24,7 +26,6 @@ CTextfield::CTextfield(Position pos, Uint16 cols, Uint16 rows, FontSize fontsize // allocate memory for the text: chiffres (cols) + '\n' for each line * rows + blinking chiffre + '\0' text_.resize((this->cols + 1) * this->rows + 2); - needRender = true; rendered = false; this->button_style = button_style; textObj = std::make_unique("", pos, fontsize, text_color); @@ -89,14 +90,11 @@ void CTextfield::setColor(int color) pic_background = -1; break; } - - needRender = true; } void CTextfield::setTextColor(FontColor color) { textObj->setColor(color); - needRender = true; } void CTextfield::setX(int x) @@ -145,10 +143,9 @@ void CTextfield::setMouseData(SDL_MouseButtonEvent button) // if mouse button is pressed ON the textfield, set active=true if(button.state == SDL_PRESSED) { - active = IsPointInRect(Position(button.x, button.y), Rect(Position(getX(), getY()), size_)); + active = IsPointInRect(button.x, button.y, Rect(Position(getX(), getY()), size_)); } } - needRender = true; } void CTextfield::setKeyboardData(const SDL_KeyboardEvent& key) @@ -239,213 +236,83 @@ void CTextfield::setKeyboardData(const SDL_KeyboardEvent& key) } break; } - needRender = true; } } -bool CTextfield::render() +void CTextfield::Draw(Position parentOrigin) { - // position in the Surface 'Surf_Text' - Position pos{0, 0}; - // width and height of the picture tile - Extent pic{0, 0}; - // we save the time to let a chiffre blink - static Uint32 currentTime; - static Uint32 lastTime = SDL_GetTicks(); - static bool blinking_chiffre = false; - // if the textfield is active, we need to render to show the blinking chiffre + const Position absPos = parentOrigin + Position(getX(), getY()); + const Rect area(absPos, size_); + + // Update cursor blink state + static Uint32 lastTime = 0; // Shared timer is OK here if(active) { - currentTime = SDL_GetTicks(); + const Uint32 currentTime = SDL_GetTicks(); + if(lastTime == 0) + lastTime = currentTime; if(currentTime - lastTime > 500) { lastTime = currentTime; - blinking_chiffre = !blinking_chiffre; } - needRender = true; - } - - // if we don't need to render, all is up to date, return true - if(!needRender) - return true; - needRender = false; - // if we need a new surface - if(!Surf_Text) + } else { - Surf_Text = makeRGBSurface(size_.x, size_.y); - if(!Surf_Text) - return false; + blinking_chiffre = false; } - // draw the pictures for background and foreground or, if not set, fill with black color + // Ensure rendered flag is cleared each frame + rendered = false; + + // Draw the background / foreground if(pic_background >= 0 && pic_foreground >= 0) { - // in case the textfield should look like a button, we do it, otherwise we use pic_foreground for the background const int bmpIdx = button_style ? pic_background : pic_foreground; + drawTiledBmp(bmpIdx, area); - // at first completly fill the background (not the fastest way, but simplier) - if(size_.x <= global::bmpArray[bmpIdx].w) - pic.x = size_.x; - else - pic.x = global::bmpArray[bmpIdx].w; - - if(size_.y <= global::bmpArray[bmpIdx].h) - pic.y = size_.y; - else - pic.y = global::bmpArray[bmpIdx].h; - - while(pos.x + pic.x <= static_cast(Surf_Text->w)) - { - while(pos.y + pic.y <= static_cast(Surf_Text->h)) - { - CSurface::Draw(Surf_Text, global::bmpArray[bmpIdx].surface, pos, Position(0, 0), pic); - pos.y += pic.y; - } - - if(pos.y < Surf_Text->h) - CSurface::Draw(Surf_Text, global::bmpArray[bmpIdx].surface, pos.x, pos.y, 0, 0, pic.x, - static_cast(Surf_Text->h - pos.y)); - - pos.y = 0; - pos.x += pic.x; - } - - if(pos.x < Surf_Text->w) - { - while(pos.y + pic.y <= static_cast(Surf_Text->h)) - { - CSurface::Draw(Surf_Text, global::bmpArray[bmpIdx].surface, pos.x, pos.y, 0, 0, - static_cast(Surf_Text->w - pos.x), pic.y); - pos.y += pic.y; - } - - if(pos.y < Surf_Text->h) - CSurface::Draw(Surf_Text, global::bmpArray[bmpIdx].surface, pos.x, pos.y, 0, 0, - static_cast(Surf_Text->w - pos.x), - static_cast(Surf_Text->h - pos.y)); - } - - // if not button_style, we are finished, otherwise continue drawing if(button_style) { - // draw partial black frame + // Draw black frame (2px) using filled rectangles + const int w = area.right - area.left; + const int h = area.bottom - area.top; + if(active) { - // black frame is left and up - // draw vertical line - for(unsigned y = 0; y < size_.y; y++) - CSurface::DrawPixel_RGB(Surf_Text, Position(0, y), 0, 0, 0); - - // draw vertical line - for(unsigned y = 0; y < size_.y - 1; y++) - CSurface::DrawPixel_RGB(Surf_Text, Position(1, y), 0, 0, 0); - - // draw horizontal line - for(unsigned x = 0; x < size_.x; x++) - CSurface::DrawPixel_RGB(Surf_Text, Position(x, 0), 0, 0, 0); - - // draw horizontal line - for(unsigned x = 0; x < size_.x - 1; x++) - CSurface::DrawPixel_RGB(Surf_Text, Position(x, 1), 0, 0, 0); + // Black frame is left and up + DrawRect(Rect(area.left, area.top, 2, h), 0, 0, 0); + DrawRect(Rect(area.left, area.top, w, 2), 0, 0, 0); } else { - // black frame is right and down - // draw vertical line - for(unsigned y = 0; y < size_.y; y++) - CSurface::DrawPixel_RGB(Surf_Text, Position(size_.x - 1, y), 0, 0, 0); - - // draw vertical line - for(unsigned y = 1; y < size_.y; y++) - CSurface::DrawPixel_RGB(Surf_Text, Position(size_.x - 2, y), 0, 0, 0); - - // draw horizontal line - for(unsigned x = 0; x < size_.x; x++) - CSurface::DrawPixel_RGB(Surf_Text, Position(x, size_.y - 1), 0, 0, 0); - - // draw horizontal line - for(unsigned x = 1; x < size_.x; x++) - CSurface::DrawPixel_RGB(Surf_Text, Position(x, size_.y - 2), 0, 0, 0); - } - - // draw the foreground --> at first the color (marked or unmarked) and then the picture or text - if(size_.x <= global::bmpArray[pic_foreground].w) - pic.x = size_.x; - else - pic.x = global::bmpArray[pic_foreground].w; - - if(size_.y <= global::bmpArray[pic_foreground].h) - pic.y = size_.y; - else - pic.y = global::bmpArray[pic_foreground].h; - - // beware overdrawing the left and upper frame - pos.x = 2; - pos.y = 2; - - // '-2' follows a few times, this means: beware overdrawing the right and lower frame - while(pos.x + pic.x <= static_cast(Surf_Text->w - 2)) - { - while(pos.y + pic.y <= static_cast(Surf_Text->h - 2)) - { - CSurface::Draw(Surf_Text, global::bmpArray[pic_foreground].surface, pos.x, pos.y, 0, 0, pic.x, - pic.y); - pos.y += pic.y; - } - - if(pos.y + 2 < Surf_Text->h) - CSurface::Draw(Surf_Text, global::bmpArray[pic_foreground].surface, pos.x, pos.y, 0, 0, pic.x, - static_cast(Surf_Text->h - 2 - pos.y)); - - pos.y = 2; - pos.x += pic.x; + // Black frame is right and down + DrawRect(Rect(area.right - 2, area.top, 2, h), 0, 0, 0); + DrawRect(Rect(area.left, area.bottom - 2, w, 2), 0, 0, 0); } - if(pos.x + 2 < Surf_Text->w) - { - while(pos.y + pic.y <= static_cast(Surf_Text->h - 2)) - { - CSurface::Draw(Surf_Text, global::bmpArray[pic_foreground].surface, pos.x, pos.y, 0, 0, - static_cast(Surf_Text->w - 2 - pos.x), pic.y); - pos.y += pic.y; - } - - if(pos.y + 2 < Surf_Text->h) - CSurface::Draw(Surf_Text, global::bmpArray[pic_foreground].surface, pos.x, pos.y, 0, 0, - static_cast(Surf_Text->w - 2 - pos.x), - static_cast(Surf_Text->h - 2 - pos.y)); - } + // Draw foreground (inset by 2px) + const Rect fgRect(area.getOrigin() + Position(2, 2), size_ - Extent(4, 4)); + drawTiledBmp(pic_foreground, fgRect); } } else - SDL_FillRect(Surf_Text.get(), nullptr, SDL_MapRGB(Surf_Text->format, 0, 0, 0)); - - char* txtPtr = text_.data(); - - // go to '\0' - while(*txtPtr != '\0') - txtPtr++; - // add blinking chiffre if necessary - if(blinking_chiffre && active) { - *txtPtr = '>'; - txtPtr++; - *txtPtr = '\0'; + // Fill with black + DrawRect(area, 0, 0, 0, 255); } - // write text - textObj->setText(text_.data()); + // Prepare text with cursor + std::string displayText = text_.data(); - // delete blinking chiffre (otherwise it could be written between user input chiffres) + // Add blinking cursor if active if(blinking_chiffre && active) { - txtPtr--; - *txtPtr = '\0'; + displayText += '>'; } - // blit text surface - CSurface::Draw(Surf_Text, textObj->getSurface(), 2, 2); + // Draw the text + if(!displayText.empty()) + { + textObj->setText(displayText); + textObj->Draw(Position(area.left + 2, area.top + 2)); + } rendered = true; - - return true; } diff --git a/CIO/CTextfield.h b/CIO/CTextfield.h index 73761b1..2a3c9d8 100644 --- a/CIO/CTextfield.h +++ b/CIO/CTextfield.h @@ -7,6 +7,7 @@ #include "defines.h" #include +#include #include class CFont; @@ -16,9 +17,7 @@ class CTextfield friend class CDebug; private: - SdlSurface Surf_Text; std::unique_ptr textObj; - bool needRender; Extent size_; Uint16 cols; Uint16 rows; @@ -31,6 +30,8 @@ class CTextfield bool rendered; // if true, the textfield looks like a button bool button_style; + // Cursor blink state + bool blinking_chiffre = false; public: // Constructor - Destructor @@ -51,12 +52,7 @@ class CTextfield bool hasRendered(); void setMouseData(SDL_MouseButtonEvent button); void setKeyboardData(const SDL_KeyboardEvent& key); - bool render(); - SDL_Surface* getSurface() - { - render(); - return Surf_Text.get(); - } + void Draw(Position parentOrigin); void setColor(int color); void setTextColor(FontColor color); std::string getText() const { return text_.data(); } diff --git a/CIO/CWindow.cpp b/CIO/CWindow.cpp index ec4fa36..5d9d575 100644 --- a/CIO/CWindow.cpp +++ b/CIO/CWindow.cpp @@ -5,7 +5,7 @@ #include "CWindow.h" #include "../CGame.h" -#include "../CSurface.h" +#include "../Texture.h" #include "../globals.h" #include "CButton.h" #include "CFont.h" @@ -14,6 +14,7 @@ #include "CTextfield.h" #include "CollisionDetection.h" #include "helpers/containerUtils.h" +#include #include CWindow::CWindow(void callback(int), int callbackQuitMessage, Position pos, Extent size, const char* title, int color, @@ -24,15 +25,6 @@ CWindow::CWindow(void callback(int), int callbackQuitMessage, Position pos, Exte callbackQuitMessage(callbackQuitMessage) { assert(callback); - // ensure window is big enough to take all basic pictures needed - // if ( w < (global::bmpArray[WINDOW_LEFT_UPPER_CORNER].w + global::bmpArray[WINDOW_UPPER_FRAME].w + - // global::bmpArray[WINDOW_RIGHT_UPPER_CORNER].w) ) - // this->w = global::bmpArray[WINDOW_LEFT_UPPER_CORNER].w + global::bmpArray[WINDOW_UPPER_FRAME].w + - // global::bmpArray[WINDOW_RIGHT_UPPER_CORNER].w; - // else - // if ( h < (global::bmpArray[WINDOW_UPPER_FRAME].h + global::bmpArray[WINDOW_CORNER_RECTANGLE].h) ) - // this->h = global::bmpArray[WINDOW_UPPER_FRAME].h + global::bmpArray[WINDOW_CORNER_RECTANGLE].h; - // else canMove = (flags & WINDOW_MOVE) != 0; canClose = (flags & WINDOW_CLOSE) != 0; canMinimize = (flags & WINDOW_MINIMIZE) != 0; @@ -57,7 +49,6 @@ CWindow::CWindow(void callback(int), int callbackQuitMessage, WindowPos pos, Ext void CWindow::setTitle(const char* title) { this->title = title; - needRender = true; } void CWindow::setColor(int color) @@ -76,7 +67,7 @@ void CWindow::setMouseData(SDL_MouseMotionEvent motion) const Position titleFrameLT = Position(x_, y_) + Position(global::bmpArray[WINDOW_LEFT_UPPER_CORNER].w + 2, 4); const Position titleFrameRB = Position(x_ + w_ - global::bmpArray[WINDOW_RIGHT_UPPER_CORNER].w - 2, y_ + global::bmpArray[WINDOW_UPPER_FRAME].h - 4); - if(IsPointInRect(Position(motion.x, motion.y), Rect(titleFrameLT, Extent(titleFrameRB - titleFrameLT)))) + if(IsPointInRect(motion.x, motion.y, Rect(titleFrameLT, Extent(titleFrameRB - titleFrameLT)))) { // left button was pressed while moving if(clicked) @@ -144,9 +135,6 @@ void CWindow::setMouseData(SDL_MouseMotionEvent motion) // MISSING: we have to test if window size is under minimum - // the window has resized, so we need a new surface - surface.reset(); - // notify the callback that the window has been resized callback_(WINDOW_RESIZED_CALL); } @@ -154,7 +142,7 @@ void CWindow::setMouseData(SDL_MouseMotionEvent motion) } // deliver mouse data to the content objects of the window (if mouse cursor is inside the window) - if(IsPointInRect(Position(motion.x, motion.y), Rect(getPos(), getSize()))) + if(IsPointInRect(motion.x, motion.y, Rect(getPos(), getSize()))) { // IMPORTANT: we use the left upper corner of the window as (x,y)=(0,0), so we have to manipulate // the motion-structure before give it to buttons, pictures....: x_absolute - x_window, y_absolute - @@ -163,8 +151,6 @@ void CWindow::setMouseData(SDL_MouseMotionEvent motion) motion.y -= y_; CControlContainer::setMouseData(motion); } - - needRender = true; } void CWindow::setMouseData(SDL_MouseButtonEvent button) @@ -195,7 +181,7 @@ void CWindow::setMouseData(SDL_MouseButtonEvent button) clicked = true; } // pressed inside the window - if((button.state == SDL_PRESSED) && (button.x >= x_) && (button.x <= x_ + w_) && (button.y >= y_) + if(button.state == SDL_PRESSED && (button.x >= x_) && (button.x <= x_ + w_) && (button.y >= y_) && (button.y <= y_ + h_)) marked = true; // else pressed outside of the window @@ -230,14 +216,10 @@ void CWindow::setMouseData(SDL_MouseButtonEvent button) if(minimized) // maximize now { h_ = maximized_h; - // the window has resized, so we need a new surface - surface.reset(); minimized = false; } else // minimize now { h_ = global::bmpArray[WINDOW_UPPER_FRAME].h + global::bmpArray[WINDOW_CORNER_RECTANGLE].h; - // the window has resized, so we need a new surface - surface.reset(); minimized = true; } } @@ -251,7 +233,7 @@ void CWindow::setMouseData(SDL_MouseButtonEvent button) } // deliver mouse data to the content objects of the window (if mouse cursor is inside the window) - if(IsPointInRect(Position(button.x, button.y), Rect(getPos(), getSize()))) + if(IsPointInRect(button.x, button.y, Rect(getPos(), getSize()))) { // IMPORTANT: we use the left upper corner of the window as (x,y)=(0,0), so we have to manipulate // the motion-structure before give it to buttons, pictures....: x_absolute - x_window, y_absolute - @@ -263,85 +245,42 @@ void CWindow::setMouseData(SDL_MouseButtonEvent button) // at least call the callback callback_(WINDOW_CLICKED_CALL); - - needRender = true; } -bool CWindow::render() +// --------------------------------------------------------------------------- +// Draw — OpenGL version of the old render() +// --------------------------------------------------------------------------- + +void CWindow::Draw(Position /*parentOrigin*/) { - // position in the Surface 'surface' - Position pos = {0, 0}; - // width and height of the window background color source picture - Uint16 pic_w = 0; - Uint16 pic_h = 0; - // upper frame (can be marked, clicked or normal) - int upperframe; - // close button (can be marked, clicked or normal) - int closebutton = WINDOW_BUTTON_CLOSE; - // minimize button (can be marked, clicked or normal) - int minimizebutton = WINDOW_BUTTON_MINIMIZE; - // resize button (can be marked, clicked or normal) - int resizebutton = WINDOW_BUTTON_RESIZE; - - // test if a textfield has changed - needRender |= helpers::contains_if(getTextFields(), [](const auto& textfield) { return textfield->hasRendered(); }); - - // if we don't need to render, all is up to date, return true - if(!needRender) - return true; - needRender = false; - // if we need a new surface - if(!surface) - { - if(!(surface = makeRGBSurface(w_, h_))) - return false; - } + const Position origin(x_, y_); + const Rect winRect(origin, Extent(w_, h_)); - // at first completly fill the background (not the fastest way, but simpler) + // 1. Background fill (tiled) if(getBackground() != WINDOW_NOTHING) - { - pic_w = std::min(w_, global::bmpArray[getBackground()].w); - pic_h = std::min(h_, global::bmpArray[getBackground()].h); - - while(pos.x + pic_w <= surface->w) - { - while(pos.y + pic_h <= surface->h) - { - CSurface::Draw(surface.get(), global::bmpArray[getBackground()].surface, pos.x, pos.y, 0, 0, pic_w, - pic_h); - pos.y += pic_h; - } + drawTiledBmp(getBackground(), winRect); - if(surface->h - pos.y > 0) - CSurface::Draw(surface.get(), global::bmpArray[getBackground()].surface, pos.x, pos.y, 0, 0, pic_w, - surface->h - pos.y); - - pos.y = 0; - pos.x += pic_w; - } - - if(surface->w - pos.x > 0) - { - while(pos.y + pic_h <= surface->h) - { - CSurface::Draw(surface.get(), global::bmpArray[getBackground()].surface, pos.x, pos.y, 0, 0, - surface->w - pos.x, pic_h); - pos.y += pic_h; - } - - if(surface->h - pos.y > 0) - CSurface::Draw(surface.get(), global::bmpArray[getBackground()].surface, pos.x, pos.y, 0, 0, - surface->w - pos.x, surface->h - pos.y); - } - } - - // if not minimized, draw the content now (this stands here to prevent the frames and corners from being overdrawn) + // 2. Content (if not minimized) — clipped to the area inside frames if(!minimized) { - renderElements(); + const auto viewH = global::s2->getRes().y; + const auto contentX = origin.x + static_cast(getBorderBegin().x); + const auto contentY = origin.y + static_cast(getBorderBegin().y); + const auto contentW = + static_cast(w_) - static_cast(getBorderBegin().x) - static_cast(getBorderEnd().x); + const auto contentH = + static_cast(h_) - static_cast(getBorderBegin().y) - static_cast(getBorderEnd().y); + if(contentW > 0 && contentH > 0) + { + glEnable(GL_SCISSOR_TEST); + glScissor(contentX, viewH - (contentY + contentH), contentW, contentH); + DrawChildren(origin); + glDisable(GL_SCISSOR_TEST); + } } - // now draw the upper frame to the top + // 3. Upper frame (state depends on marked/clicked) + int upperframe; if(clicked) upperframe = WINDOW_UPPER_FRAME_CLICKED; else if(marked) @@ -349,110 +288,117 @@ bool CWindow::render() else upperframe = WINDOW_UPPER_FRAME; - pic_w = std::min(w_, global::bmpArray[upperframe].w); - - pos.x = 0; - pos.y = 0; - while(pos.x + pic_w <= surface->w) + // Draw upper frame tile across the top of the window { - CSurface::Draw(surface, global::bmpArray[upperframe].surface, pos); - pos.x += pic_w; + const auto& bmp = global::bmpArray[upperframe]; + const Rect upperFrameRect(origin, Extent(w_, bmp.h)); + drawTiledBmp(upperframe, upperFrameRect); } - if(surface->w - pos.x > 0) - CSurface::Draw(surface.get(), global::bmpArray[upperframe].surface, pos.x, pos.y, 0, 0, surface->w - pos.x, - pic_h); - // write text in the upper frame + // 4. Title text if(title) - CFont::writeText(surface.get(), title, (int)w_ / 2, (int)((global::bmpArray[WINDOW_UPPER_FRAME].h - 9) / 2), - FontSize::Small, FontColor::Yellow, FontAlign::Middle); - - // now draw the other frames (left, right, down) - // down - pic_w = std::min(w_, global::bmpArray[WINDOW_LOWER_FRAME].w); - pic_h = global::bmpArray[WINDOW_LOWER_FRAME].h; - pos.x = 0; - pos.y = h_ - global::bmpArray[WINDOW_LOWER_FRAME].h; - while(pos.x + pic_w <= surface->w) { - CSurface::Draw(surface, global::bmpArray[WINDOW_LOWER_FRAME].surface, pos); - pos.x += pic_w; + const int titleY = origin.y + (static_cast(global::bmpArray[WINDOW_UPPER_FRAME].h) - 9) / 2; + CFont::Draw(title, Position(origin.x + static_cast(w_) / 2, titleY), FontSize::Small, FontColor::Yellow, + FontAlign::Middle); + } + + // 5. Lower frame (tiled across bottom) + { + const auto& bmp = global::bmpArray[WINDOW_LOWER_FRAME]; + const Rect lowerFrameRect(Position(origin.x, origin.y + static_cast(h_) - static_cast(bmp.h)), + Extent(w_, bmp.h)); + drawTiledBmp(WINDOW_LOWER_FRAME, lowerFrameRect); + } + + // 6. Left frame (tiled down left side) + { + const auto& bmp = global::bmpArray[WINDOW_LEFT_FRAME]; + const Rect leftFrameRect(origin, Extent(bmp.w, h_)); + drawTiledBmp(WINDOW_LEFT_FRAME, leftFrameRect); } - if(surface->w - pos.x > 0) - CSurface::Draw(surface.get(), global::bmpArray[WINDOW_LOWER_FRAME].surface, pos.x, pos.y, 0, 0, - surface->w - pos.x, pic_h); - // left - pic_h = std::min(h_, global::bmpArray[WINDOW_LEFT_FRAME].h); - pos.x = 0; - pos.y = 0; - while(pos.y + pic_h <= surface->h) + + // 7. Right frame (tiled down right side) { - CSurface::Draw(surface, global::bmpArray[WINDOW_LEFT_FRAME].surface, pos); - pos.y += pic_h; + const auto& bmp = global::bmpArray[WINDOW_RIGHT_FRAME]; + const Rect rightFrameRect(Position(origin.x + static_cast(w_) - static_cast(bmp.w), origin.y), + Extent(bmp.w, h_)); + drawTiledBmp(WINDOW_RIGHT_FRAME, rightFrameRect); } - if(surface->w - pos.x > 0) - CSurface::Draw(surface.get(), global::bmpArray[WINDOW_LEFT_FRAME].surface, pos.x, pos.y, 0, 0, - surface->w - pos.x, pic_h); - // right - pic_h = std::min(h_, global::bmpArray[WINDOW_RIGHT_FRAME].h); - pos.x = w_ - global::bmpArray[WINDOW_RIGHT_FRAME].w; - pos.y = 0; - while(pos.y + pic_h <= surface->h) + + // 8. Corners { - CSurface::Draw(surface, global::bmpArray[WINDOW_RIGHT_FRAME].surface, pos); - pos.y += pic_h; + auto& texLU = getBmpTexture(WINDOW_LEFT_UPPER_CORNER); + if(texLU.isValid()) + texLU.Draw(origin); + + auto& texRU = getBmpTexture(WINDOW_RIGHT_UPPER_CORNER); + if(texRU.isValid()) + { + const auto& ruBmp = global::bmpArray[WINDOW_RIGHT_UPPER_CORNER]; + texRU.Draw(Position(origin.x + static_cast(w_) - static_cast(ruBmp.w), origin.y)); + } + + auto& texCR = getBmpTexture(WINDOW_CORNER_RECTANGLE); + if(texCR.isValid()) + { + const auto& crBmp = global::bmpArray[WINDOW_CORNER_RECTANGLE]; + texCR.Draw(Position(origin.x, origin.y + static_cast(h_) - static_cast(crBmp.h))); + texCR.Draw(Position(origin.x + static_cast(w_) - static_cast(crBmp.w), + origin.y + static_cast(h_) - static_cast(crBmp.h))); + } } - if(surface->w - pos.x > 0) - CSurface::Draw(surface.get(), global::bmpArray[WINDOW_RIGHT_FRAME].surface, pos.x, pos.y, 0, 0, - surface->w - pos.x, pic_h); - - // now draw the corners - CSurface::Draw(surface, global::bmpArray[WINDOW_LEFT_UPPER_CORNER].surface); - CSurface::Draw(surface, global::bmpArray[WINDOW_RIGHT_UPPER_CORNER].surface, - Position(w_ - global::bmpArray[WINDOW_RIGHT_UPPER_CORNER].w, 0)); - CSurface::Draw(surface, global::bmpArray[WINDOW_CORNER_RECTANGLE].surface, - Position(0, h_ - global::bmpArray[WINDOW_CORNER_RECTANGLE].h)); - CSurface::Draw( - surface, global::bmpArray[WINDOW_CORNER_RECTANGLE].surface, - Position(w_ - global::bmpArray[WINDOW_CORNER_RECTANGLE].w, h_ - global::bmpArray[WINDOW_CORNER_RECTANGLE].h)); - // now the corner buttons - // close + + // 9. Close button if(canClose) { + int closebutton; if(canClose_clicked) closebutton = WINDOW_BUTTON_CLOSE_CLICKED; else if(canClose_marked) closebutton = WINDOW_BUTTON_CLOSE_MARKED; else closebutton = WINDOW_BUTTON_CLOSE; - CSurface::Draw(surface, global::bmpArray[closebutton].surface); + auto& tex = getBmpTexture(closebutton); + if(tex.isValid()) + tex.Draw(origin); } - // minimize + + // 10. Minimize button if(canMinimize) { + int minimizebutton; if(canMinimize_clicked) minimizebutton = WINDOW_BUTTON_MINIMIZE_CLICKED; else if(canMinimize_marked) minimizebutton = WINDOW_BUTTON_MINIMIZE_MARKED; else minimizebutton = WINDOW_BUTTON_MINIMIZE; - CSurface::Draw(surface, global::bmpArray[minimizebutton].surface, - Position(w_ - global::bmpArray[minimizebutton].w, 0)); + auto& tex = getBmpTexture(minimizebutton); + if(tex.isValid()) + { + const auto& bmp = global::bmpArray[minimizebutton]; + tex.Draw(Position(origin.x + static_cast(w_) - static_cast(bmp.w), origin.y)); + } } - // resize + + // 11. Resize button if(canResize) { + int resizebutton; if(canResize_clicked) resizebutton = WINDOW_BUTTON_RESIZE_CLICKED; else if(canResize_marked) resizebutton = WINDOW_BUTTON_RESIZE_MARKED; else resizebutton = WINDOW_BUTTON_RESIZE; - CSurface::Draw(surface, global::bmpArray[resizebutton].surface, - Position(w_, h_) - global::bmpArray[resizebutton].getSize()); + auto& tex = getBmpTexture(resizebutton); + if(tex.isValid()) + { + const auto& bmp = global::bmpArray[resizebutton]; + tex.Draw(Position(origin + Position(w_, h_)) - Position(static_cast(bmp.w), static_cast(bmp.h))); + } } - - return true; } void CWindow::setInactive() @@ -460,7 +406,6 @@ void CWindow::setInactive() active = false; clicked = false; marked = false; - needRender = true; for(auto& textfield : getTextFields()) { diff --git a/CIO/CWindow.h b/CIO/CWindow.h index 2faf97e..a236853 100644 --- a/CIO/CWindow.h +++ b/CIO/CWindow.h @@ -16,13 +16,16 @@ class CWindow : public CControlContainer { friend class CDebug; -private: // if active is false, the window will be render behind the active windows within the game loop bool active = true; + +protected: Sint16 x_; Sint16 y_; Uint16 w_; Uint16 h_; + +private: const char* title; bool marked = true; bool clicked = false; @@ -43,10 +46,10 @@ class CWindow : public CControlContainer void (*callback_)(int); int callbackQuitMessage; -protected: - bool render() override; - public: + /// Draw the window using OpenGL (chrome + children). + void Draw(Position parentOrigin) override; + CWindow(void callback(int), int callbackQuitMessage, Position pos, Extent size, const char* title = nullptr, int color = WINDOW_GREEN1, Uint8 flags = 0); CWindow(void callback(int), int callbackQuitMessage, WindowPos pos, Extent size, const char* title = nullptr, @@ -68,14 +71,13 @@ class CWindow : public CControlContainer { active = true; marked = true; - needRender = true; } void setInactive(); bool isActive() const { return active; } bool isMoving() const { return moving; } bool isResizing() const { return resizing; } bool isMarked() const { return marked; } - void setDirty() { needRender = true; } + void setDirty() {} // we can not trust this information, cause if minimized is false, it is possible, that we still have the old // minimized surface bool isMinimized() { return minimized; }; we need an information if a input-element (textfield // etc.) is active to not deliver the input to other gui-element in the event system diff --git a/Texture.cpp b/Texture.cpp index 5064573..1c64a61 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -3,10 +3,18 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "Texture.h" +#include "defines.h" +#include "globals.h" #include +#include +#include #include #include +// --------------------------------------------------------------------------- +// Texture +// --------------------------------------------------------------------------- + Texture::~Texture() { if(texture_) @@ -115,15 +123,19 @@ void Texture::Draw(const Rect& destRect) const if(!texture_) return; + const float uScale = 1.0f; + const float vScale = 1.0f; + + glColor4f(1, 1, 1, 1); glBindTexture(GL_TEXTURE_2D, texture_); glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex2i(destRect.left, destRect.top); - glTexCoord2f(1, 0); + glTexCoord2f(uScale, 0); glVertex2i(destRect.right, destRect.top); - glTexCoord2f(1, 1); + glTexCoord2f(uScale, vScale); glVertex2i(destRect.right, destRect.bottom); - glTexCoord2f(0, 1); + glTexCoord2f(0, vScale); glVertex2i(destRect.left, destRect.bottom); glEnd(); } @@ -133,6 +145,7 @@ void Texture::Draw(Position pos) const if(!texture_) return; + glColor4f(1, 1, 1, 1); glBindTexture(GL_TEXTURE_2D, texture_); glBegin(GL_QUADS); glTexCoord2f(0, 0); @@ -145,3 +158,143 @@ void Texture::Draw(Position pos) const glVertex2i(pos.x, pos.y + size_.y); glEnd(); } + +void Texture::Draw(const Rect& destRect, const Rect& srcRect) const +{ + if(!texture_) + return; + + // Clamp source rect to texture bounds + int srcL = std::max(0, srcRect.left); + int srcT = std::max(0, srcRect.top); + int srcR = std::min(static_cast(size_.x), srcRect.right); + int srcB = std::min(static_cast(size_.y), srcRect.bottom); + if(srcL >= srcR || srcT >= srcB) + return; + + const float u0 = float(srcL) / float(size_.x); + const float v0 = float(srcT) / float(size_.y); + const float u1 = float(srcR) / float(size_.x); + const float v1 = float(srcB) / float(size_.y); + + glBindTexture(GL_TEXTURE_2D, texture_); + glBegin(GL_QUADS); + glTexCoord2f(u0, v0); + glVertex2i(destRect.left, destRect.top); + glTexCoord2f(u1, v0); + glVertex2i(destRect.right, destRect.top); + glTexCoord2f(u1, v1); + glVertex2i(destRect.right, destRect.bottom); + glTexCoord2f(u0, v1); + glVertex2i(destRect.left, destRect.bottom); + glEnd(); +} + +// --------------------------------------------------------------------------- +// DrawRect / DrawLine helpers +// --------------------------------------------------------------------------- + +void DrawRect(const Rect& rect, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + glDisable(GL_TEXTURE_2D); + glColor4ub(r, g, b, a); + glBegin(GL_QUADS); + glVertex2i(rect.left, rect.top); + glVertex2i(rect.right, rect.top); + glVertex2i(rect.right, rect.bottom); + glVertex2i(rect.left, rect.bottom); + glEnd(); + glEnable(GL_TEXTURE_2D); +} + +void DrawRect(const Rect& rect, unsigned color) +{ + DrawRect(rect, (color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, (color >> 24) & 0xFF); +} + +void DrawLine(Position p1, Position p2, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + glDisable(GL_TEXTURE_2D); + glColor4ub(r, g, b, a); + glBegin(GL_LINES); + glVertex2i(p1.x, p1.y); + glVertex2i(p2.x, p2.y); + glEnd(); + glEnable(GL_TEXTURE_2D); +} + +// --------------------------------------------------------------------------- +// Texture-drawing helpers +// --------------------------------------------------------------------------- + +Texture& getBmpTexture(int idx, bool filterLinear) +{ + static std::vector> cache; + static std::vector linearFlags; + if(idx < 0 || idx >= static_cast(global::bmpArray.size())) + { + static Texture dummy; + return dummy; + } + if(static_cast(cache.size()) <= idx) + { + cache.resize(idx + 1); + linearFlags.resize(idx + 1, false); + } + if(!cache[idx] || linearFlags[idx] != filterLinear) + { + if(!cache[idx]) + cache[idx] = std::make_unique(); + linearFlags[idx] = filterLinear; + auto& bmp = global::bmpArray[idx]; + if(bmp.surface) + cache[idx]->load(bmp.surface.get(), filterLinear); + } + return *cache[idx]; +} + +void ensureBmpTex(int idx) +{ + if(idx < 0 || idx >= static_cast(global::bmpArray.size())) + return; + getBmpTexture(idx); +} + +void drawTiledBmp(int bmpIdx, const Rect& destRect) +{ + if(bmpIdx < 0 || bmpIdx >= static_cast(global::bmpArray.size())) + return; + auto& tex = getBmpTexture(bmpIdx); + if(!tex.isValid()) + return; + + const auto& bmp = global::bmpArray[bmpIdx]; + const unsigned tileW = bmp.w; + const unsigned tileH = bmp.h; + if(tileW == 0 || tileH == 0) + return; + + glColor4f(1, 1, 1, 1); + glBindTexture(GL_TEXTURE_2D, tex.getHandle()); + glBegin(GL_QUADS); + for(int y = destRect.top; y < destRect.bottom; y += static_cast(tileH)) + { + const int rowH = std::min(static_cast(tileH), static_cast(destRect.bottom - y)); + const float v1 = static_cast(rowH) / static_cast(tileH); + for(int x = destRect.left; x < destRect.right; x += static_cast(tileW)) + { + const int colW = std::min(static_cast(tileW), static_cast(destRect.right - x)); + const float u1 = static_cast(colW) / static_cast(tileW); + + glTexCoord2f(0, 0); + glVertex2i(x, y); + glTexCoord2f(u1, 0); + glVertex2i(x + colW, y); + glTexCoord2f(u1, v1); + glVertex2i(x + colW, y + rowH); + glTexCoord2f(0, v1); + glVertex2i(x, y + rowH); + } + } + glEnd(); +} diff --git a/Texture.h b/Texture.h index 2d6c26c..33d7419 100644 --- a/Texture.h +++ b/Texture.h @@ -41,6 +41,10 @@ class Texture /// Convenience overload for callers using separate x/y. void Draw(int x, int y) const { Draw(Position(x, y)); } + /// Draw a sub-rectangle of the texture to fill the given destination rect. + /// srcRect is in texture-local coordinates (may be clipped). + void Draw(const Rect& destRect, const Rect& srcRect) const; + /// Returns the raw GL texture name (for use with glBindTexture). unsigned getHandle() const { return texture_; } @@ -60,3 +64,31 @@ class Texture /// Internal: create or recreate texture from raw BGRA pixel data. void load(const void* bgraPixels, Extent size, bool filterLinear); }; + +// --------------------------------------------------------------------------- +// Free functions for simple GL drawing (rect, line) used by UI components. +// --------------------------------------------------------------------------- + +/// Draw a filled rectangle (disables texturing). +void DrawRect(const Rect& rect, unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255); + +/// Draw a filled rectangle with a 32-bit RGBA colour. +void DrawRect(const Rect& rect, unsigned color); + +/// Draw a 1-pixel-wide axis-aligned line (disables texturing). +void DrawLine(Position p1, Position p2, unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255); + +// --------------------------------------------------------------------------- +// Texture-drawing helpers for UI components +// --------------------------------------------------------------------------- + +/// Ensure the OpenGL texture for a bitmap index is loaded from its SDL surface. +void ensureBmpTex(int idx); + +/// Draw a bitmap texture tiled to fill the given rectangle. +void drawTiledBmp(int bmpIdx, const Rect& destRect); + +/// Get or create the cached OpenGL texture for a bitmap index. +/// The texture is loaded from the SDL surface on first access. +/// @param filterLinear Whether to use linear filtering (for scaled backgrounds). +Texture& getBmpTexture(int idx, bool filterLinear = false); diff --git a/callbacks.cpp b/callbacks.cpp index 38ec75b..6c57267 100644 --- a/callbacks.cpp +++ b/callbacks.cpp @@ -54,9 +54,8 @@ void callback::PleaseWait(int Param) // we need to render this window NOW, cause the render loop will do it too late (when the operation // is done and we don't need the "Please wait"-window anymore) { - const auto res = global::s2->getRes(); glClear(GL_COLOR_BUFFER_BIT); - WNDWait->getTexture().Draw(Position(res.x / 2 - 106, res.y / 2 - 35)); + WNDWait->Draw(Position(0, 0)); global::s2->RenderPresent(); } break; diff --git a/include/defines.h b/include/defines.h index 5adf11c..11bc44a 100644 --- a/include/defines.h +++ b/include/defines.h @@ -85,6 +85,8 @@ struct bobBMP SdlSurface surface; }; +class Texture; + // Structure for Bobtype 5 (Palette) struct bobPAL { From 76531bd18aae56cb5333621daa9eceb57cf8f157 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Fri, 3 Jul 2026 20:53:20 +0200 Subject: [PATCH 3/3] drawMinimap --- CIO/CMinimapWindow.cpp | 61 +++++++++++++++++++++++++++++++++--------- CIO/CMinimapWindow.h | 4 +-- CMap.cpp | 51 +++++++++++------------------------ CMap.h | 6 ++++- 4 files changed, 71 insertions(+), 51 deletions(-) diff --git a/CIO/CMinimapWindow.cpp b/CIO/CMinimapWindow.cpp index a91eb0f..3c418cf 100644 --- a/CIO/CMinimapWindow.cpp +++ b/CIO/CMinimapWindow.cpp @@ -7,6 +7,7 @@ #include "../CMap.h" #include "../Texture.h" #include "../globals.h" +#include "CFont.h" void CMinimapWindow::Draw(Position /*parentOrigin*/) { @@ -24,24 +25,58 @@ void CMinimapWindow::Draw(Position /*parentOrigin*/) if(contentW <= 0 || contentH <= 0) return; - // Draw minimap terrain overlay onto a temporary SDL surface, then upload to texture - if(auto* map = global::s2->getMapObj()) + auto* map = global::s2->getMapObj(); + if(!map) + return; + + // Fill pixel buffer with minimap terrain + int num_x = 1, num_y = 1; + map->drawMinimap(pixels_, contentW, contentH, num_x, num_y); + + // Upload to texture and draw + if(!minimapTex_.isValid() || minimapTex_.getWidth() != contentW || minimapTex_.getHeight() != contentH) + minimapTex_.createEmpty(Extent(contentW, contentH)); + minimapTex_.upload(pixels_.data()); + minimapTex_.Draw(Rect(contentX, contentY, contentW, contentH)); + + // Draw player flags and numbers on top + for(int i = 0; i < MAXPLAYERS; i++) { - // Create or resize the minimap surface - if(!minimapSurface_ || minimapSurface_->w != contentW || minimapSurface_->h != contentH) - minimapSurface_ = makeRGBSurface(static_cast(contentW), static_cast(contentH), true); + const auto hqX = map->getPlayerHQx()[i]; + const auto hqY = map->getPlayerHQy()[i]; + if(hqX == 0xFFFF || hqY == 0xFFFF) + continue; - if(minimapSurface_) + const int flagIdx = FLAG_BLUE_DARK + i % 7; + const auto& flagBmp = global::bmpArray[flagIdx]; + auto& flagTex = getBmpTexture(flagIdx); + if(flagTex.isValid()) { - // Clear with transparency - SDL_FillRect(minimapSurface_.get(), nullptr, SDL_MapRGBA(minimapSurface_->format, 0, 0, 0, 0)); + const int fx = contentX + hqX / num_x - static_cast(flagBmp.nx); + const int fy = contentY + hqY / num_y - static_cast(flagBmp.ny); + flagTex.Draw(Position(fx, fy)); + } - // Draw minimap onto the temporary surface - map->drawMinimap(minimapSurface_.get()); + // Player number + CFont::Draw(std::to_string(i + 1), Position(contentX + hqX / num_x, contentY + hqY / num_y), FontSize::Small, + FontColor::MintGreen); + } - // Upload to texture and draw - minimapTex_.load(minimapSurface_.get()); - minimapTex_.Draw(Rect(contentX, contentY, contentW, contentH)); + // Draw the position arrow + { + const int arrowIdx = MAPPIC_ARROWCROSS_ORANGE; + const auto& arrowBmp = global::bmpArray[arrowIdx]; + auto& arrowTex = getBmpTexture(arrowIdx); + if(arrowTex.isValid()) + { + const auto& dispRect = map->getDisplayRect(); + const int ax = contentX + + (dispRect.left + static_cast(dispRect.getSize().x) / 2) / triangleWidth / num_x + - static_cast(arrowBmp.nx); + const int ay = contentY + + (dispRect.top + static_cast(dispRect.getSize().y) / 2) / triangleHeight / num_y + - static_cast(arrowBmp.ny); + arrowTex.Draw(Position(ax, ay)); } } } diff --git a/CIO/CMinimapWindow.h b/CIO/CMinimapWindow.h index 7817e06..dc76cda 100644 --- a/CIO/CMinimapWindow.h +++ b/CIO/CMinimapWindow.h @@ -6,11 +6,11 @@ #include "../Texture.h" #include "CWindow.h" +#include class CMinimapWindow final : public CWindow { - /// Temporary SDL surface for minimap terrain overlay (kept until terrain is also OpenGL) - SdlSurface minimapSurface_; + std::vector pixels_; ///< Pixel buffer for minimap terrain Texture minimapTex_; void Draw(Position parentOrigin) override; diff --git a/CMap.cpp b/CMap.cpp index dd63715..a7e80eb 100644 --- a/CMap.cpp +++ b/CMap.cpp @@ -1328,18 +1328,18 @@ static void getTriangleColor(const bobMAP& map, Uint8 rawTextureId, Sint16& r, S b = 128; } -void CMap::drawMinimap(SDL_Surface* Window) +void CMap::drawMinimap(std::vector& pixels, int w, int h, int& num_x, int& num_y) { - // this variables are needed to reduce the size of minimap-windows of big maps - int num_x = (map->width > 256 ? map->width / 256 : 1); - int num_y = (map->height > 256 ? map->height / 256 : 1); + // Scale factors to keep minimap within a reasonable size + num_x = (map->width > 256 ? map->width / 256 : 1); + num_y = (map->height > 256 ? map->height / 256 : 1); - // make sure the minimap has the same proportions as the "real" map, so scale the same rate + // Keep aspect ratio uniform num_x = (num_x > num_y ? num_x : num_y); - num_y = (num_x > num_y ? num_x : num_y); + num_y = num_x; - // if (Window->w < map->width || Window->h < map->height) - // return; + // Ensure pixel buffer is the right size + pixels.assign(static_cast(w) * h, 0); for(int y = 0; y < map->height; y++) { @@ -1354,9 +1354,12 @@ void CMap::drawMinimap(SDL_Surface* Window) Sint16 r, g, b; getTriangleColor(*map, map->getVertex(x, y).rsuTexture, r, g, b); - Uint32* row = (Uint32*)Window->pixels + (y / num_y + 20) * Window->pitch / 4; //-V206 - //+6 because of the left window frame - Uint32* pixel = row + x / num_x + 6; + const int py = y / num_y; + const int px = x / num_x; + if(py >= h || px >= w) + continue; + + auto& pixel = pixels[static_cast(py) * w + px]; Sint32 vertexLighting = map->getVertex(x, y).i; r = ((r * vertexLighting) >> 16); @@ -1365,32 +1368,10 @@ void CMap::drawMinimap(SDL_Surface* Window) const auto r8 = (Uint8)(r > 255 ? 255 : (r < 0 ? 0 : r)); const auto g8 = (Uint8)(g > 255 ? 255 : (g < 0 ? 0 : g)); const auto b8 = (Uint8)(b > 255 ? 255 : (b < 0 ? 0 : b)); - *pixel = ((r8 << Window->format->Rshift) + (g8 << Window->format->Gshift) + (b8 << Window->format->Bshift)); - } - } - - // draw the player flags - for(int i = 0; i < MAXPLAYERS; i++) - { - if(PlayerHQx[i] != 0xFFFF && PlayerHQy[i] != 0xFFFF) - { - // draw flag - //%7 cause in the original game there are only 7 players and 7 different flags - CSurface::Draw(Window, global::bmpArray[FLAG_BLUE_DARK + i % 7].surface, - 6 + PlayerHQx[i] / num_x - global::bmpArray[FLAG_BLUE_DARK + i % 7].nx, - 20 + PlayerHQy[i] / num_y - global::bmpArray[FLAG_BLUE_DARK + i % 7].ny); - // write player number - CFont::writeText(Window, std::to_string(i + 1), 6 + PlayerHQx[i] / num_x, 20 + PlayerHQy[i] / num_y, - FontSize::Small, FontColor::MintGreen); + // BGRA format: A<<24 | R<<16 | G<<8 | B + pixel = (0xFFu << 24) | (r8 << 16) | (g8 << 8) | b8; } } - - // draw the arrow --> 6px is width of left window frame and 20px is the height of the upper window frame - CSurface::Draw(Window, global::bmpArray[MAPPIC_ARROWCROSS_ORANGE].surface, - 6 + (displayRect.left + displayRect.getSize().x / 2) / triangleWidth / num_x - - global::bmpArray[MAPPIC_ARROWCROSS_ORANGE].nx, - 20 + (displayRect.top + displayRect.getSize().y / 2) / triangleHeight / num_y - - global::bmpArray[MAPPIC_ARROWCROSS_ORANGE].ny); } void CMap::modifyVertex() diff --git a/CMap.h b/CMap.h index 431f2c3..ab6341b 100644 --- a/CMap.h +++ b/CMap.h @@ -161,7 +161,11 @@ class CMap std::string getAuthor() const { return map->getAuthor(); } void setAuthor(const std::string& author) { map->setAuthor(author); } - void drawMinimap(SDL_Surface* Window); + /// Fill a pixel buffer with the minimap terrain view. + /// @param pixels BGRA pixel buffer (w * h entries, 0xAARRGGBB layout). + /// @param w,h Dimensions of the pixel buffer. + /// @param num_x,num_y Output: scaling factors (map coords → pixel coords). + void drawMinimap(std::vector& pixels, int w, int h, int& num_x, int& num_y); void render(); // get and set some variables necessary for cursor behavior void setHexagonMode(bool HexagonMode)