Suggestion for SDL_Window and SDL_Renderer

Hello, SDL3 community. I’ve been programming with SDL for a while now, and I often repeat the same lines of code in my projects. I have a suggestion for adding functionality to SDL_Window and SDL_Renderer.
I am currently creating a game engine in C++ with SDL3 and Vulkan, in which a dynamic UI.
I noticed that you could initialize the window with properties, which I thought was cool because that way you only need to use one SDL function to create the window. I noticed that the minimum and maximum size properties and aspect ratio were missing. Here is an example:

bool SDLWindow::DoInitialize() {
        SDL_PropertiesID props = SDL_CreateProperties();
        SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title_.c_str());
        SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, static_cast<int>(pos_.x));
        SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, static_cast<int>(pos_.y));
        SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, static_cast<int>(size_.w));
        SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, static_cast<int>(size_.h));
        SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, flags_);
        /* === [TODO] Properties to add in SDL_video === */
        SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_MINIMUM_WIDTH_NUMBER, static_cast<int>(minSize_.w));
        SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_MINIMUM_HEIGHT_NUMBER, static_cast<int>(minSize_.h));
        SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_MAXIMUM_WIDTH_NUMBER, static_cast<int>(maxSize_.w));
        SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_MAXIMUM_HEIGHT_NUMBER, static_cast<int>(maxSize_.h));
        SDL_SetFloatProperty(props, SDL_PROP_WINDOW_CREATE_MINIMUM_ASPECT_FLOAT, minAscpect_);
        SDL_SetFloatProperty(props, SDL_PROP_WINDOW_CREATE_MAXIMUM_ASPECT_FLOAT, maxAscpect_);
        window_ = SDL_CreateWindowWithProperties(props);
        SDL_DestroyProperties(props);
        if (!window_) {
            VG_LOG_CRITICAL << "Failed to create window: " << SDL_GetError();
            return false;
        }
        return true;
    }

Add to SDL_video.h

#define SDL_PROP_WINDOW_CREATE_MINIMUM_WIDTH_NUMBER                "SDL.window.create.minimum_width"
#define SDL_PROP_WINDOW_CREATE_MINIMUM_HEIGHT_NUMBER               "SDL.window.create.minimum_height"
#define SDL_PROP_WINDOW_CREATE_MAXIMUM_WIDTH_NUMBER                "SDL.window.create.maximum_width"
#define SDL_PROP_WINDOW_CREATE_MAXIMUM_HEIGHT_NUMBER               "SDL.window.create.maximum_height"
#define SDL_PROP_WINDOW_CREATE_MINIMUM_ASPECT_FLOAT                "SDL.window.create.minimum_aspect"
#define SDL_PROP_WINDOW_CREATE_MAXIMUM_ASPECT_FLOAT                "SDL.window.create.maximum_aspect"

Change to SDL_video.c

SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props)
{
    SDL_Window *window;
    const char *title = SDL_GetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, NULL);
    int x     = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_UNDEFINED);
    int y     = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_UNDEFINED);
    int w     = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 0);
    int h     = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 0);
    int min_w = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_MINIMUM_WIDTH_NUMBER, 0);
    int min_h = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_MINIMUM_HEIGHT_NUMBER, 0);
    int max_w = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_MAXIMUM_WIDTH_NUMBER, 0);
    int max_h = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_MAXIMUM_HEIGHT_NUMBER, 0);
    float min_aspect = (float)SDL_GetFloatProperty(props, SDL_PROP_WINDOW_CREATE_MINIMUM_ASPECT_FLOAT, 0.0f);
    float max_aspect = (float)SDL_GetFloatProperty(props, SDL_PROP_WINDOW_CREATE_MAXIMUM_ASPECT_FLOAT, 0.0f);
    
    SDL_Window *parent = (SDL_Window *)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, NULL);
    SDL_WindowFlags flags = SDL_GetWindowFlagProperties(props);
    SDL_WindowFlags type_flags, graphics_flags;
    SDL_DisplayID displayID = 0;
    bool undefined_x = false;
    bool undefined_y = false;
    bool external_graphics_context = SDL_GetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN, false);

    if (!_this) {
        // Initialize the video system if needed
        if (!SDL_Init(SDL_INIT_VIDEO)) {
            return NULL;
        }

        // Make clang-tidy happy
        if (!_this) {
            return NULL;
        }
    }

    if ((flags & SDL_WINDOW_MODAL) && !SDL_ObjectValid(parent, SDL_OBJECT_TYPE_WINDOW)) {
        SDL_SetError("Modal windows must specify a parent window");
        return NULL;
    }

    if ((flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU)) != 0) {
        if (!(_this->device_caps & VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT)) {
            SDL_Unsupported();
            return NULL;
        }

        // Tooltip and popup menu window must specify a parent window
        if (!SDL_ObjectValid(parent, SDL_OBJECT_TYPE_WINDOW)) {
            SDL_SetError("Tooltip and popup menu windows must specify a parent window");
            return NULL;
        }

        // Remove invalid flags
        flags &= ~(SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS);
    }

    // Ensure no more than one of these flags is set
    type_flags = flags & (SDL_WINDOW_UTILITY | SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU | SDL_WINDOW_MODAL);
    if (type_flags & (type_flags - 1)) {
        SDL_SetError("Conflicting window type flags specified: 0x%.8x", (unsigned int)type_flags);
        return NULL;
    }

    // Make sure the display list is up to date for window placement
    if (_this->RefreshDisplays) {
        _this->RefreshDisplays(_this);
    }

    // Some platforms can't create zero-sized windows
    if (w < 1) {
        w = 1;
    }
    if (h < 1) {
        h = 1;
    }

    if (SDL_WINDOWPOS_ISUNDEFINED(x) || SDL_WINDOWPOS_ISUNDEFINED(y) ||
        SDL_WINDOWPOS_ISCENTERED(x) || SDL_WINDOWPOS_ISCENTERED(y)) {
        SDL_Rect bounds;

        if ((SDL_WINDOWPOS_ISUNDEFINED(x) || SDL_WINDOWPOS_ISCENTERED(x)) && (x & 0xFFFF)) {
            displayID = (x & 0xFFFF);
        } else if ((SDL_WINDOWPOS_ISUNDEFINED(y) || SDL_WINDOWPOS_ISCENTERED(y)) && (y & 0xFFFF)) {
            displayID = (y & 0xFFFF);
        }
        if (displayID == 0 || SDL_GetDisplayIndex(displayID) < 0) {
            displayID = SDL_GetPrimaryDisplay();
        }

        SDL_zero(bounds);
        SDL_GetDisplayUsableBounds(displayID, &bounds);
        if (w > bounds.w || h > bounds.h) {
            // This window is larger than the usable bounds, just center on the display
            SDL_GetDisplayBounds(displayID, &bounds);
        }
        if (SDL_WINDOWPOS_ISCENTERED(x) || SDL_WINDOWPOS_ISUNDEFINED(x)) {
            if (SDL_WINDOWPOS_ISUNDEFINED(x)) {
                undefined_x = true;
            }
            x = bounds.x + (bounds.w - w) / 2;
        }
        if (SDL_WINDOWPOS_ISCENTERED(y) || SDL_WINDOWPOS_ISUNDEFINED(y)) {
            if (SDL_WINDOWPOS_ISUNDEFINED(y)) {
                undefined_y = true;
            }
            y = bounds.y + (bounds.h - h) / 2;
        }
    }

    // ensure no more than one of these flags is set
    graphics_flags = flags & (SDL_WINDOW_OPENGL | SDL_WINDOW_METAL | SDL_WINDOW_VULKAN);
    if (graphics_flags & (graphics_flags - 1)) {
        SDL_SetError("Conflicting window graphics flags specified: 0x%.8x", (unsigned int)graphics_flags);
        return NULL;
    }

    // Some platforms have certain graphics backends enabled by default
    if (!graphics_flags && !external_graphics_context) {
        flags |= SDL_DefaultGraphicsBackends(_this);
    }

    if (flags & SDL_WINDOW_OPENGL) {
        if (!_this->GL_CreateContext) {
            SDL_ContextNotSupported("OpenGL");
            return NULL;
        }
        if (!SDL_GL_LoadLibrary(NULL)) {
            return NULL;
        }
    }

    if (flags & SDL_WINDOW_VULKAN) {
        if (!_this->Vulkan_CreateSurface) {
            SDL_ContextNotSupported("Vulkan");
            return NULL;
        }
        if (!SDL_Vulkan_LoadLibrary(NULL)) {
            return NULL;
        }
    }

    if (flags & SDL_WINDOW_METAL) {
        if (!_this->Metal_CreateView) {
            SDL_ContextNotSupported("Metal");
            return NULL;
        }
    }

    window = (SDL_Window *)SDL_calloc(1, sizeof(*window));
    if (!window) {
        return NULL;
    }
    SDL_SetObjectValid(window, SDL_OBJECT_TYPE_WINDOW, true);
    window->id = SDL_GetNextObjectID();
    window->floating.x = window->windowed.x = window->x = x;
    window->floating.y = window->windowed.y = window->y = y;
    window->floating.w = window->windowed.w = window->w = w;
    window->floating.h = window->windowed.h = window->h = h;
    window->undefined_x = undefined_x;
    window->undefined_y = undefined_y;
    window->pending_displayID = displayID;

    SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
    if (display) {
        SDL_SetWindowHDRProperties(window, &display->HDR, false);
    }

    if (flags & SDL_WINDOW_FULLSCREEN || IsFullscreenOnly(_this)) {
        SDL_Rect bounds;

        SDL_GetDisplayBounds(display ? display->id : SDL_GetPrimaryDisplay(), &bounds);
        window->x = bounds.x;
        window->y = bounds.y;
        window->w = bounds.w;
        window->h = bounds.h;
        window->pending_flags |= SDL_WINDOW_FULLSCREEN;
        flags |= SDL_WINDOW_FULLSCREEN;
    }

    window->flags = ((flags & CREATE_FLAGS) | SDL_WINDOW_HIDDEN);
    window->display_scale = 1.0f;
    window->opacity = 1.0f;
    window->next = _this->windows;
    window->is_destroying = false;
    window->displayID = SDL_GetDisplayForWindow(window);
    window->external_graphics_context = external_graphics_context;
    window->constrain_popup = SDL_GetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN, true);

    if (!_this->SetWindowFillDocument) {
        window->flags &= ~SDL_WINDOW_FILL_DOCUMENT;  // not an error, just unsupported here, so remove the flag.
    }

    if (_this->windows) {
        _this->windows->prev = window;
    }
    _this->windows = window;

    // Set the parent before creation.
    SDL_UpdateWindowHierarchy(window, parent);

    if (_this->CreateSDLWindow && !_this->CreateSDLWindow(_this, window, props)) {
        PUSH_SDL_ERROR()
        SDL_DestroyWindow(window);
        POP_SDL_ERROR()
        return NULL;
    }

    /* Clear minimized if not on windows, only windows handles it at create rather than FinishWindowCreation,
     * but it's important or window focus will get broken on windows!
     */
