Added WidgetText. Fixed some things. Updated README

This commit is contained in:
nedko 2025-12-04 17:23:53 +02:00
parent 58abb91c64
commit 56e07e1080
8 changed files with 473 additions and 8 deletions

View File

@ -2,6 +2,7 @@ src_files += main.cpp
src_files += sdl_helpers.cpp
src_files += Widgets/Widget.cpp
src_files += Widgets/WidgetText.cpp
all:
g++ $(src_files) -Wall -lSDL2 -lSDL2_ttf -lSDL2_image -o trmnl_sdl

View File

@ -3,17 +3,17 @@ A utility which can be used to create custom images for TRMNL devices via code
# How to write new visual stuff
1. Inherit from `Widget` and then do your magic inside your own class.
2. Add your widget builder in `main.cpp` inside `init builders`.
3. Write your config file.
2. Add your widget builder in `main.cpp` inside `init_builders`.
3. Write your config file. Either named `config.json` or added as the first command line parameter.
4. ???
5. Profit
# ImageMagick commands
### Convert image without dithering
`magick input.png -monochrome -colors 2 -depth 1 -strip png:output.png`
`convert trmnl.png -monochrome -colors 2 -depth 1 -strip png:output.png`
### Convert image with dithering
`magick input.png -dither FloydSteinberg -remap pattern:gray50 -depth 1 -strip png:output.png`
`convert trmnl.png -dither FloydSteinberg -remap pattern:gray50 -depth 1 -strip png:output.png`
# Notes
json.hpp from nlohmann/json v3.11.2

View File

