Compare commits

..

13 Commits

14 changed files with 575 additions and 46 deletions

9
.gitignore vendored
View File

@@ -1,4 +1,13 @@
# Exclude the executable
trmnl_sdl trmnl_sdl
# Exclude configuration files
*.json *.json
# Exclude images
*.bmp
*.jpg
*.png *.png
# Exclude fonts
*.ttf *.ttf

View File

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

121
README.md
View File

@@ -1,19 +1,124 @@
# What is this # What is this
A utility which can be used to create custom images for TRMNL devices via code A utility which can be used to create custom images for TRMNL devices via code
# How to write new visual stuff # How to build
1. Inherit from `Widget` and then do your magic inside your own class. **NOTE: Tested on Ubuntu 24.04.**
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 You will need the following packages: `build-essential` `libsdl2-dev` `libsdl2-image-dev` `libsdl2-ttf-dev` `imagemagick`
Just run `make` and an executable called `trmnl_sdl` should be produced.
# How to use
1. You will need to write a JSON config file.
It can either be named `config.json` or you will need to pass it as the first command line parameter to the executable.
An example structure of the config file can be found in `config_example.json`.
You can find more info on the available Widgets and their parameters at the end of this file.
2. Run the executable
3. Convert the image to a suitable format via an ImageMagick command from below.
# ImageMagick commands to prepare image for TRMNL device
### Convert image without dithering ### Convert image without dithering
`convert trmnl.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 ### Convert image with dithering
`convert trmnl.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`
### Resize image to fit with dithering
`convert image.png -resize 800x480 -background white -compose Copy -gravity center -extent 800x480 -dither FloydSteinberg -remap pattern:gray50 -depth 1 -strip png:output.png`
# 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. Rebuild the executable.
# Notes # Notes
json.hpp from nlohmann/json v3.11.2 json.hpp from [nlohmann/json](https://github.com/nlohmann/json) v3.11.2
# List of special types
### color
* **Object** type
* Components - Red, Green, Blue, Alpha
* Each component ranges 0-255
* When Alpha is 255 - the color is fully opaque. When it is 0 - fully transparent.
* Has the structure shown below:
```
"color": {
"r": 0,
"g": 0,
"b": 0,
"a": 255
}
```
**NOTE**: If only some components are present in the config then only the present ones are overwritten from the default.
### HAlign
Controls horizontal alignment of elements. **String** type. Has several options:
* left - Align objects to the left
* center - Align objects to be centered
* right - Align objects to the right
### VAlign
Controls vertical alignment of elements. **String** type. Has several options:
* top - Align objects to the top
* center - Align objects to be centered
* bottom - Align objects to the bottom
### ImageResize
Controls image resizing. **String** type. Has several options:
* none - Image is not resized and is only clipped by the bounding box
* fit - Image is uniformly scaled to fit inside the box
* stretch - Image is scaled (with possible stretching) to fully fill the box
### TextFit
Controls the automatic change of the text size depending on contents. **String** type. Has several options:
* none - Text is not changed in any way
* shrink - Renders text with desired size and shrinks it to fit if contents are too large
* auto - Renders text with desired size and enlarges/shrinks it to fit if contents are too small/large
# List of Widgets and their parameters
### image
Renders an image with optional scaling
| Name | Type | Required | Default | Description |
| ---: | :--: | :------: | ------: | :---------- |
| x | int | | 0 | Horizontal position in pixels (from left to right) |
| y | int | | 0 | Vertical position in pixels (from top to bottom) |
| width | int | REQ | | Width in pixels |
| height | int | REQ | | Height in pixels |
| filename | string | | | Filename of the image to render |
| resize_type | ImageResize | | fit | Whether to resize the image and how to do so |
| bg_color | color | | 255, 255, 255, 0 (Transparent WHITE) | The background color to fill around the image if it is not fully in the box |
| halign | HAlign | | center | Horizontal alignment of the image |
| valign | VAlign | | center | Vertical alignment of the image |
Supported image formats: **JPG**, **PNG**, **BMP**
### rect
Renders a rectangle with optional rounded corners using either fill or internal stroke.
| Name | Type | Required | Default | Description |
| ---: | :--: | :------: | ------: | :---------- |
| x | int | | 0 | Horizontal position in pixels (from left to right) |
| y | int | | 0 | Vertical position in pixels (from top to bottom) |
| width | int | REQ | | Width in pixels |
| height | int | REQ | | Height in pixels |
| radius | int | | 0 | Corner rounding radius in pixels |
| stroke | int | | -1 | Internal stroke size in pixels. If <= 0 then the rectangle will be filled with the desired color |
| color | color | | 0, 0, 0, 255 (BLACK) | Color to use for rectangle |
### text
Renders text within a specified box
| Name | Type | Required | Default | Description |
| ---: | :--: | :------: | ------: | :---------- |
| x | int | | 0 | Horizontal position in pixels (from left to right) |
| y | int | | 0 | Vertical position in pixels (from top to bottom) |
| width | int | REQ | | Max width in pixels |
| height | int | REQ | | Max height in pixels |
| text | string | | | The text to be shown |
| size | int | REQ | | Desired size of the text to use |
| font | string | | Global font key | Font file to use when rendering the text |
| color | color | | 0, 0, 0, 255 (BLACK) | Color to use for text rendering |
| should_wrap | boolean | | false | Whether the text should automatically wrap |
| fit | TextFit | | none | Controls automatic change the text size depending on contents |
| halign | HAlign | | center | Horizontal alignment of text |
| valign | VAlign | | center | Vertical alignment of text |
| halign_via_visible | boolean | | true | Whether to use visible pixels of the text for horizontal alignment. If false - uses text renderer hints |
| valign_via_visible | boolean | | true | Whether to use visible pixels of the text for vertical alignment. If false - uses text renderer hints |

View File

@@ -3,7 +3,9 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "../json.hpp" #include "../json.hpp"
#include <memory>
// Base Widget class for the whole Widget system
class Widget class Widget
{ {
protected: protected:

165
Widgets/WidgetImage.cpp Normal file
View File

@@ -0,0 +1,165 @@
#include "WidgetImage.h"
#include <SDL2/SDL_image.h>
using std::string;
WidgetImage::WidgetImage(int x, int y, int width, int height, string filename,
ImageResize resize_type, HorizontalAlign halign, VerticalAlign valign,
SDL_Color bg_color)
: Widget(x, y, width, height),
m_filename(filename),
m_image_surface(nullptr),
m_resize_type(resize_type),
m_halign(halign),
m_valign(valign),
m_bg_color(bg_color)
{
m_image_surface = IMG_Load(m_filename.c_str());
if (nullptr == m_image_surface)
{
// TODO: Print errors
}
}
void WidgetImage::set_filename(const string& filename)
{
if (m_image_surface != nullptr)
{
SDL_FreeSurface(m_image_surface);
m_image_surface = nullptr;
}
m_filename = filename;
m_image_surface = IMG_Load(m_filename.c_str());
if (nullptr == m_image_surface)
{
// TODO: Print errors
}
}
void WidgetImage::set_resize(ImageResize type)
{
m_resize_type = type;
}
void WidgetImage::set_halign(HorizontalAlign halign)
{
m_halign = halign;
}
void WidgetImage::set_valign(VerticalAlign valign)
{
m_valign = valign;
}
void WidgetImage::set_bg_color(SDL_Color bg_color)
{
m_bg_color = bg_color;
}
void WidgetImage::draw()
{
if (nullptr == m_surface)
{
return;
}
// Clear surface with BG color
SDL_FillRect(m_surface, nullptr,
SDL_MapRGBA(m_surface->format, m_bg_color.r, m_bg_color.g, m_bg_color.b, m_bg_color.a));
if (nullptr == m_image_surface)
{
return;
}
SDL_Rect align;
SDL_Surface* scaled_image = nullptr;
switch (m_resize_type)
{
case RESIZE_NONE:
align = surface_align(m_surface, m_image_surface, m_halign, m_valign);
SDL_BlitSurface(m_image_surface, nullptr, m_surface, &align);
break;
case RESIZE_FIT:
scaled_image = image_scale_fit();
if (nullptr != scaled_image)
{
align = surface_align(m_surface, scaled_image, m_halign, m_valign);
SDL_BlitScaled(scaled_image, nullptr, m_surface, &align);
SDL_FreeSurface(scaled_image);
}
break;
case RESIZE_STRETCH:
SDL_BlitScaled(m_image_surface, nullptr, m_surface, nullptr);
break;
}
}
std::unique_ptr<Widget> WidgetImage::builder(const nlohmann::json& j)
{
int x = 0;
int y = 0;
int width = 0;
int height = 0;
string filename;
ImageResize resize_type = RESIZE_FIT;
HorizontalAlign halign = HALIGN_CENTER;
VerticalAlign valign = VALIGN_CENTER;
SDL_Color bg_color = {.r = 255, .g = 255, .b = 255, .a = SDL_ALPHA_TRANSPARENT};
json_extract(j, "x", x);
json_extract(j, "y", y);
json_extract(j, "width", width);
json_extract(j, "height", height);
json_extract(j, "filename", filename);
json_extract(j, "resize_type", resize_type);
json_extract(j, "halign", halign);
json_extract(j, "valign", valign);
json_extract(j, "bg_color", bg_color);
return std::make_unique<WidgetImage>(x, y, width, height, filename,
resize_type, halign, valign, bg_color);
}
SDL_Surface* WidgetImage::image_scale_fit()
{
SDL_Rect align = {.x = 0, .y = 0, .w = 0, .h = 0};
if ((nullptr == m_surface) || (nullptr == m_image_surface))
{
return nullptr;
}
double x_scale = m_surface->w;
x_scale /= m_image_surface->w;
double y_scale = m_surface->h;
y_scale /= m_image_surface->h;
// Find smallest scale factor
double min_scale = x_scale;
if (y_scale < min_scale)
{
min_scale = y_scale;
}
// Scale with double in the front and then clamp to int via SDL_Rect
align.w = min_scale * m_image_surface->w;
align.h = min_scale * m_image_surface->h;
SDL_Surface* scaled_image_surface =
SDL_CreateRGBSurfaceWithFormat(0, align.w, align.h, 32, SDL_PIXELFORMAT_RGBA8888);
if (nullptr == scaled_image_surface)
{
// TODO: Print Error
return nullptr;
}
SDL_BlitScaled(m_image_surface, nullptr, scaled_image_surface, nullptr);
return scaled_image_surface;
}

59
Widgets/WidgetImage.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef WIDGET_IMAGE_H_
#define WIDGET_IMAGE_H_
#include "Widget.h"
#include <string>
#include "../sdl_helpers.h"
// Renders an image with optional scaling
class WidgetImage : public Widget
{
protected:
// Image filepath
std::string m_filename;
// Image surface
SDL_Surface* m_image_surface;
// Whether to resize the image and how
// RESIZE_NONE - Do not resize
// RESIZE_FIT - Scale the image to fit the rectangle
// RESIZE_STRETCH - Scale and stretch the image to fit the box perfectly
ImageResize m_resize_type;
// Default - center
HorizontalAlign m_halign;
// Default - center
VerticalAlign m_valign;
// Background color to be used
// Default - transparent white
SDL_Color m_bg_color;
public:
WidgetImage(int x, int y, int width, int height, std::string filename,
ImageResize resize_type = RESIZE_FIT,
HorizontalAlign halign = HALIGN_CENTER,
VerticalAlign valign = VALIGN_CENTER,
SDL_Color bg_color = SDL_Color{.r = 255, .g = 255, .b = 255, .a = SDL_ALPHA_TRANSPARENT});
void set_filename(const std::string& filename);
void set_resize(ImageResize type);
void set_halign(HorizontalAlign halign);
void set_valign(VerticalAlign valign);
void set_bg_color(SDL_Color bg_color);
virtual void draw() override;
static std::unique_ptr<Widget> builder(const nlohmann::json& j);
protected:
// Create a new surface of the image_surface that is scaled via RESIZE_FIT method
// NOTE: The user MUST free the surface when done with it
SDL_Surface* image_scale_fit();
};
#endif // WIDGET_IMAGE_H_

View File

@@ -24,8 +24,28 @@ m_radius(radius)
{ {
} }
void WidgetRect::set_color(SDL_Color color)
{
m_color = color;
}
void WidgetRect::set_stroke_size(int stroke_size)
{
m_stroke_size = stroke_size
}
void WidgetRect::set_radius(int radius)
{
m_radius = radius;
}
void WidgetRect::draw() void WidgetRect::draw()
{ {
if (nullptr == m_surface)
{
return;
}
// Clear surface // Clear surface
SDL_FillRect(m_surface, nullptr, SDL_MapRGBA(m_surface->format, 255, 255, 255, SDL_ALPHA_TRANSPARENT)); SDL_FillRect(m_surface, nullptr, SDL_MapRGBA(m_surface->format, 255, 255, 255, SDL_ALPHA_TRANSPARENT));
@@ -97,7 +117,7 @@ void WidgetRect::draw()
} }
} }
Widget* WidgetRect::builder(const nlohmann::json& j) std::unique_ptr<Widget> WidgetRect::builder(const nlohmann::json& j)
{ {
int x = 0; int x = 0;
int y = 0; int y = 0;
@@ -115,7 +135,7 @@ Widget* WidgetRect::builder(const nlohmann::json& j)
json_extract(j, "stroke", stroke_size); json_extract(j, "stroke", stroke_size);
json_extract(j, "color", color); json_extract(j, "color", color);
return new WidgetRect(x, y, width, height, radius, stroke_size, color); return std::make_unique<WidgetRect>(x, y, width, height, radius, stroke_size, color);
} }
void WidgetRect::draw_circle_corner(int x, int y, int quadrant) void WidgetRect::draw_circle_corner(int x, int y, int quadrant)

View File

@@ -3,6 +3,7 @@
#include "Widget.h" #include "Widget.h"
// Renders a rectangle with optional rounded corners using either fill or internal stroke
class WidgetRect : public Widget class WidgetRect : public Widget
{ {
protected: protected:
@@ -26,11 +27,15 @@ public:
WidgetRect(int x, int y, int width, int height, WidgetRect(int x, int y, int width, int height,
int radius = 0, int stroke_size = -1); int radius = 0, int stroke_size = -1);
void set_color(SDL_Color color);
void set_stroke_size(int stroke_size);
void set_radius(int radius);
virtual void draw() override; virtual void draw() override;
static Widget* builder(const nlohmann::json& j); static std::unique_ptr<Widget> builder(const nlohmann::json& j);
private: protected:
// x, y - center of circle // x, y - center of circle
// quadrant - 1--4 // quadrant - 1--4
void draw_circle_corner(int x, int y, int quadrant); void draw_circle_corner(int x, int y, int quadrant);

View File

@@ -15,9 +15,76 @@ static SDL_Surface* render_text(bool should_wrap, TTF_Font* font, const string&
return txt_surface; return txt_surface;
} }
static SDL_Rect find_visible_area(const SDL_Surface* txt_surface)
{
int x_start = -1;
int x_end = -1;
int y_start = -1;
int y_end = -1;
for (int y = 0; y < txt_surface->h; ++y)
{
bool found = false;
for (int x = 0; x < txt_surface->w; ++x)
{
if (1 == (static_cast<Uint8*>(txt_surface->pixels)[y * txt_surface->pitch + x]))
{
if (-1 == x_start)
{
x_start = x;
}
else
{
if (x < x_start)
{
x_start = x;
}
}
if (-1 == x_end)
{
x_end = x;
}
else
{
if (x > x_end)
{
x_end = x;
}
}
found = true;
}
}
if (found)
{
if (-1 == y_start)
{
y_start = y;
}
y_end = y;
}
}
SDL_Rect result = {.x = 0, .y = 0, .w = 0, .h = 0};
if ((-1 != x_start) && (-1 != x_end))
{
result.x = x_start;
result.w = x_end - x_start + 1;
}
if ((-1 != y_start) && (-1 != y_end))
{
result.y = y_start;
result.h = y_end - y_start + 1;
}
return result;
}
WidgetText::WidgetText(int x, int y, int width, int height, string text, WidgetText::WidgetText(int x, int y, int width, int height, string text,
TextFit fit, bool should_wrap, TextFit fit, bool should_wrap,
HorizontalAlign halign, VerticalAlign valign, HorizontalAlign halign, VerticalAlign valign,
bool halign_via_visible, bool valign_via_visible,
SDL_Color text_color, SDL_Color text_color,
int size, std::string font) int size, std::string font)
: Widget(x, y, width, height), : Widget(x, y, width, height),
@@ -28,7 +95,9 @@ m_fit(fit),
m_should_wrap(should_wrap), m_should_wrap(should_wrap),
m_halign(halign), m_halign(halign),
m_valign(valign), m_valign(valign),
m_text_color(text_color) m_text_color(text_color),
m_halign_via_visible(halign_via_visible),
m_valign_via_visible(valign_via_visible)
{ {
} }
@@ -43,7 +112,9 @@ m_fit(fit),
m_should_wrap(should_wrap), m_should_wrap(should_wrap),
m_halign(HALIGN_CENTER), m_halign(HALIGN_CENTER),
m_valign(VALIGN_CENTER), m_valign(VALIGN_CENTER),
m_text_color{.r = 0, .g = 0, .b = 0, .a = SDL_ALPHA_OPAQUE} m_text_color{.r = 0, .g = 0, .b = 0, .a = SDL_ALPHA_OPAQUE},
m_halign_via_visible(true),
m_valign_via_visible(true)
{ {
} }
@@ -56,7 +127,9 @@ m_size(size),
m_fit(FIT_NONE), m_fit(FIT_NONE),
m_halign(HALIGN_CENTER), m_halign(HALIGN_CENTER),
m_valign(VALIGN_CENTER), m_valign(VALIGN_CENTER),
m_text_color{.r = 0, .g = 0, .b = 0, .a = SDL_ALPHA_OPAQUE} m_text_color{.r = 0, .g = 0, .b = 0, .a = SDL_ALPHA_OPAQUE},
m_halign_via_visible(true),
m_valign_via_visible(true)
{ {
} }
@@ -95,8 +168,22 @@ void WidgetText::set_color(SDL_Color text_color)
m_text_color = text_color; m_text_color = text_color;
} }
void WidgetText::set_halign_via_visible(bool value)
{
m_halign_via_visible = value;
}
void WidgetText::set_valign_via_visible(bool value)
{
m_valign_via_visible = value;
}
void WidgetText::draw() void WidgetText::draw()
{ {
if (nullptr == m_surface)
{
return;
}
// Clear surface // Clear surface
SDL_FillRect(m_surface, nullptr, SDL_MapRGBA(m_surface->format, 255, 255, 255, SDL_ALPHA_TRANSPARENT)); SDL_FillRect(m_surface, nullptr, SDL_MapRGBA(m_surface->format, 255, 255, 255, SDL_ALPHA_TRANSPARENT));
@@ -171,12 +258,25 @@ void WidgetText::draw()
} }
// Now we have the final rendered text surface // Now we have the final rendered text surface
SDL_Rect align = surface_align(m_surface, txt_surface, m_halign, m_valign); SDL_Rect hint = find_visible_area(txt_surface);
if (!m_halign_via_visible)
{
hint.x = 0;
hint.w = txt_surface->w;
}
if (!m_valign_via_visible)
{
hint.y = 0;
hint.h = txt_surface->h;
}
SDL_Rect align = surface_align(m_surface, txt_surface, m_halign, m_valign, &hint);
SDL_BlitSurface(txt_surface, NULL, m_surface, &align); SDL_BlitSurface(txt_surface, NULL, m_surface, &align);
SDL_FreeSurface(txt_surface); SDL_FreeSurface(txt_surface);
} }
Widget* WidgetText::builder(const nlohmann::json& j) std::unique_ptr<Widget> WidgetText::builder(const nlohmann::json& j)
{ {
int x = 0; int x = 0;
int y = 0; int y = 0;
@@ -187,6 +287,8 @@ Widget* WidgetText::builder(const nlohmann::json& j)
bool should_wrap = false; bool should_wrap = false;
HorizontalAlign halign = HALIGN_CENTER; HorizontalAlign halign = HALIGN_CENTER;
VerticalAlign valign = VALIGN_CENTER; VerticalAlign valign = VALIGN_CENTER;
bool halign_via_visible = true;
bool valign_via_visible = true;
SDL_Color text_color = {.r = 0, .g = 0, .b = 0, .a = SDL_ALPHA_OPAQUE}; SDL_Color text_color = {.r = 0, .g = 0, .b = 0, .a = SDL_ALPHA_OPAQUE};
int size = 0; int size = 0;
string font = ""; string font = "";
@@ -200,6 +302,8 @@ Widget* WidgetText::builder(const nlohmann::json& j)
json_extract(j, "should_wrap", should_wrap); json_extract(j, "should_wrap", should_wrap);
json_extract(j, "halign", halign); json_extract(j, "halign", halign);
json_extract(j, "valign", valign); json_extract(j, "valign", valign);
json_extract(j, "halign_via_visible", halign_via_visible);
json_extract(j, "valign_via_visible", valign_via_visible);
json_extract(j, "color", text_color); json_extract(j, "color", text_color);
json_extract(j, "size", size); json_extract(j, "size", size);
json_extract(j, "font", font); json_extract(j, "font", font);
@@ -209,5 +313,9 @@ Widget* WidgetText::builder(const nlohmann::json& j)
return nullptr; return nullptr;
} }
return new WidgetText(x, y, width, height, text, fit, should_wrap, halign, valign, text_color, size, font); return std::make_unique<WidgetText>(x, y, width, height,
text, fit, should_wrap,
halign, valign,
halign_via_visible, valign_via_visible,
text_color, size, font);
} }

View File

@@ -2,12 +2,12 @@
#define WIDGET_TEXT_H_ #define WIDGET_TEXT_H_
#include "Widget.h" #include "Widget.h"
#include <SDL2/SDL.h>
#include <string> #include <string>
#include "../sdl_helpers.h" #include "../sdl_helpers.h"
// Renders text within a specified box
class WidgetText : public Widget class WidgetText : public Widget
{ {
protected: protected:
@@ -40,10 +40,19 @@ protected:
// Default - black // Default - black
SDL_Color m_text_color; SDL_Color m_text_color;
// Whether to H-align via visible pixels or rendering surface
// Default - true
bool m_halign_via_visible;
// Whether to V-align via visible pixels or rendering surface
// Default - true
bool m_valign_via_visible;
public: public:
WidgetText(int x, int y, int width, int height, std::string text, WidgetText(int x, int y, int width, int height, std::string text,
TextFit fit, bool should_wrap, TextFit fit, bool should_wrap,
HorizontalAlign halign, VerticalAlign valign, HorizontalAlign halign, VerticalAlign valign,
bool halign_via_visible, bool valign_via_visible,
SDL_Color text_color, SDL_Color text_color,
int size, std::string font = ""); int size, std::string font = "");
@@ -61,10 +70,12 @@ public:
void set_halign(HorizontalAlign halign); void set_halign(HorizontalAlign halign);
void set_valign(VerticalAlign valign); void set_valign(VerticalAlign valign);
void set_color(SDL_Color text_color); void set_color(SDL_Color text_color);
void set_halign_via_visible(bool value);
void set_valign_via_visible(bool value);
virtual void draw() override; virtual void draw() override;
static Widget* builder(const nlohmann::json& j); static std::unique_ptr<Widget> builder(const nlohmann::json& j);
}; };
#endif // WIDGET_TEXT_H_ #endif // WIDGET_TEXT_H_

View File

@@ -1,6 +1,6 @@
{ {
"width": 800, "width": 800,
"height": 600, "height": 480,
"output": "trmnl.png", "output": "trmnl.png",
"font": "font.ttf", "font": "font.ttf",
"widgets": "widgets":

View File

@@ -4,6 +4,7 @@
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -11,20 +12,24 @@
#include "sdl_helpers.h" #include "sdl_helpers.h"
#include "Widgets/Widget.h" #include "Widgets/Widget.h"
#include "Widgets/WidgetText.h" #include "Widgets/WidgetImage.h"
#include "Widgets/WidgetRect.h" #include "Widgets/WidgetRect.h"
#include "Widgets/WidgetText.h"
using std::cout; using std::cout;
using std::endl; using std::endl;
using std::map; using std::map;
using std::shared_ptr;
using std::string; using std::string;
using std::unique_ptr;
using std::vector; using std::vector;
using nlohmann::json; using nlohmann::json;
void init_builders(map<string, Widget*(*)(const json&)>& widget_builders) void init_builders(map<string, unique_ptr<Widget>(*)(const json&)>& widget_builders)
{ {
widget_builders["text"] = &WidgetText::builder; widget_builders["image"] = &WidgetImage::builder;
widget_builders["rect"] = &WidgetRect::builder; widget_builders["rect"] = &WidgetRect::builder;
widget_builders["text"] = &WidgetText::builder;
} }
int main(int argc, char **argv) int main(int argc, char **argv)
@@ -33,7 +38,7 @@ int main(int argc, char **argv)
bool ok; bool ok;
int screen_width = 800; int screen_width = 800;
int screen_height = 600; int screen_height = 480;
string output_filename = "trmnl.png"; string output_filename = "trmnl.png";
string cfg_filename = "config.json"; string cfg_filename = "config.json";
json cfg; json cfg;
@@ -41,8 +46,8 @@ int main(int argc, char **argv)
SDL_Surface* main_surface = nullptr; SDL_Surface* main_surface = nullptr;
vector<json> widgets_cfg; vector<json> widgets_cfg;
vector<Widget*> widgets; vector<shared_ptr<Widget>> widgets;
map<string, Widget*(*)(const json&)> widget_builders; map<string, unique_ptr<Widget>(*)(const json&)> widget_builders;
ok = init_sdl(); ok = init_sdl();
if (!ok) if (!ok)
@@ -102,7 +107,7 @@ int main(int argc, char **argv)
} }
// Construct and add widget to vector // Construct and add widget to vector
Widget* widget = widget_builders[widget_name](j); shared_ptr<Widget> widget = widget_builders[widget_name](j);
if (nullptr != widget) if (nullptr != widget)
{ {
widgets.push_back(widget); widgets.push_back(widget);
@@ -113,7 +118,7 @@ int main(int argc, char **argv)
SDL_FillRect(main_surface, nullptr, SDL_MapRGBA(main_surface->format, 255, 255, 255, SDL_ALPHA_OPAQUE)); SDL_FillRect(main_surface, nullptr, SDL_MapRGBA(main_surface->format, 255, 255, 255, SDL_ALPHA_OPAQUE));
// Draw and apply all widgets // Draw and apply all widgets
for (Widget* widget : widgets) for (shared_ptr<Widget>& widget : widgets)
{ {
widget->draw(); widget->draw();
SDL_Rect rect = widget->get_rect(); SDL_Rect rect = widget->get_rect();
@@ -125,11 +130,6 @@ int main(int argc, char **argv)
IMG_SavePNG(main_surface, output_filename.c_str()); IMG_SavePNG(main_surface, output_filename.c_str());
cleanup: cleanup:
// Destroy All Widgets
for (Widget* widget : widgets)
{
delete widget;
}
widgets.clear(); widgets.clear();
clean_sdl(); clean_sdl();

View File

@@ -77,42 +77,56 @@ TTF_Font* get_font(int size, const string& filename)
} }
SDL_Rect surface_align(const SDL_Surface* base, const SDL_Surface* applied, SDL_Rect surface_align(const SDL_Surface* base, const SDL_Surface* applied,
HorizontalAlign halign, VerticalAlign valign) HorizontalAlign halign, VerticalAlign valign, const SDL_Rect* hint_external)
{ {
SDL_Rect align = {.x = 0, .y = 0, .w = 0, .h = 0}; SDL_Rect align = {.x = 0, .y = 0, .w = 0, .h = 0};
SDL_Rect hint;
if ((nullptr == base) || (nullptr == applied)) if ((nullptr == base) || (nullptr == applied))
{ {
return align; return align;
} }
// These are set in order to handle scaled blits
align.w = applied->w;
align.h = applied->h;
if (nullptr == hint_external)
{
hint = SDL_Rect{.x = 0, .y = 0, .w = applied->w, .h = applied->h};
}
else
{
hint = *hint_external;
}
switch (halign) switch (halign)
{ {
case HALIGN_LEFT: case HALIGN_LEFT:
align.x = 0; align.x = -hint.x;
break; break;
case HALIGN_CENTER: case HALIGN_CENTER:
align.x = base->w / 2 - applied->w / 2; align.x = base->w / 2 - hint.x - hint.w / 2;
break; break;
case HALIGN_RIGHT: case HALIGN_RIGHT:
align.x = base->w - applied->w; align.x = base->w - (hint.x + hint.w);
break; break;
} }
switch (valign) switch (valign)
{ {
case VALIGN_TOP: case VALIGN_TOP:
align.y = 0; align.y = -hint.y;
break; break;
case VALIGN_CENTER: case VALIGN_CENTER:
align.y = base->h / 2 - applied->h / 2; align.y = base->h / 2 - hint.y - hint.h / 2;
break; break;
case VALIGN_BOTTOM: case VALIGN_BOTTOM:
align.y = base->h - applied->h; align.y = base->h - (hint.y + hint.h);
break; break;
} }
@@ -157,7 +171,7 @@ void json_extract(const json& j, const string& key, string& out)
} }
} }
void json_extract(const nlohmann::json& j, const string& key, int& out) void json_extract(const json& j, const string& key, int& out)
{ {
if (j.contains(key) && j[key].is_number_integer()) if (j.contains(key) && j[key].is_number_integer())
{ {
@@ -165,7 +179,7 @@ void json_extract(const nlohmann::json& j, const string& key, int& out)
} }
} }
void json_extract(const nlohmann::json& j, const string& key, bool& out) void json_extract(const json& j, const string& key, bool& out)
{ {
if (j.contains(key) && j[key].is_boolean()) if (j.contains(key) && j[key].is_boolean())
{ {
@@ -228,10 +242,24 @@ void json_extract(const json& j, const string& key, SDL_Color& out)
} }
} }
void json_extract(const nlohmann::json& j, const string& key, Uint8& out) void json_extract(const json& j, const string& key, Uint8& out)
{ {
if (j.contains(key) && j[key].is_number_unsigned()) if (j.contains(key) && j[key].is_number_unsigned())
{ {
out = j[key]; out = j[key];
} }
} }
void json_extract(const json& j, const string& key, ImageResize& out)
{
if (j.contains(key))
{
try
{
out = j[key].get<ImageResize>();
}
catch(const std::exception& e)
{
}
}
}

View File

@@ -52,6 +52,20 @@ NLOHMANN_JSON_SERIALIZE_ENUM(TextFit,
{FIT_AUTO, "auto"}, {FIT_AUTO, "auto"},
}) })
enum ImageResize
{
RESIZE_NONE,
RESIZE_FIT,
RESIZE_STRETCH
};
NLOHMANN_JSON_SERIALIZE_ENUM(ImageResize,
{
{RESIZE_NONE, "none"},
{RESIZE_FIT, "fit"},
{RESIZE_STRETCH, "stretch"},
})
// Call this before everything // Call this before everything
// Prints its messages // Prints its messages
bool init_sdl(); bool init_sdl();
@@ -66,8 +80,9 @@ TTF_Font* get_font(int size, const std::string& filename = default_font_name);
// Returns a rect to use during bliting of 2 surfaces // Returns a rect to use during bliting of 2 surfaces
// base - surface on which the other is applied to // base - surface on which the other is applied to
// applied - the surface which will be applied to the other // applied - the surface which will be applied to the other
// hint - rect to hint visible area from applied - can be NULL
SDL_Rect surface_align(const SDL_Surface* base, const SDL_Surface* applied, SDL_Rect surface_align(const SDL_Surface* base, const SDL_Surface* applied,
HorizontalAlign halign, VerticalAlign valign); HorizontalAlign halign, VerticalAlign valign, const SDL_Rect* hint = nullptr);
// Reads the file and tries to parse a JSON file with comments // Reads the file and tries to parse a JSON file with comments
// cfg - output json struct // cfg - output json struct
@@ -85,5 +100,6 @@ void json_extract(const nlohmann::json& j, const std::string& key, VerticalAlign
void json_extract(const nlohmann::json& j, const std::string& key, TextFit& 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, SDL_Color& out);
void json_extract(const nlohmann::json& j, const std::string& key, Uint8& out); void json_extract(const nlohmann::json& j, const std::string& key, Uint8& out);
void json_extract(const nlohmann::json& j, const std::string& key, ImageResize& out);
#endif // SDL_HELPERS_H_ #endif // SDL_HELPERS_H_