#if !defined(SDL_PLATFORM_WINDOWS)
    if (window->flags & SDL_WINDOW_MINIMIZED) {
        window->flags &= ~SDL_WINDOW_MINIMIZED;
    }
#endif

    if (title) {
        SDL_SetWindowTitle(window, title);
    }
    if (min_w || min_h) {
        SDL_SetWindowMinimumSize(window, min_w, min_h);
    }
    if (max_w || max_h) {
        SDL_SetWindowMinimumSize(window, max_w, max_h);
    }
    if (min_aspect || max_aspect) {
        SDL_SetWindowAspectRatio(window, min_aspect, max_aspect);
    }
    SDL_FinishWindowCreation(window, flags);

    // Make sure window pixel size is up to date
    SDL_CheckWindowPixelSizeChanged(window);

#ifdef SDL_VIDEO_DRIVER_UIKIT
    SDL_UpdateLifecycleObserver();
#endif

    SDL_ClearError();

    return window;
}

When creating the UI, I chose to use SDL_gfx to display shapes such as circles and rectangles with rounded edges. However, the functions provided are a bit annoying to use because they only support 32-bit colors, whereas I often use the SDL_FColor structure, and for displaying shapes, they don’t accept floating-point coordinates and sizes. So I rewrote all the functionality in a custom class. I think it would be cool to integrate them directly into the SDL_Renderer functions, such as SDL_RenderRoundedRect, SDL_RenderRoundedFillRect, SDL_RenderCircle, SDL_RenderFillCircle, SDL_RenderElipse, SDL_RenderFillElipse, SDL_RenderFillPolygon, SDL_RenderArc, SDL_RenderBezierPolygon etc.

