FreeRDP
Loading...
Searching...
No Matches
SDL3/dialogs/sdl_widget.cpp
1
20#include <cassert>
21#include <cstdio>
22#include <cstdlib>
23#include <algorithm>
24
25#include <SDL3/SDL.h>
26#include <SDL3_ttf/SDL_ttf.h>
27
28#include "sdl_widget.hpp"
29#include "sdl_blend_mode_guard.hpp"
30#include "../sdl_utils.hpp"
31
32#include "res/sdl3_resource_manager.hpp"
33
34#include <freerdp/log.h>
35
36#if defined(WITH_SDL_IMAGE_DIALOGS)
37#include <SDL3_image/SDL_image.h>
38#endif
39
40#define TAG CLIENT_TAG("SDL.widget")
41
42static const Uint32 hpadding = 10;
43
44SdlWidget::SdlWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect)
45 : _renderer(renderer),
46 _engine(TTF_CreateRendererTextEngine(renderer.get()), TTF_DestroyRendererTextEngine),
47 _rect(rect)
48{
49 assert(renderer);
50
52 "OpenSans-VariableFont_wdth,wght.ttf");
53 if (!ops)
54 widget_log_error(false, "SDLResourceManager::get");
55 else
56 {
57 _font = std::shared_ptr<TTF_Font>(TTF_OpenFontIO(ops, true, 64), TTF_CloseFont);
58 if (!_font)
59 widget_log_error(false, "TTF_OpenFontRW");
60 }
61}
62
63#if defined(WITH_SDL_IMAGE_DIALOGS)
64SdlWidget::SdlWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect,
65 SDL_IOStream* ops)
66 : _renderer(renderer),
67 _engine(TTF_CreateRendererTextEngine(renderer.get()), TTF_DestroySurfaceTextEngine),
68 _rect(rect)
69{
70 if (ops)
71 {
72 _image = std::shared_ptr<SDL_Texture>(IMG_LoadTexture_IO(renderer.get(), ops, 1),
73 SDL_DestroyTexture);
74 if (!_image)
75 widget_log_error(false, "IMG_LoadTexture_IO");
76 }
77}
78#endif
79
80SdlWidget::SdlWidget(SdlWidget&& other) noexcept
81 : _renderer(std::move(other._renderer)), _backgroundcolor(other._backgroundcolor),
82 _fontcolor(other._fontcolor), _text(std::move(other._text)), _font(std::move(other._font)),
83 _image(std::move(other._image)), _engine(std::move(other._engine)), _rect(other._rect),
84 _wrap(other._wrap), _text_width(other._text_width)
85{
86 other._font = nullptr;
87 other._image = nullptr;
88 other._engine = nullptr;
89}
90
91std::shared_ptr<SDL_Texture> SdlWidget::render_text(const std::string& text, SDL_Color fgcolor,
92 SDL_FRect& src, SDL_FRect& dst) const
93{
94 auto surface = std::shared_ptr<SDL_Surface>(
95 TTF_RenderText_Blended(_font.get(), text.c_str(), 0, fgcolor), SDL_DestroySurface);
96 if (!surface)
97 {
98 widget_log_error(false, "TTF_RenderText_Blended");
99 return nullptr;
100 }
101
102 auto texture = std::shared_ptr<SDL_Texture>(
103 SDL_CreateTextureFromSurface(_renderer.get(), surface.get()), SDL_DestroyTexture);
104 if (!texture)
105 {
106 widget_log_error(false, "SDL_CreateTextureFromSurface");
107 return nullptr;
108 }
109
110 if (!_engine)
111 {
112 widget_log_error(false, "TTF_CreateRendererTextEngine");
113 return nullptr;
114 }
115
116 std::unique_ptr<TTF_Text, decltype(&TTF_DestroyText)> txt(
117 TTF_CreateText(_engine.get(), _font.get(), text.c_str(), text.size()), TTF_DestroyText);
118
119 if (!txt)
120 {
121 widget_log_error(false, "TTF_CreateText");
122 return nullptr;
123 }
124 int w = 0;
125 int h = 0;
126 if (!TTF_GetTextSize(txt.get(), &w, &h))
127 {
128 widget_log_error(false, "TTF_GetTextSize");
129 return nullptr;
130 }
131
132 src.w = static_cast<float>(w);
133 src.h = static_cast<float>(h);
134 /* Do some magic:
135 * - Add padding before and after text
136 * - if text is too long only show the last elements
137 * - if text is too short only update used space
138 */
139 dst = _rect;
140 dst.x += hpadding;
141 dst.w -= 2 * hpadding;
142 const float scale = dst.h / src.h;
143 const float sws = (src.w) * scale;
144 const float dws = (dst.w) / scale;
145 dst.w = std::min(dst.w, sws);
146 if (src.w > dws)
147 {
148 src.x = src.w - dws;
149 src.w = dws;
150 }
151 return texture;
152}
153
154static float scale(float dw, float dh)
155{
156 const auto scale = dh / dw;
157 const auto dr = dh * scale;
158 return dr;
159}
160
161std::shared_ptr<SDL_Texture> SdlWidget::render_text_wrapped(const std::string& text,
162 SDL_Color fgcolor, SDL_FRect& src,
163 SDL_FRect& dst) const
164{
165 assert(_text_width < INT32_MAX);
166
167 auto surface = std::shared_ptr<SDL_Surface>(
168 TTF_RenderText_Blended_Wrapped(_font.get(), text.c_str(), 0, fgcolor,
169 static_cast<int>(_text_width)),
170 SDL_DestroySurface);
171 if (!surface)
172 {
173 widget_log_error(false, "TTF_RenderText_Blended");
174 return nullptr;
175 }
176
177 src.w = static_cast<float>(surface->w);
178 src.h = static_cast<float>(surface->h);
179
180 auto texture = std::shared_ptr<SDL_Texture>(
181 SDL_CreateTextureFromSurface(_renderer.get(), surface.get()), SDL_DestroyTexture);
182 if (!texture)
183 {
184 widget_log_error(false, "SDL_CreateTextureFromSurface");
185 return nullptr;
186 }
187
188 /* Do some magic:
189 * - Add padding before and after text
190 * - if text is too long only show the last elements
191 * - if text is too short only update used space
192 */
193 dst = _rect;
194 dst.x += hpadding;
195 dst.w -= 2 * hpadding;
196 auto dh = scale(src.w, src.h);
197 dst.h = std::min<float>(dh, dst.h);
198
199 return texture;
200}
201
202SdlWidget::~SdlWidget() = default;
203
204bool SdlWidget::error_ex(bool success, const char* what, const char* file, size_t line,
205 const char* fkt)
206{
207 if (success)
208 {
209 // Flip SDL3 convention to existing code convention to minimize code changes
210 return false;
211 }
212 static wLog* log = nullptr;
213 if (!log)
214 log = WLog_Get(TAG);
215 // Use -1 as it indicates error similar to SDL2 conventions
216 // sdl_log_error_ex treats any value other than 0 as SDL error
217 return sdl_log_error_ex(-1, log, what, file, line, fkt);
218}
219
220bool SdlWidget::updateInternal()
221{
222 return update_text(_text);
223}
224
225bool SdlWidget::draw_rect(const SDL_FRect& rect, SDL_Color color) const
226{
227 const auto drc = SDL_SetRenderDrawColor(_renderer.get(), color.r, color.g, color.b, color.a);
228 if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
229 return false;
230
231 const auto rc = SDL_RenderFillRect(_renderer.get(), &rect);
232 return !widget_log_error(rc, "SDL_RenderFillRect");
233}
234
235bool SdlWidget::fill(SDL_Color color) const
236{
237 std::vector<SDL_Color> colors = { color };
238 return fill(colors);
239}
240
241bool SdlWidget::fill(const std::vector<SDL_Color>& colors) const
242{
243 SdlBlendModeGuard guard(_renderer, SDL_BLENDMODE_NONE);
244
245 for (auto color : colors)
246 {
247 if (!draw_rect(_rect, color))
248 return false;
249
250 if (!guard.update(SDL_BLENDMODE_ADD))
251 return false;
252 }
253
254 return true;
255}
256
257bool SdlWidget::wrap() const
258{
259 return _wrap;
260}
261
262bool SdlWidget::set_wrap(bool wrap, size_t width)
263{
264 _wrap = wrap;
265 _text_width = width;
266 return _wrap;
267}
268
269const SDL_FRect& SdlWidget::rect() const
270{
271 return _rect;
272}
273
274bool SdlWidget::clear() const
275{
276 if (!_renderer)
277 return false;
278
279 SdlBlendModeGuard guard(_renderer, SDL_BLENDMODE_NONE);
280
281 const auto drc = SDL_SetRenderDrawColor(_renderer.get(), _backgroundcolor.r, _backgroundcolor.g,
282 _backgroundcolor.b, _backgroundcolor.a);
283 if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
284 return false;
285
286 const auto rcls = SDL_RenderRect(_renderer.get(), &_rect);
287 return !widget_log_error(rcls, "SDL_RenderRect");
288}
289
290bool SdlWidget::update()
291{
292 if (!clear())
293 return false;
294 // TODO: Draw widget specifics
295 return updateInternal();
296}
297
298bool SdlWidget::update_text(const std::string& text)
299{
300 _text = text;
301 if (_text.empty())
302 return true;
303
304 SDL_FRect src{};
305 SDL_FRect dst{};
306
307 std::shared_ptr<SDL_Texture> texture;
308 if (_image)
309 {
310 texture = _image;
311 dst = _rect;
312 auto propId = SDL_GetTextureProperties(_image.get());
313 auto w = SDL_GetNumberProperty(propId, SDL_PROP_TEXTURE_WIDTH_NUMBER, -1);
314 auto h = SDL_GetNumberProperty(propId, SDL_PROP_TEXTURE_HEIGHT_NUMBER, -1);
315 if (w < 0 || h < 0)
316 widget_log_error(false, "SDL_GetTextureProperties");
317 src.w = static_cast<float>(w);
318 src.h = static_cast<float>(h);
319 }
320 else if (_wrap)
321 texture = render_text_wrapped(_text, _fontcolor, src, dst);
322 else
323 texture = render_text(_text, _fontcolor, src, dst);
324 if (!texture)
325 return false;
326
327 const auto rc = SDL_RenderTexture(_renderer.get(), texture.get(), &src, &dst);
328 return !widget_log_error(rc, "SDL_RenderCopy");
329}
static SDL_IOStream * get(const std::string &type, const std::string &id)
static std::string typeFonts()