convert between glm::vec and sb::Color, print hierarchy on config access error, composite text onto a separate background surface, set frame time to zero when timer paused

This commit is contained in:
ohsqueezy 2023-07-08 16:19:31 -04:00
parent 6cb9688bec
commit 55d6e08480
9 changed files with 177 additions and 51 deletions

View File

@ -9,7 +9,7 @@ void Animation::play(float delay, bool play_once)
{
this->delay = delay;
play_state = true;
paused = false;
unpause();
previous_step_time = timer.elapsed();
overflow = 0;
ending = play_once;

View File

@ -154,7 +154,7 @@ public:
bool collide(const glm::vec2& point) const;
/*!
* Collide a line segment with the box. If intersection is passed and there is a collision, intersection will be filled with
* Collide a line segment with the box. If intrseection is passed and there is a collision, intersection will be filled with
* the coordinates of the first intersection found unless the segment is fully within the box not touching any edges.
*
* @param segment line segment to collide with the box

View File

@ -16,6 +16,7 @@
#include <ostream>
#include <type_traits>
#include "SDL_pixels.h"
#include "glm/glm.hpp"
struct Color : SDL_Color
{
@ -24,6 +25,24 @@ public:
Color();
Color(const SDL_Color&);
template<glm::length_t dimensions, typename Type, glm::qualifier qualifier>
Color(glm::vec<dimensions, Type, qualifier> vector)
{
if constexpr (dimensions == 2)
{
*this = Color(vector.x, vector.y, 0.0f);
}
else if constexpr (dimensions == 3)
{
*this = Color(vector.x, vector.y, vector.z);
}
else if constexpr (dimensions == 4)
{
*this = Color(vector.x, vector.y, vector.z, vector.w);
}
}
void percent(float, float, float);
void percent(float, float, float, float);
void hsv(float, float = 1.0f, float = 1.0f);
@ -52,6 +71,26 @@ public:
a = static_cast<int>(alpha) & 255;
}
/*!
* Convert sb::Color to glm::vec of 3 or 4 dimensions. If vector is 3-dimensional, return only the RGB portion
* of the color.
*/
template<glm::length_t dimensions, typename Type, glm::qualifier qualifier>
operator glm::vec<dimensions, Type, qualifier>() const
{
if constexpr (dimensions == 3)
{
return glm::vec3 {r, g, b};
}
else if constexpr (dimensions == 4)
{
return glm::vec4 {r, g, b, a};
}
else
{
throw std::runtime_error("Cannot convert color the glm::vec of dimensions other than 3 or 4");
}
}
};
void RGBtoHSV(const float&, const float&, const float&, float&, float&, float&);

View File

@ -42,20 +42,34 @@ private:
* @warning This probably should not be called directly and is just used to provide a recursive call for variadic
* access through Configuration::operator()
*
* @param json a JSON element in the configuration
* @param key first-level key
* @param keys zero or more next level keys
* @param hierarchy string that appends the key as a string to the end as the function runs recursively to represent the hierarchy
* in case of an error
* @param json a JSON element in the configuration
* @param key first-level key
* @param keys zero or more next level keys
*/
template<class Key, class... Keys>
const nlohmann::json& access(const nlohmann::json& json, const Key& key, const Keys&... keys) const
const nlohmann::json& access(std::ostringstream& hierarchy, const nlohmann::json& json, const Key& key, const Keys&... keys) const
{
hierarchy << '"' << key << '"';
if constexpr (sizeof...(keys) > 0)
{
return access(json[key], keys...);
hierarchy << " > ";
return access(hierarchy, json[key], keys...);
}
else
{
return json[key];
hierarchy << ")";
try
{
return json.at(key);
}
catch (const std::exception& exception)
{
std::ostringstream message;
message << "Error accessing configuration at " << hierarchy.str() << ": " << exception.what();
throw std::runtime_error(message.str());
}
}
}
@ -95,9 +109,9 @@ public:
const nlohmann::json& operator()() const;
/*!
* Get a read-only reference to the value at the given hierarchy of keys. If no type is specified for the return type, a
* JSON object will be returned.
*
* Get a read-only reference to the JSON object at the given hierarchy of keys. The key must exist in the configuration, otherwise an exception
* will be thrown. To get a reference to JSON object for a new key, use sb::Configuration::operator[](const std::string) instead.
*
* Example,
*
* std::cout << _configuration() << std::endl;
@ -109,13 +123,15 @@ public:
* true {"any-key-ignore-commands":[],"default-unsuppress-delay":0.7,"ignore-repeat-keypress":true,"suppress-any-key-on-mods":true, \
* "system-any-key-ignore-commands":["fullscreen","screenshot","record","quit"]} 261.0
*
* @param keys hierarchy of keys used to look up a specific value in the config JSON
* @return read-only reference to value specified by keys
* @param keys hierarchy of keys used to look up a specific JSON object in the config
* @return read-only reference to JSON object specified by keys
*/
template<typename... Key>
const nlohmann::json& operator()(const Key&... keys) const
{
return access(config, keys...);
std::ostringstream hierarchy;
hierarchy << "(";
return access(hierarchy, config, keys...);
}
/*!

View File

@ -75,6 +75,14 @@ public:
};
/* Add Input class to the sb namespace. This should be the default location, but Input is left in the global namespace
* for backward compatibility.
*/
namespace sb
{
using ::Input;
}
#include "extension.hpp"
#include "Configuration.hpp"
#include "Delegate.hpp"