I think it would also be interesting to add functions for SDL_FColor, such as SDL_SetColorFloat, which imposes a clamp between 0.0f and 1.0f, SDL_BrightnessColorFloat, SDL_SetContrastColorFloat, etc.

#include "Utils.hpp"
#include "Vec4.hpp"

namespace VoxelGame {

	struct Color : public Vec4 {
		// Constructeurs par défaut hérités
		constexpr Color() : Vec4(0.0f, 0.0f, 0.0f, 1.0f) {} // Noir opaque par défaut
		constexpr Color(const Vec4& v) : Vec4(v) {}
		constexpr Color(float _r, float _g, float _b, float _a = 1.0f) : Vec4(_r, _g, _b, _a) {}

		// Constructeur depuis des entiers 0-255
		Color(uint8_t _r, uint8_t _g, uint8_t _b, uint8_t _a = 255) 
			: Vec4(_r / 255.0f, _g / 255.0f, _b / 255.0f, _a / 255.0f) {}

		// Constructeur depuis Hexadécimal (ex: 0xFF0000FF pour rouge opaque)
		// Format attendu: RRGGBBAA
		Color(uint32_t hex) {
			r = ((hex >> 24) & 0xFF) / 255.0f;
			g = ((hex >> 16) & 0xFF) / 255.0f;
			b = ((hex >> 8)  & 0xFF) / 255.0f;
			a = ((hex)       & 0xFF) / 255.0f;
		}

		// --- Conversion ---
		
		// Convertit en entier 32 bits (RRGGBBAA)
		uint32_t ToUInt32() const {
			uint8_t _r = static_cast<uint8_t>(std::clamp(r * 255.0f, 0.0f, 255.0f));
			uint8_t _g = static_cast<uint8_t>(std::clamp(g * 255.0f, 0.0f, 255.0f));
			uint8_t _b = static_cast<uint8_t>(std::clamp(b * 255.0f, 0.0f, 255.0f));
			uint8_t _a = static_cast<uint8_t>(std::clamp(a * 255.0f, 0.0f, 255.0f));
			return (_r << 24) | (_g << 16) | (_b << 8) | _a;
		}

		// Assombrit ou éclaircit (factor > 1 éclaircit, < 1 assombrit)
		Color ScaleRGB(float factor) const {
			return Color(r * factor, g * factor, b * factor, a);
		}

		// Correction Gamma
		Color ToLinear(float gamma = 2.2f) const {
			return Color(std::pow(r, gamma), std::pow(g, gamma), std::pow(b, gamma), a);
		}

		Color ToSRGB(float gamma = 2.2f) const {
			float invGamma = 1.0f / gamma;
			return Color(std::pow(r, invGamma), std::pow(g, invGamma), std::pow(b, invGamma), a);
		}

		// --- Static Presets ---
		
	};
	