@ -5,7 +5,7 @@
class Widget
{
private:
protected:
// Internal surface for drawing
SDL_Surface* m_surface;

223
Widgets/WidgetText.cpp Normal file
View File

@ -0,0 +1,223 @@
#include "WidgetText.h"
using std::string;
static SDL_Surface* render_text(bool should_wrap, TTF_Font* font, const string& text, SDL_Color color, Uint32 wrap_size)
{
SDL_Surface* txt_surface = nullptr;
if (should_wrap)
{
txt_surface = TTF_RenderUTF8_Solid_Wrapped(font, text.c_str(), color, wrap_size);
}
else
{
txt_surface = TTF_RenderUTF8_Solid(font, text.c_str(), color);
}
return txt_surface;
}
WidgetText::WidgetText(int width, int height, string text,
TextFit fit, bool should_wrap,
HorizontalAlign halign, VerticalAlign valign,
SDL_Color text_color,
int size, std::string font)
: Widget(width, height),
m_text(text),
m_font_file(font),
m_size(size),
m_fit(fit),
m_should_wrap(should_wrap),
m_halign(halign),
m_valign(valign),
m_text_color(text_color)
{
if (nullptr != m_surface)
{
SDL_SetSurfaceBlendMode(m_surface, SDL_BLENDMODE_BLEND);
}
}
WidgetText::WidgetText(int width, int height, string text,
TextFit fit, bool should_wrap,
int size, string font)
: Widget(width, height),
m_text(text),
m_font_file(font),
m_size(size),
m_fit(fit),
m_should_wrap(should_wrap),
m_halign(HALIGN_CENTER),
m_valign(VALIGN_CENTER),
m_text_color{.r = 0, .g = 0, .b = 0, .a = SDL_ALPHA_OPAQUE}
{
if (nullptr != m_surface)
{
SDL_SetSurfaceBlendMode(m_surface, SDL_BLENDMODE_BLEND);
}
}
WidgetText::WidgetText(int width, int height, string text,
int size, string font)
: Widget(width, height),
m_text(text),
m_font_file(font),
m_size(size),
m_fit(FIT_NONE),
m_halign(HALIGN_CENTER),
m_valign(VALIGN_CENTER),
m_text_color{.r = 0, .g = 0, .b = 0, .a = SDL_ALPHA_OPAQUE}
{
if (nullptr != m_surface)
{
SDL_SetSurfaceBlendMode(m_surface, SDL_BLENDMODE_BLEND);
}
}
void WidgetText::set_text(const string& text)
{
m_text = text;
}
void WidgetText::set_font(const string& font_file)
{
m_font_file = font_file;
}
void WidgetText::set_font_size(int size)
{
m_size = size;
}
void WidgetText::set_fit(TextFit fit)
{
m_fit = fit;
}
void WidgetText::set_halign(HorizontalAlign halign)
{
m_halign = halign;
}
void WidgetText::set_valign(VerticalAlign valign)
{
m_valign = valign;
}
void WidgetText::set_color(SDL_Color text_color)
{
m_text_color = text_color;
}
void WidgetText::draw()
{
// Clear surface
SDL_FillRect(m_surface, nullptr, SDL_MapRGBA(m_surface->format, 255, 255, 255, SDL_ALPHA_TRANSPARENT));
string font_name = m_font_file;
if (font_name.empty())
{
font_name = default_font_name;
}
TTF_Font* font = get_font(m_size, font_name);
if (nullptr == font)
{
// TODO: Message printing
return;
}
// Render text
SDL_Surface* txt_surface = render_text(m_should_wrap, font, m_text, m_text_color, m_rect.w);
if (nullptr == txt_surface)
{
// TODO: Message printing
return;
}
// Check if text fits
if (m_fit != FIT_NONE)
{
double x_scale;
double y_scale;
x_scale = m_surface->w;
x_scale /= txt_surface->w;
y_scale = m_surface->h;
y_scale /= txt_surface->h;
// Find the scale needed to shrink or enlarge with
double min_scale = x_scale;
if (y_scale < min_scale)
{
min_scale = y_scale;
}
// Do not scale up if only shrinkage is allowed
if ((min_scale > 1.0) && (FIT_SHRINK == m_fit))
{
min_scale = 1.0;
}
// Find new font size
int new_text_size = m_size;
new_text_size *= min_scale;
// Re-render text
if (new_text_size != m_size)
{
SDL_FreeSurface(txt_surface);
font = get_font(new_text_size, font_name);
if (nullptr == font)
{
// TODO: Message printing
return;
}
txt_surface = render_text(m_should_wrap, font, m_text, m_text_color, m_rect.w);
if (nullptr == txt_surface)
{
// TODO: Message printing
return;
}
}
}
// Now we have the final rendered text surface
SDL_Rect align = surface_align(m_surface, txt_surface, m_halign, m_valign);
SDL_BlitSurface(txt_surface, NULL, m_surface, &align);
SDL_FreeSurface(txt_surface);
}
Widget* WidgetText::builder(const nlohmann::json& j)
{
int width = 0;
int height = 0;
string text = "";
TextFit fit = FIT_NONE;
bool should_wrap = false;
HorizontalAlign halign = HALIGN_CENTER;
VerticalAlign valign = VALIGN_CENTER;
SDL_Color text_color = {.r = 0, .g = 0, .b = 0, .a = SDL_ALPHA_OPAQUE};
int size = 0;
string font = "";
json_extract(j, "width", width);
json_extract(j, "height", height);
json_extract(j, "text", text);
json_extract(j, "fit", fit);
json_extract(j, "should_wrap", should_wrap);
json_extract(j, "halign", halign);
json_extract(j, "valign", valign);
json_extract(j, "color", text_color);
json_extract(j, "size", size);
json_extract(j, "font", font);
if ((0 == width) || (0 == height) || (0 == size))
{
return nullptr;
}
return new WidgetText(width, height, text, fit, should_wrap, halign, valign, text_color, size, font);
}

71
Widgets/WidgetText.h Normal file
View File

@ -0,0 +1,71 @@
#ifndef WIDGET_TEXT_H_
#define WIDGET_TEXT_H_
#include "Widget.h"
#include <SDL2/SDL.h>
#include <string>
#include "../sdl_helpers.h"
#include "../json.hpp"
class WidgetText : public Widget
{
protected:
// Text to display
std::string m_text;
// Desired font file
// Leave empty for default
std::string m_font_file;
// Desired font size
int m_size;
// Whether to fit text inside rectangle
// FIT_SHRINK will try with the desired font size and shrink if needed
// FIT_AUTO will with the desired font size and shrink or enlarge if needed
// Default - FIT_NONE
TextFit m_fit;
// Whether to wrap to multiple lines
// Default - false
bool m_should_wrap;
// Default - center
HorizontalAlign m_halign;
// Default - center
VerticalAlign m_valign;
// Default - black
SDL_Color m_text_color;
public:
WidgetText(int width, int height, std::string text,
TextFit fit, bool should_wrap,
HorizontalAlign halign, VerticalAlign valign,
SDL_Color text_color,
int size, std::string font = "");
WidgetText(int width, int height, std::string text,
TextFit fit, bool should_wrap,
int size, std::string font = "");
WidgetText(int width, int height, std::string text,
int size, std::string font = "");
void set_text(const std::string& text);
void set_font(const std::string& font_file);
void set_font_size(int size);
void set_fit(TextFit fit);
void set_halign(HorizontalAlign halign);
void set_valign(VerticalAlign valign);
void set_color(SDL_Color text_color);
virtual void draw() override;
static Widget* builder(const nlohmann::json& j);
};
#endif // WIDGET_TEXT_H_

View File

@ -11,6 +11,7 @@
#include "sdl_helpers.h"
#include "Widgets/Widget.h"
#include "Widgets/WidgetText.h"
using std::cout;
using std::endl;
@ -21,7 +22,7 @@ using nlohmann::json;
void init_builders(map<string, Widget*(*)(const json&)>& widget_builders)
{
// widget_builders["name"] = &WidgetName::builder;
widget_builders["text"] = &WidgetText::builder;
}
int main(int argc, char **argv)
@ -107,7 +108,7 @@ int main(int argc, char **argv)
}
// Clear screen with white
SDL_FillRect(main_surface, nullptr, SDL_MapRGBA(main_surface->format, 255, 255, 255, 255));
SDL_FillRect(main_surface, nullptr, SDL_MapRGBA(main_surface->format, 255, 255, 255, SDL_ALPHA_OPAQUE));
// Draw and apply all widgets
for (Widget* widget : widgets)