View File

@ -2,74 +2,98 @@
using namespace sb;
/*!
* @param content text to be displayed
*/
void Text::content(const std::string& content)
{
_content = content;
refresh();
}
/*!
* @param foreground text color
*/
void Text::foreground(const sb::Color& foreground)
{
_foreground = foreground;
refresh();
}
/*!
* @param background text background color which fills the Plane
*/
void Text::background(const sb::Color& background)
{
_background = background;
refresh();
}
/*!
* @param font shared pointer to a TTF_Font
*/
void Text::font(std::shared_ptr<TTF_Font> font)
{
_font = font;
refresh();
}
void Text::size(const glm::vec2 &size)
{
_size = size;
refresh();
}
void Text::scaling_quality(GLint quality)
{
_scaling_quality = quality;
refresh();
}
void Text::refresh()
{
/* Create pixel data using the SDL image library. The pixel data surface is converted from paletted format to ARGB and flipped. */
SDL_Surface* shaded = TTF_RenderText_Shaded(_font.get(), _content.c_str(), _foreground, _background);
if (!shaded)
/* Render the text with transparent background as RGBA pixel data using the SDL image library. */
std::shared_ptr<SDL_Surface> blended {TTF_RenderText_Blended(_font.get(), _content.c_str(), _foreground), SDL_FreeSurface};
if (!blended)
{
sb::Log::sdl_error("Could not create text");
}
else
{
SDL_Surface* converted = converted = SDL_ConvertSurfaceFormat(shaded, SDL_PIXELFORMAT_ARGB8888, 0);
if (!converted)
/* Use the size of the rendered text unless size has been set. */
glm::vec2 size;
if (_size.has_value())
{
sb::Log::sdl_error("Could not convert pixel format");
size = _size.value();
}
else
{
SDL_Surface* flipped = rotozoomSurfaceXY(converted, 0, 1, -1, 0);
if (!flipped)
{
sb::Log::sdl_error("Could not flip surface");
}
else
{
/* Generate texture and create storage. Load the pixels from the text rendering surface into the default texture. The texture object will handle
* destroying the previous texture. Then destroy the pixels surface. */
texture().generate({flipped->w, flipped->h}, GL_RGBA8, GL_LINEAR);
texture().load(flipped);
SDL_FreeSurface(flipped);
}
SDL_FreeSurface(converted);
size = glm::vec2{blended->w, blended->h};
}
/* Create a container surface with the same format as the rendered text that the rendered text will be composited onto. */
std::shared_ptr<SDL_Surface> container
{
SDL_CreateRGBSurfaceWithFormat(0, size.x, size.y, blended->format->BitsPerPixel, blended->format->format),
SDL_FreeSurface
};
if (!container)
{
sb::Log::sdl_error("Could not create container surface for rendered text");
}
else
{
SDL_FillRect(container.get(), nullptr, _background);
sb::Box blended_box {0.0f, 0.0f, float(blended->w), float(blended->h), false};
sb::Box container_box {0.0f, 0.0f, float(container->w), float(container->h), false};
blended_box.center(container_box.center());
SDL_Rect r = blended_box;
SDL_BlitSurface(blended.get(), nullptr, container.get(), &r);
blended = container;
}
/* Rotate and mirror the surface for compatibility with OpenGL */
std::shared_ptr<SDL_Surface> flipped {rotozoomSurfaceXY(blended.get(), 0, 1, -1, 0), SDL_FreeSurface};
if (!flipped)
{
sb::Log::sdl_error("Could not flip surface");
}
else
{
/* Generate texture and create storage. Load pixels from the text rendering surface into the texture. The texture object will handle
* destroying the previous texture. */
texture().generate({flipped->w, flipped->h}, GL_RGBA8, _scaling_quality);
texture().load(flipped.get());
}
SDL_FreeSurface(shaded);
}
}