	static const Color TRANSPARENT = Color(0x00000000);
	static const Color ALICE_BLUE = Color(0xF0F8FFFF);
	static const Color AMBER_GREEN = Color(0x9A803AFF);
	static const Color AMBER_SUN = Color(0xFF9988FF);
	static const Color ANTIQUE_WHITE = Color(0xFAEBD7FF);
	static const Color AQUA_MARINE = Color(0xF7FFD4FF);
	static const Color AZURE = Color(0xF0FFFFFF);
	static const Color BEIGE = Color(0xF5F5DCFF);
	static const Color BISQUE = Color(0xFFE4C4FF);
	static const Color BLACK = Color(0x000000FF);
	static const Color BLUE = Color(0x0000FFFF);
	static const Color BLUE_VIOLET = Color(0x502AE2FF);
	static const Color BROWN = Color(0xA52A2AFF);
	static const Color BURLY_WOOD = Color(0xDEB887FF);
	static const Color CADET_BLUE = Color(0x5F9EA0FF);
	static const Color CHARTREUSE = Color(0x7FFF00FF);
	static const Color CHOCOLATE = Color(0xD2691EFF);
	static const Color CORAL = Color(0xFF7F50FF);
	static const Color CORN_FLOWER_BLUE = Color(0x6495EDFF);
	static const Color CORN_SILK = Color(0xFFF8DCFF);
	static const Color CRIMSON = Color(0xDC1450FF);
	static const Color CYAN = Color(0x00FFFFFF);
	static const Color DARK_BLUE = Color(0x00008BFF);
	static const Color DARK_CYAN = Color(0x008B8BFF);
	static const Color DARK_GOLDEN_ROD = Color(0xB8860BFF);
	static const Color DARK_GREEN = Color(0x006400FF);
	static const Color DARK_KAKI = Color(0xADA75BFF);
	static const Color DARK_MAGENTA = Color(0x8B008BFF);
	static const Color DARK_OLiVER_GREEN = Color(0x455B1FFF);
	static const Color DARK_ORANGE = Color(0xE77400FF);
	static const Color DARK_ORCHILD = Color(0x9932CCFF);
	static const Color DARK_PINK = Color(0xC78D9BFF);
	static const Color DARK_PURPLE = Color(0x610094FF);
	static const Color DARK_RED = Color(0x8B0000FF);
	static const Color DARK_SALMON = Color(0xE9967AFF);
	static const Color DARK_SEA_GREEN = Color(0x8FBC8FFF);
	static const Color DARK_SLATE_BLUE = Color(0x483D8BFF);
	static const Color DARK_STEEL_BLUE = Color(0x152736FF);
	static const Color DODGER_BLUE1 = Color(0x0060CFFF);
	static const Color DODGER_BLUE2 = Color(0x1E90FFFF);
	static const Color DODGER_BLUE3 = Color(0x46B8FFFF);
	static const Color FIRE_BRICK = Color(0xB22222FF);
	static const Color FLORAL_WHITE = Color(0xFFFAF0FF);
	static const Color FOREST_GREEN = Color(0x228B22FF);
	static const Color GOLD = Color(0xFFD700FF);
	static const Color GOLD_ROD = Color(0xDAA520FF);
	inline Color GRAY(float s) { return Color(s, s, s, 1.0f); }
	static const Color GREEN = Color(0x00FF00FF);
	static const Color GREEN_YELLOW = Color(0xADFF2FFF);
	static const Color HOT_PINK = Color(0xFF69B4FF);
	static const Color INDIGO = Color(0x4B00B2FF);
	static const Color IVORY = Color(0xFFFFF0FF);
	static const Color KHAKI = Color(0xF0E68CFF);
	static const Color LARCHMERE = Color(0x70BAA7FF);
	static const Color LAVENDER = Color(0xE6E6FAFF);
	static const Color LAWN_GREEN = Color(0x7CFC00FF);
	static const Color LIGHT_BLUE = Color(0xADD8E6FF);
	static const Color LIGHT_CORAL = Color(0xF08080FF);
	static const Color LIGHT_CYAN = Color(0xE0FFFFFF);
	static const Color LIGHT_GREEN = Color(0x90EE90FF);
	static const Color LIGHT_ORANGE = Color(0xFFB410FF);
	static const Color LIGHT_PINK1 = Color(0xFFB6C1FF);
	static const Color LIGHT_PINK2 = Color(0xFFA9DCFF);
	static const Color LIGHT_PINK3 = Color(0xFFBFFFFF);
	static const Color LIGHT_SALMON = Color(0xFFA07AFF);
	static const Color LIGHT_STEEL_BLUE = Color(0x1682B4FF);
	static const Color LIGHT_YELLOW = Color(0xFFD9A6FF);
	static const Color LIME_GREEN = Color(0x32CD32FF);
	static const Color MAGENTA = Color(0xFF00FFFF);
	static const Color MAROON = Color(0x800000FF);
	static const Color MEDIUM_AQUA_MARINE = Color(0x66CDAAFF);
	static const Color MEDIUM_GREY1 = Color(0x1C1C1CFF);
	static const Color MEDIUM_SLATE_BLUE = Color(0x7B68EEFF);
	static const Color MEDIUM_STEEL_BLUE = Color(0x386890FF);
	static const Color ORANGE = Color(0xFF6400FF);
	static const Color ORANGE_JUICE = Color(0xFFA500FF);
	static const Color ORANGE_RED = Color(0xFF4500FF);
	static const Color PERU = Color(0xCD853FFF);
	static const Color PINK = Color(0xFF81CAFF);
	static const Color PURPLE = Color(0x9900CCFF);
	static const Color PURPLE2 = Color(0x66306CFF);
	static const Color RED = Color(0xFF0000FF);
	static const Color SIENNA = Color(0xA0522DFF);
	static const Color SKY_BLUE = Color(0x87CEEBFF);
	static const Color STEEL_BLUE = Color(0x315B7EFF);
	static const Color SLATE_BLUE = Color(0x4682B4FF);
	static const Color SLATE_GREY = Color(0x7A8A7CFF);
	static const Color TURQUOISE = Color(0x40E0D0FF);
	static const Color VIOLET = Color(0xEE82EEFF);
	static const Color WEAT = Color(0xF5DEB3FF);
	static const Color WHITE = Color(0xFFFFFFFF);
	static const Color WHITE_SMOKE = Color(0xF5F5F5FF);
	static const Color YELLOW = Color(0xFFFF00FF);
	static const Color YELLOW_GREEN = Color(0x9ACD32FF);