View File

@ -11,6 +11,7 @@ using namespace std;
using nlohmann::json;
map<pair<string, int>, TTF_Font*> font_map;
string default_font_name = "font.ttf";
bool init_sdl()
{
@ -75,6 +76,49 @@ TTF_Font* get_font(int size, const string& filename)
}
}
SDL_Rect surface_align(const SDL_Surface* base, const SDL_Surface* applied,
HorizontalAlign halign, VerticalAlign valign)
{
SDL_Rect align = {.x = 0, .y = 0, .w = 0, .h = 0};
if ((nullptr == base) || (nullptr == applied))
{
return align;
}
switch (halign)
{
case HALIGN_LEFT:
align.x = 0;
break;
case HALIGN_CENTER:
align.x = base->w / 2 - applied->w / 2;
break;
case HALIGN_RIGHT:
align.x = base->w - applied->w;
break;
}
switch (valign)
{
case VALIGN_TOP:
align.y = 0;
break;
case VALIGN_CENTER:
align.y = base->h / 2 - applied->h / 2;
break;
case VALIGN_BOTTOM:
align.y = base->h - applied->h;
break;
}
return align;
}
bool read_config_json(json& cfg, const string& filename, ostream* log)
{
ifstream cfg_file(filename);
@ -120,3 +164,74 @@ void json_extract(const nlohmann::json& j, const string& key, int& out)
out = j[key];
}
}
void json_extract(const nlohmann::json& j, const string& key, bool& out)
{
if (j.contains(key) && j[key].is_boolean())
{
out = j[key];
}
}
void json_extract(const json& j, const string& key, HorizontalAlign& out)
{
if (j.contains(key))
{
try
{
out = j[key].get<HorizontalAlign>();
}
catch(const std::exception& e)
{
}
}
}
void json_extract(const json& j, const string& key, VerticalAlign& out)
{
if (j.contains(key))
{
try
{
out = j[key].get<VerticalAlign>();
}
catch(const std::exception& e)
{
}
}
}
void json_extract(const json& j, const string& key, TextFit& out)
{
if (j.contains(key))
{
try
{
out = j[key].get<TextFit>();
}
catch(const std::exception& e)
{
}
}
}
void json_extract(const json& j, const string& key, SDL_Color& out)
{
if (j.contains(key) && j[key].is_object())
{
const json& j2 = j[key];
json_extract(j2, "r", out.r);
json_extract(j2, "g", out.g);
json_extract(j2, "b", out.b);
json_extract(j2, "a", out.a);
}
}
void json_extract(const nlohmann::json& j, const string& key, Uint8& out)
{
if (j.contains(key) && j[key].is_number_unsigned())
{
out = j[key];
}
}

