Compare commits
13 Commits
8e88adf1df
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c5cec7d8a | |||
| 3f4e42d7ce | |||
| 40df0f6e0c | |||
| 17f73211a9 | |||
| c626578557 | |||
| af9ebe5fe8 | |||
| 3d45fc89fc | |||
| 8e276ab2ab | |||
| ddc7898a78 | |||
| d0f538224b | |||
| 908ddeba44 | |||
| 82c35a6636 | |||
| 9f8a037f69 |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,4 +1,13 @@
|
||||
# Exclude the executable
|
||||
trmnl_sdl
|
||||
|
||||
# Exclude configuration files
|
||||
*.json
|
||||
|
||||
# Exclude images
|
||||
*.bmp
|
||||
*.jpg
|
||||
*.png
|
||||
|
||||
# Exclude fonts
|
||||
*.ttf
|
||||
|
||||
3
Makefile
3
Makefile
@@ -2,8 +2,9 @@ src_files += main.cpp
|
||||
src_files += sdl_helpers.cpp
|
||||
|
||||
src_files += Widgets/Widget.cpp
|
||||
src_files += Widgets/WidgetText.cpp
|
||||
src_files += Widgets/WidgetImage.cpp
|
||||
src_files += Widgets/WidgetRect.cpp
|
||||
src_files += Widgets/WidgetText.cpp
|
||||
|
||||
all:
|
||||
g++ $(src_files) -Wall -lSDL2 -lSDL2_ttf -lSDL2_image -o trmnl_sdl
|
||||
|
||||
121
README.md
121
README.md
@@ -1,19 +1,124 @@
|
||||
# What is this
|
||||
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. Either named `config.json` or added as the first command line parameter.
|
||||
4. ???
|
||||
5. Profit
|
||||
# How to build
|
||||
**NOTE: Tested on Ubuntu 24.04.**
|
||||
|
||||
# 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 trmnl.png -monochrome -colors 2 -depth 1 -strip png:output.png`
|
||||
|
||||
### Convert image with dithering
|
||||
`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
|
||||
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 |
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include "../json.hpp"
|
||||
#include <memory>
|
||||
|
||||
// Base Widget class for the whole Widget system
|
||||
class Widget
|
||||
{
|
||||
protected:
|
||||
|
||||
165
Widgets/WidgetImage.cpp
Normal file
165
Widgets/WidgetImage.cpp
Normal 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
59
Widgets/WidgetImage.h
Normal 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_
|
||||
@@ -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()
|
||||
{
|
||||
if (nullptr == m_surface)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear surface
|
||||
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 y = 0;
|
||||
@@ -115,7 +135,7 @@ Widget* WidgetRect::builder(const nlohmann::json& j)
|
||||
json_extract(j, "stroke", stroke_size);
|
||||
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)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "Widget.h"
|
||||
|
||||
// Renders a rectangle with optional rounded corners using either fill or internal stroke
|
||||
class WidgetRect : public Widget
|
||||
{
|
||||
protected:
|
||||
@@ -26,11 +27,15 @@ public:
|
||||
WidgetRect(int x, int y, int width, int height,
|
||||
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;
|
||||
|
||||
static Widget* builder(const nlohmann::json& j);
|
||||
static std::unique_ptr<Widget> builder(const nlohmann::json& j);
|
||||
|
||||
private:
|
||||
protected:
|
||||
// x, y - center of circle
|
||||
// quadrant - 1--4
|
||||
void draw_circle_corner(int x, int y, int quadrant);
|
||||
|
||||
@@ -15,9 +15,76 @@ static SDL_Surface* render_text(bool should_wrap, TTF_Font* font, const string&
|
||||
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,
|
||||
TextFit fit, bool should_wrap,
|
||||
HorizontalAlign halign, VerticalAlign valign,
|
||||
bool halign_via_visible, bool valign_via_visible,
|
||||
SDL_Color text_color,
|
||||
int size, std::string font)
|
||||
: Widget(x, y, width, height),
|
||||
@@ -28,7 +95,9 @@ m_fit(fit),
|
||||
m_should_wrap(should_wrap),
|
||||
m_halign(halign),
|
||||
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_halign(HALIGN_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_halign(HALIGN_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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (nullptr == m_surface)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear surface
|
||||
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
|
||||
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_FreeSurface(txt_surface);
|
||||
}
|
||||
|
||||
Widget* WidgetText::builder(const nlohmann::json& j)
|
||||
std::unique_ptr<Widget> WidgetText::builder(const nlohmann::json& j)
|
||||
{
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
@@ -187,6 +287,8 @@ Widget* WidgetText::builder(const nlohmann::json& j)
|
||||
bool should_wrap = false;
|
||||
HorizontalAlign halign = HALIGN_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};
|
||||
int size = 0;
|
||||
string font = "";
|
||||
@@ -200,6 +302,8 @@ Widget* WidgetText::builder(const nlohmann::json& j)
|
||||
json_extract(j, "should_wrap", should_wrap);
|
||||
json_extract(j, "halign", halign);
|
||||
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, "size", size);
|
||||
json_extract(j, "font", font);
|
||||
@@ -209,5 +313,9 @@ Widget* WidgetText::builder(const nlohmann::json& j)
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
#define WIDGET_TEXT_H_
|
||||
|
||||
#include "Widget.h"
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../sdl_helpers.h"
|
||||
|
||||
// Renders text within a specified box
|
||||
class WidgetText : public Widget
|
||||
{
|
||||
protected:
|
||||
@@ -40,10 +40,19 @@ protected:
|
||||
// Default - black
|
||||
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:
|
||||
WidgetText(int x, int y, int width, int height, std::string text,
|
||||
TextFit fit, bool should_wrap,
|
||||
HorizontalAlign halign, VerticalAlign valign,
|
||||
bool halign_via_visible, bool valign_via_visible,
|
||||
SDL_Color text_color,
|
||||
int size, std::string font = "");
|
||||
|
||||
@@ -61,10 +70,12 @@ public:
|
||||
void set_halign(HorizontalAlign halign);
|
||||
void set_valign(VerticalAlign valign);
|
||||
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;
|
||||
|
||||
static Widget* builder(const nlohmann::json& j);
|
||||
static std::unique_ptr<Widget> builder(const nlohmann::json& j);
|
||||
};
|
||||
|
||||
#endif // WIDGET_TEXT_H_
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"height": 480,
|
||||
"output": "trmnl.png",
|
||||
"font": "font.ttf",
|
||||
"widgets":
|
||||
|
||||
26
main.cpp
26
main.cpp
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -11,20 +12,24 @@
|
||||
|
||||
#include "sdl_helpers.h"
|
||||
#include "Widgets/Widget.h"
|
||||
#include "Widgets/WidgetText.h"
|
||||
#include "Widgets/WidgetImage.h"
|
||||
#include "Widgets/WidgetRect.h"
|
||||
#include "Widgets/WidgetText.h"
|
||||
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::map;
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
using std::unique_ptr;
|
||||
using std::vector;
|
||||
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["text"] = &WidgetText::builder;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
@@ -33,7 +38,7 @@ int main(int argc, char **argv)
|
||||
bool ok;
|
||||
|
||||
int screen_width = 800;
|
||||
int screen_height = 600;
|
||||
int screen_height = 480;
|
||||
string output_filename = "trmnl.png";
|
||||
string cfg_filename = "config.json";
|
||||
json cfg;
|
||||
@@ -41,8 +46,8 @@ int main(int argc, char **argv)
|
||||
SDL_Surface* main_surface = nullptr;
|
||||
|
||||
vector<json> widgets_cfg;
|
||||
vector<Widget*> widgets;
|
||||
map<string, Widget*(*)(const json&)> widget_builders;
|
||||
vector<shared_ptr<Widget>> widgets;
|
||||
map<string, unique_ptr<Widget>(*)(const json&)> widget_builders;
|
||||
|
||||
ok = init_sdl();
|
||||
if (!ok)
|
||||
@@ -102,7 +107,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
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));
|
||||
|
||||
// Draw and apply all widgets
|
||||
for (Widget* widget : widgets)
|
||||
for (shared_ptr<Widget>& widget : widgets)
|
||||
{
|
||||
widget->draw();
|
||||
SDL_Rect rect = widget->get_rect();
|
||||
@@ -125,11 +130,6 @@ int main(int argc, char **argv)
|
||||
IMG_SavePNG(main_surface, output_filename.c_str());
|
||||
|
||||
cleanup:
|
||||
// Destroy All Widgets
|
||||
for (Widget* widget : widgets)
|
||||
{
|
||||
delete widget;
|
||||
}
|
||||
widgets.clear();
|
||||
|
||||
clean_sdl();
|
||||
|
||||
@@ -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,
|
||||
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 hint;
|
||||
|
||||
if ((nullptr == base) || (nullptr == applied))
|
||||
{
|
||||
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)
|
||||
{
|
||||
case HALIGN_LEFT:
|
||||
align.x = 0;
|
||||
align.x = -hint.x;
|
||||
break;
|
||||
|
||||
case HALIGN_CENTER:
|
||||
align.x = base->w / 2 - applied->w / 2;
|
||||
align.x = base->w / 2 - hint.x - hint.w / 2;
|
||||
break;
|
||||
|
||||
case HALIGN_RIGHT:
|
||||
align.x = base->w - applied->w;
|
||||
align.x = base->w - (hint.x + hint.w);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (valign)
|
||||
{
|
||||
case VALIGN_TOP:
|
||||
align.y = 0;
|
||||
align.y = -hint.y;
|
||||
break;
|
||||
|
||||
case VALIGN_CENTER:
|
||||
align.y = base->h / 2 - applied->h / 2;
|
||||
align.y = base->h / 2 - hint.y - hint.h / 2;
|
||||
break;
|
||||
|
||||
case VALIGN_BOTTOM:
|
||||
align.y = base->h - applied->h;
|
||||
align.y = base->h - (hint.y + hint.h);
|
||||
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())
|
||||
{
|
||||
@@ -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())
|
||||
{
|
||||
@@ -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())
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,20 @@ NLOHMANN_JSON_SERIALIZE_ENUM(TextFit,
|
||||
{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
|
||||
// Prints its messages
|
||||
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
|
||||
// base - surface on which the other is applied to
|
||||
// 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,
|
||||
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
|
||||
// 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, 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, ImageResize& out);
|
||||
|
||||
#endif // SDL_HELPERS_H_
|
||||
|
||||
Reference in New Issue
Block a user