    class SDLRenderer : public Initializer {
    public:
        SDLRenderer(std::shared_ptr<SDLWindow> window, const char* name = nullptr)
            : window_(window), name_(name) {}

        const char*   GetName() const override { return "SDL_Renderer"; }
        SDL_Renderer* GetSDLRenderer() const { return renderer_; }
        SDL_Window*   GetSDLWindow() const { return window_->GetSDLWindow(); }

        /* ---- RENDERING ---- */
        SDLRenderer &Clear();
        SDLRenderer &Present();

        SDLRenderer &SetScale(float scaleX, float scaleY);
        SDLRenderer &SetClipRect(const Rect &rect);

        /* ---- COLOR ---- */
        SDLRenderer &SetDrawColor(float r, float g, float b, float a = 1.0f);
        SDLRenderer &SetDrawColor(const Color &color);
        Color GetDrawColor() const;
        void SetLastColor();

        /* ---- GEOMETRY ---- */
        SDLRenderer &DrawPoint(const Vec2 &point);

        SDLRenderer &DrawLine(const Vec2 &p1, const Vec2 &p2);

        SDLRenderer &DrawArc(const Vec2 &point, float rad, float start, float end);

        SDLRenderer &DrawRect(const Rect &rect);
        SDLRenderer &DrawFillRect(const Rect &rect);

        SDLRenderer &DrawRoundedRect(const Rect &rect, float rad);
        SDLRenderer &DrawRoundedFillRect(const Rect &rect, float rad);

        SDLRenderer &DrawCircle(const Vec2 &center, float radius);
        SDLRenderer &DrawFillCircle(const Vec2 &center, float radius);

        SDLRenderer &DrawElipse(const Vec2 &center, float radiusX, float radiusY);
        SDLRenderer &DrawFillElipse(const Vec2 &center, float radiusX, float radiusY);

        SDLRenderer &DrawPolygon(const std::vector<Vec2> points);
        SDLRenderer &DrawFillPolygon(const std::vector<Vec2> points);

        SDLRenderer &DrawBezierPolygon(const std::vector<Vec2> points, const std::vector<float> radius);
        SDLRenderer &DrawBezierFillPolygon(const std::vector<Vec2> points, const std::vector<float> radius);

        ...

        /* ---- TEXT & CURSOR ---- */
        SDLRenderer &SetCursorPosition(float x, float y);
        SDLRenderer &SetCursorPosition(const Vec2 &pos);
        Point GetCursorPosition() const;

        SDLRenderer &SetAutoReturnCursor(bool enable);
        bool GetAutoReturnCursorEnable() const;