View File

@ -1,3 +1,5 @@
#include <optional>
#include "SDL.h"
#include "SDL_ttf.h"
#include "sdl2-gfx/SDL2_rotozoom.h"
@ -18,6 +20,8 @@ namespace sb
std::string _content;
sb::Color _foreground, _background;
std::shared_ptr<TTF_Font> _font;
std::optional<glm::vec2> _size;
GLint _scaling_quality = GL_LINEAR;
/*!
* Load the texture with the appropriate SDL_Surface pixels created by the SDL TTF library.
@ -27,17 +31,24 @@ namespace sb
public:
/*!
* Construct a sb::Text object from a font, string, and background and foreground colors.
* Construct a sb::Text object from a font, string, and background and foreground colors. A texture with the text rendered in the
* given colors will be created and attached to this object.
*
* The font must be wrapped in a shared pointer. A default loaded font is available from Game::font().
*
* A size can be given in pixels, in which case the text will be rendered at the center of a texture in exactly the given dimensions,
* regardless of the length of the text. If the given dimensions are larger than the text, the resulting texture will have padding around
* the text. This can be useful, for example, for creating identically sized text buttons.
*
* @param font a TTF_Font object wrapped in a shared pointer
* @param content text content
* @param foreground text color
* @param background background color
* @param size force the texture to be a certain size in pixels, extra area is filled with the background color
*/
Text(std::shared_ptr<TTF_Font> font, const std::string& content = "", const sb::Color& foreground = DEFAULT_FG,
const sb::Color& background = DEFAULT_BG) : _content(content), _foreground(foreground), _background(background), _font(font)
const sb::Color& background = DEFAULT_BG, std::optional<glm::vec2> size = std::nullopt) :
_content(content), _foreground(foreground), _background(background), _font(font), _size(size)
{
/* Add an empty Texture object */
texture(sb::Texture());
@ -62,6 +73,18 @@ namespace sb
* @param font shared pointer to a TTF_Font
*/
void font(std::shared_ptr<TTF_Font> font);
/*!
* @param size force the texture to be a certain size in pixels, extra area is filled with the background color
*/
void size(const glm::vec2& size);
/*!
* This GL parameter will be passed to `glTexParameter` for `GL_TEXTURE_MIN_FILTER` and `GL_TEXTURE_MAG_FILTER`.
*
* @param quality quality of texture scaling passed to `glTexParameter`
*/
void scaling_quality(GLint quality);
};
}

View File

@ -5,6 +5,11 @@ sb::Timer::operator bool() const
return timing;
}
sb::Timer::operator float() const
{
return _elapsed;
}
void sb::Timer::toggle()
{
toggle(!*this);
@ -50,11 +55,15 @@ void sb::Timer::update(float stamp)
_stamp = stamp;
if (previous_is_recorded)
{
frame_duration = stamp - stamp_previous;
if (*this)
{
frame_duration = stamp - stamp_previous;
_elapsed += frame();
}
else
{
frame_duration = 0.0f;
}
}
stamp_previous = stamp;
previous_is_recorded = true;

View File

@ -10,6 +10,9 @@
#pragma once
#include <iostream>
#include <string>
namespace sb
{
/*!
@ -38,6 +41,11 @@ namespace sb
*/
operator bool() const;
/*!
* Convert a sb::Timer to a float by returning the elapsed time.
*/
operator float() const;
/*!
* Toggle timing on/off
*/
@ -68,7 +76,6 @@ namespace sb
*/
float elapsed() const;
/*!
* @return length of the previous frame in seconds
*/