Add a function for reading a sprite's translation. Add functions for reading a text object's foreground, background, and font. Log a warning when the draw function of a sprite containing textures is called but the currently active texture will not be drawn because it has not been generated yet. Line-length linting.
226 lines
6.1 KiB
C++
226 lines
6.1 KiB
C++
/* +=======================================================+
|
|
____/ \____ /: Open source game framework licensed to freely use, :
|
|
\ / / : copy, and modify - created for dank.game :
|
|
+==\ ^__^ /==+ : :
|
|
: ~/ \~ : : Download at https://open.shampoo.ooo/shampoo/spacebox :
|
|
: ~~~~~~~~~~~~ : +=======================================================+
|
|
: SPACE ~~~~~ : /
|
|
: ~~~~~~~ BOX :/
|
|
+=============*/
|
|
|
|
#include "Text.hpp"
|
|
|
|
using namespace sb;
|
|
|
|
void Text::bind_textures() const
|
|
{
|
|
try
|
|
{
|
|
Model::bind_textures();
|
|
}
|
|
catch (const std::runtime_error& error)
|
|
{
|
|
std::ostringstream message;
|
|
message << "Error binding textures for " << *this << ": " << error.what();
|
|
throw std::runtime_error(message.str());
|
|
}
|
|
}
|
|
|
|
void Text::content(const std::string& content)
|
|
{
|
|
_content = content;
|
|
}
|
|
|
|
void Text::content(char character)
|
|
{
|
|
_content = "";
|
|
_content += character;
|
|
}
|
|
|
|
const std::string& Text::content() const
|
|
{
|
|
return _content;
|
|
}
|
|
|
|
void Text::foreground(const Color& foreground)
|
|
{
|
|
_foreground = foreground;
|
|
}
|
|
|
|
const sb::Color& Text::foreground() const
|
|
{
|
|
return _foreground;
|
|
}
|
|
|
|
void Text::background(const Color& background)
|
|
{
|
|
_background = background;
|
|
}
|
|
|
|
const sb::Color& Text::background() const
|
|
{
|
|
return _background;
|
|
}
|
|
|
|
void Text::font(std::shared_ptr<TTF_Font> font)
|
|
{
|
|
_font = font;
|
|
}
|
|
|
|
const std::shared_ptr<TTF_Font> Text::font() const
|
|
{
|
|
return _font;
|
|
}
|
|
|
|
void Text::dimensions(const glm::ivec2& dimensions)
|
|
{
|
|
_dimensions = dimensions;
|
|
}
|
|
|
|
glm::ivec2 Text::dimensions() const
|
|
{
|
|
if (_dimensions.has_value())
|
|
{
|
|
return *_dimensions;
|
|
}
|
|
else
|
|
{
|
|
int width;
|
|
int height;
|
|
if (TTF_SizeUTF8(_font.get(), _content.c_str(), &width, &height) == 0)
|
|
{
|
|
return {width, height};
|
|
}
|
|
else
|
|
{
|
|
std::ostringstream message;
|
|
message << "Error checking dimensions of text content \"" << _content << "\":";
|
|
sb::Log::sdl_error(message.str());
|
|
return {0, 0};
|
|
}
|
|
}
|
|
}
|
|
|
|
void Text::scaling_quality(GLint quality)
|
|
{
|
|
_scaling_quality = quality;
|
|
}
|
|
|
|
void Text::wrap(int width)
|
|
{
|
|
_wrap = width;
|
|
}
|
|
|
|
void Text::offset(const glm::vec2& amount)
|
|
{
|
|
_offset = amount;
|
|
}
|
|
|
|
void Text::refresh()
|
|
{
|
|
/* Render the text with transparent background as RGBA pixel data using the SDL image library. Apply wrap if it is set. */
|
|
std::shared_ptr<SDL_Surface> blended;
|
|
if (_wrap.has_value())
|
|
{
|
|
blended = std::shared_ptr<SDL_Surface>{
|
|
TTF_RenderUTF8_Blended_Wrapped(
|
|
_font.get(), _content.c_str(), _foreground, _wrap.value()), SDL_FreeSurface};
|
|
}
|
|
else
|
|
{
|
|
blended = std::shared_ptr<SDL_Surface>{
|
|
TTF_RenderUTF8_Blended(_font.get(), _content.c_str(), _foreground), SDL_FreeSurface};
|
|
}
|
|
|
|
/* Composite the rendered text onto a background surface. */
|
|
if (!blended)
|
|
{
|
|
Log::sdl_error("Could not create text");
|
|
}
|
|
else
|
|
{
|
|
/* Set the text surface blend mode to no blending if the background if fully transparent, or to blend if there
|
|
* is a visible background surface. If it fails, print an error and continue anyway.
|
|
*
|
|
* A better algorithm may be to do BLEND_MAX, but that will need to be custom built using
|
|
* SDL_ComposeCustomBlendMode.
|
|
*/
|
|
SDL_BlendMode blend;
|
|
if (_background.normal().a <= 0.0f)
|
|
{
|
|
blend = SDL_BLENDMODE_NONE;
|
|
}
|
|
else
|
|
{
|
|
blend = SDL_BLENDMODE_BLEND;
|
|
}
|
|
if (SDL_SetSurfaceBlendMode(blended.get(), blend) != 0)
|
|
{
|
|
Log::sdl_error("Could not set blend mode on text surface");
|
|
}
|
|
|
|
/* Use the size of the rendered text unless dimensions has been set. */
|
|
glm::ivec2 dimensions;
|
|
if (_dimensions.has_value())
|
|
{
|
|
dimensions = _dimensions.value();
|
|
}
|
|
else
|
|
{
|
|
dimensions = glm::ivec2 {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, dimensions.x, dimensions.y, blended->format->BitsPerPixel, blended->format->format),
|
|
SDL_FreeSurface
|
|
};
|
|
|
|
if (!container)
|
|
{
|
|
Log::sdl_error("Could not create container surface for rendered text");
|
|
}
|
|
else
|
|
{
|
|
SDL_FillRect(container.get(), nullptr, _background);
|
|
Box blended_box {0.0f, 0.0f, float(blended->w), float(blended->h), false};
|
|
Box container_box {0.0f, 0.0f, float(container->w), float(container->h), false};
|
|
blended_box.center(container_box.center());
|
|
blended_box.move(_offset);
|
|
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)
|
|
{
|
|
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. The texture object is optimized so that it won't reallocate pixel memory
|
|
* if it was previously generated at the same size. */
|
|
texture(0).generate({flipped->w, flipped->h}, GL_RGBA8, _scaling_quality);
|
|
texture(0).load(flipped.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
Text::operator std::string() const
|
|
{
|
|
std::ostringstream message;
|
|
message << "<sb::Text \"" << _content << "\">";
|
|
return message.str();
|
|
}
|
|
|
|
std::ostream& sb::operator<<(std::ostream& out, const Text& text)
|
|
{
|
|
out << std::string(text);
|
|
return out;
|
|
}
|