View File

@ -8,7 +8,49 @@
#include "json.hpp"
std::string default_font_name = "font.ttf";
extern std::string default_font_name;
enum HorizontalAlign
{
HALIGN_LEFT,
HALIGN_CENTER,
HALIGN_RIGHT
};
NLOHMANN_JSON_SERIALIZE_ENUM(HorizontalAlign,
{
{HALIGN_LEFT, "left"},
{HALIGN_CENTER, "center"},
{HALIGN_RIGHT, "right"},
})
enum VerticalAlign
{
VALIGN_TOP,
VALIGN_CENTER,
VALIGN_BOTTOM
};
NLOHMANN_JSON_SERIALIZE_ENUM(VerticalAlign,
{
{VALIGN_TOP, "top"},
{VALIGN_CENTER, "center"},
{VALIGN_BOTTOM, "bottom"},
})
enum TextFit
{
FIT_NONE,
FIT_SHRINK,
FIT_AUTO
};
NLOHMANN_JSON_SERIALIZE_ENUM(TextFit,
{
{FIT_NONE, "none"},
{FIT_SHRINK, "shrink"},
{FIT_AUTO, "auto"},
})
// Call this before everything
// Prints its messages
@ -21,6 +63,12 @@ void clean_sdl();
// Can return NULL
TTF_Font* get_font(int size, const std::string& filename = default_font_name);
// Returns a rect to use during bliting of 2 surfaces
// base - surface on which the other is applied to
// applied - the surface which will be applied to the other
SDL_Rect surface_align(const SDL_Surface* base, const SDL_Surface* applied,
HorizontalAlign halign, VerticalAlign valign);
// Reads the file and tries to parse a JSON file with comments
// cfg - output json struct
// filename - filepath to open and read
@ -31,5 +79,11 @@ bool read_config_json(nlohmann::json& cfg, const std::string& filename, std::ost
void json_extract(const nlohmann::json& j, const std::string& key, std::string& out);
void json_extract(const nlohmann::json& j, const std::string& key, int& out);
void json_extract(const nlohmann::json& j, const std::string& key, bool& out);
void json_extract(const nlohmann::json& j, const std::string& key, HorizontalAlign& out);
void json_extract(const nlohmann::json& j, const std::string& key, VerticalAlign& out);
void json_extract(const nlohmann::json& j, const std::string& key, TextFit& out);
void json_extract(const nlohmann::json& j, const std::string& key, SDL_Color& out);
void json_extract(const nlohmann::json& j, const std::string& key, Uint8& out);
#endif // SDL_HELPERS_H_