        SDLRenderer &Print(std::string_view str);

        template< class... Args >
        SDLRenderer &Print(std::string_view fmt, Args&&... args) {
            std::string str = std::vformat(fmt, std::make_format_args(args...));
            Print(str);
        }

    protected:
        bool DoInitialize() override;

        void DoShutdown() override;

    private:
        std::shared_ptr<SDLWindow> window_;
        const char* name_;
        SDL_Renderer* renderer_ = nullptr;

        Vec2 cursor_           = {0,0};
        float fontSize_        = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE;
        bool cursorAutoReturn_ = true;

        Color lastDrawColor_ = TRANSPARENT;
    };

} // VoxelGame

One last suggestion, I think it would be interesting to add a feature to automatically manage archives with easy-to-use functions such as:

/* Supported archive types (extensible via libarchive or miniz backend) */
typedef enum SDL_ArchiveType {
    SDL_ARCHIVE_UNKNOWN = 0,
    SDL_ARCHIVE_ZIP,
    SDL_ARCHIVE_RAR,
    SDL_ARCHIVE_7Z,
    SDL_ARCHIVE_TAR,
    SDL_ARCHIVE_GZIP
} SDL_ArchiveType;

/* Metadata of a file in the archive */
typedef struct SDL_ArchiveEntry {
    const char *filename;       // Full path in the archive
    Uint64 size;                // Uncompressed size
    Uint64 compressed_size;     // Compressed size
    Uint64 mod_time;            // Modification timestamp
    SDL_bool is_directory;      // Is this a directory?
    SDL_bool is_encrypted;      // Requires a password ?
} SDL_ArchiveEntry;

/* Opaque structure (handle) for the open archive */
typedef struct SDL_Archive SDL_Archive;

/* Open from a file */
extern DECLSPEC SDL_Archive * SDLCALL SDL_OpenArchive(const char *path);

/* Check if archive is locked with passworld */
extern DECLSPEC bool SDLCALL SDL_IsArchiveLocked(SDL_Archive* archive);

/* Unlock archive is locked with passworld */
extern DECLSPEC SDL_Archive * SDLCALL SDL_UnlockArchive(SDL_Archive* archive, const char* passworld);

/* Lock archive with old or new passworld */
extern DECLSPEC int SDLCALL SDL_LockPassword(SDL_Archive *archive, const char *password);

/* Open from an existing stream (e.g., archive loaded into RAM) */
/* ‘autoclose’ indicates whether the archive should close the source stream at the end */
extern DECLSPEC SDL_Archive * SDLCALL SDL_OpenArchiveIO(SDL_IOStream *src, SDL_bool autoclose);

/* Close and release */
extern DECLSPEC void SDLCALL SDL_CloseArchive(SDL_Archive *archive);

/* Get the number of entries */
extern DECLSPEC Uint64 SDLCALL SDL_GetArchiveEntryCount(SDL_Archive *archive);

/* Get information about an entry by index (iteration) */
extern DECLSPEC const SDL_ArchiveEntry * SDLCALL SDL_GetArchiveEntryIndex(SDL_Archive *archive, Uint64 index);

/* Find an entry by name (e.g., “assets/textures/player.png”) */
extern DECLSPEC const SDL_ArchiveEntry * SDLCALL SDL_GetArchiveEntry(SDL_Archive *archive, const char *path);

/* Opens a file IN the archive as a standard SDL stream */
extern DECLSPEC SDL_IOStream * SDLCALL SDL_OpenArchiveEntryIO(SDL_Archive *archive, const char *filename);

/* Loads all the contents of a file into memory (allocates the buffer) */
extern DECLSPEC void * SDLCALL SDL_LoadFileFromArchive(SDL_Archive *archive, const char *filename, size_t *datasize);

extern DECLSPEC int SDLCALL SDL_ArchiveAddFile(SDL_Archive *archive, const char *filename_in_zip, const char *local_path);
extern DECLSPEC int SDLCALL SDL_ArchiveAddMemory(SDL_Archive *archive, const char *filename_in_zip, const void *buffer, size_t len);

Thank you for reading my suggestion. I hope you found it useful.