Symphony Of Empires
widget.cpp
Go to the documentation of this file.
1 // Symphony of Empires
2 // Copyright (C) 2021, Symphony of Empires contributors
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <https://www.gnu.org/licenses/>.
16 //
17 // ----------------------------------------------------------------------------
18 // Name:
19 // eng3d/ui/widget.cpp
20 //
21 // Abstract:
22 // Does some important stuff.
23 // ----------------------------------------------------------------------------
24 
25 #include <cstdlib>
26 #include <cstring>
27 #include <string>
28 #include <cassert>
29 #include <algorithm>
30 #include <stack>
31 #include <glm/vec2.hpp>
32 #include <SDL_ttf.h>
33 
34 #include "eng3d/ui/widget.hpp"
35 #include "eng3d/ui/ui.hpp"
36 #include "eng3d/ui/checkbox.hpp"
37 #include "eng3d/texture.hpp"
38 #include "eng3d/rectangle.hpp"
39 #include "eng3d/state.hpp"
40 #include "eng3d/ui/tooltip.hpp"
41 #include "eng3d/primitive.hpp"
42 #include "eng3d/utils.hpp"
43 
44 using namespace UI;
45 
46 Widget::Widget(Widget* _parent, int _x, int _y, const unsigned w, const unsigned h, WidgetType _type)
47  : parent{ _parent },
48  type{ _type },
49  x{ _x },
50  y{ _y },
51  width{ w },
52  height{ h }
53 {
54  if(parent != nullptr) {
55  x += parent->padding.x;
56  y += parent->padding.y;
57  parent->add_child(*this);
58  } else {
59  // Add the widget to the context in each construction without parent
60  g_ui_context->add_widget(this);
61  }
62 }
63 
64 Widget::Widget(Widget* _parent, int _x, int _y, const unsigned w, const unsigned h, WidgetType _type, std::shared_ptr<Eng3D::Texture> tex)
65  : parent{ _parent },
66  type{ _type },
67  x{ _x },
68  y{ _y },
69  width{ w },
70  height{ h },
71  current_texture{ tex }
72 {
73  if(parent != nullptr) {
74  x += parent->padding.x;
75  y += parent->padding.y;
76  parent->add_child(*this);
77  } else {
78  // Add the widget to the context in each construction without parent
79  g_ui_context->add_widget(this);
80  }
81 }
82 
84  // Common texture also deleted?
85  if(g_ui_context->tooltip_widget == this)
86  g_ui_context->tooltip_widget = nullptr;
87  delete tooltip;
88 }
89 
90 void Widget::draw_rect(const Eng3D::Texture* tex, Eng3D::Rect rect_pos, Eng3D::Rect rect_tex, Eng3D::Rect viewport) {
91  glm::vec2 pos_size = rect_pos.size();
92  pos_size.x = pos_size.x > 0 ? pos_size.x : 1.f;
93  pos_size.y = pos_size.y > 0 ? pos_size.y : 1.f;
94  glm::vec2 tex_size = rect_tex.size();
95 
96  if(rect_pos.left < viewport.left) {
97  float x_ratio = (viewport.left - rect_pos.left) / pos_size.x;
98  rect_tex.left = rect_tex.left + x_ratio * tex_size.x;
99  rect_pos.left = viewport.left;
100  }
101  if(rect_pos.right > viewport.right) {
102  float x_ratio = (rect_pos.right - viewport.right) / pos_size.x;
103  rect_tex.right = rect_tex.right - x_ratio * tex_size.x;
104  rect_pos.right = viewport.right;
105  }
106  if(rect_pos.top < viewport.top) {
107  float y_ratio = (viewport.top - rect_pos.top) / pos_size.y;
108  rect_tex.top = rect_tex.top + y_ratio * tex_size.y;
109  rect_pos.top = viewport.top;
110  }
111  if(rect_pos.bottom > viewport.bottom) {
112  float y_ratio = (rect_pos.bottom - viewport.bottom) / pos_size.y;
113  rect_tex.bottom = rect_tex.bottom - y_ratio * tex_size.y;
114  rect_pos.bottom = viewport.bottom;
115  }
116 
118  if(tex != nullptr) {
119  g_ui_context->obj_shader->set_texture(0, "diffuse_map", *tex);
120  } else {
121  g_ui_context->obj_shader->set_texture(0, "diffuse_map", *Eng3D::State::get_instance().tex_man.get_white());
122  }
123  Eng3D::Square(rect_pos, rect_tex).draw();
124 }
125 
126 void Widget::draw_border(Eng3D::Rect viewport) {
127  float x_offset = border.offset.x;
128  float y_offset = border.offset.y;
129  float b_w = border.size.x;
130  float b_h = border.size.y;
131  float b_tex_w = border.texture_size.x;
132  float b_tex_h = border.texture_size.y;
133 
134  const auto& border_tex = *border.texture.get();
135 
136  // Draw border edges and corners
137  Eng3D::Rect pos_rect(0, 0, 0, 0);
138  Eng3D::Rect tex_rect(0, 0, 0, 0);
139  pos_rect.left = x_offset;
140  pos_rect.top = y_offset;
141  pos_rect.right = x_offset + b_w;
142  pos_rect.bottom = y_offset + b_h;
143  tex_rect.left = 0;
144  tex_rect.top = 0;
145  tex_rect.right = b_tex_w / border_tex.width;
146  tex_rect.bottom = b_tex_h / border_tex.height;
147 
148  // Top left corner
149  draw_rect(&border_tex, pos_rect, tex_rect, viewport);
150 
151  // Top right corner
152  pos_rect.left = width - b_w;
153  tex_rect.left = (border_tex.width - b_tex_w) / border_tex.width;
154  pos_rect.right = width;
155  tex_rect.right = 1.f;
156  draw_rect(&border_tex, pos_rect, tex_rect, viewport);
157 
158  // Bottom right corner
159  pos_rect.top = height - b_h;
160  tex_rect.top = (border_tex.height - b_tex_h) / border_tex.height;
161  pos_rect.bottom = height;
162  tex_rect.bottom = 1.f;
163  draw_rect(&border_tex, pos_rect, tex_rect, viewport);
164 
165  // Bottom left corner
166  pos_rect.left = x_offset;
167  tex_rect.left = 0;
168  pos_rect.right = x_offset + b_w;
169  tex_rect.right = b_tex_w / border_tex.width;
170  draw_rect(&border_tex, pos_rect, tex_rect, viewport);
171 
172  // Top edge
173  pos_rect.left = x_offset + b_w;
174  tex_rect.left = b_tex_w / border_tex.width;
175  pos_rect.right = width - b_w;
176  tex_rect.right = (border_tex.width - b_tex_w) / border_tex.width;
177  pos_rect.top = y_offset;
178  tex_rect.top = 0;
179  pos_rect.bottom = y_offset + b_h;
180  tex_rect.bottom = b_tex_h / border_tex.height;
181  draw_rect(&border_tex, pos_rect, tex_rect, viewport);
182 
183  // Bottom edge
184  pos_rect.top = height - b_h;
185  tex_rect.top = (border_tex.height - b_tex_h) / border_tex.height;
186  pos_rect.bottom = height;
187  tex_rect.bottom = 1.f;
188  draw_rect(&border_tex, pos_rect, tex_rect, viewport);
189 
190  // Left edge
191  pos_rect.top = y_offset + b_h;
192  tex_rect.top = b_tex_h / border_tex.height;
193  pos_rect.bottom = height - b_h;
194  tex_rect.bottom = (border_tex.height - b_tex_h) / border_tex.height;
195  pos_rect.left = x_offset;
196  tex_rect.left = 0;
197  pos_rect.right = b_w;
198  tex_rect.right = b_tex_w / border_tex.width;
199  draw_rect(&border_tex, pos_rect, tex_rect, viewport);
200 
201  // Right edge
202  pos_rect.left = width - b_w;
203  tex_rect.left = (border_tex.width - b_tex_w) / border_tex.width;
204  pos_rect.right = width;
205  tex_rect.right = 1.f;
206  draw_rect(&border_tex, pos_rect, tex_rect, viewport);
207 
208  // Middle
209  pos_rect.left = x_offset + b_w;
210  tex_rect.left = b_tex_w / border_tex.width;
211  pos_rect.right = width - b_w;
212  tex_rect.right = (border_tex.width - b_tex_w) / border_tex.width;
213  pos_rect.top = y_offset + b_h;
214  tex_rect.top = b_tex_h / border_tex.height;
215  pos_rect.bottom = height - b_h;
216  tex_rect.bottom = (border_tex.height - b_tex_h) / border_tex.height;
217  draw_rect(&border_tex, pos_rect, tex_rect, viewport);
218 }
219 
220 // Draw a simple quad
221 void Widget::draw_rectangle(int _x, int _y, unsigned _w, unsigned _h, Eng3D::Rect viewport, const Eng3D::Texture* tex) {
222  // Texture switching in OpenGL is expensive
223  Eng3D::Rect pos_rect(_x, _y, (int)_w, (int)_h);
224  Eng3D::Rect tex_rect(0.f, 0.f, 1.f, 1.f);
225  draw_rect(tex, pos_rect, tex_rect, viewport);
226 }
227 
228 #include <deque>
229 void Widget::on_render(Context& ctx, Eng3D::Rect viewport) {
230  const Eng3D::Rect pos_rect((int)0u, 0u, width, height);
231  const Eng3D::Rect tex_rect((int)0u, 0u, 1u, 1u);
232  g_ui_context->obj_shader->set_texture(0, "diffuse_map", *Eng3D::State::get_instance().tex_man.get_white());
233 
234  // Shadow
235  if(have_shadow) {
236  g_ui_context->obj_shader->set_uniform("diffuse_color", glm::vec4(0.f, 0.f, 0.8f, 0.5f));
237  draw_rectangle(-2, -2, width + 8, height + 8, viewport, nullptr);
238  }
239  g_ui_context->obj_shader->set_uniform("diffuse_color", glm::vec4(1.f));
241  draw_rect(nullptr, pos_rect, tex_rect, viewport);
242  if(background_color.a != 0) {
243  g_ui_context->obj_shader->set_uniform("diffuse_color", glm::vec4(background_color.r, background_color.g, background_color.b, background_color.a));
244  draw_rect(nullptr, pos_rect, tex_rect, viewport);
245  g_ui_context->obj_shader->set_uniform("diffuse_color", glm::vec4(1.f));
246  }
247  if(current_texture != nullptr)
248  draw_rectangle(0, 0, width, height, viewport, current_texture.get());
249 
250  // Top bar of windows display
251  g_ui_context->obj_shader->set_uniform("diffuse_color", glm::vec4(1.f));
253  draw_rectangle(0, 0, width, 24, viewport, ctx.window_top.get());
254  if(border.texture != nullptr)
255  draw_border(viewport);
256 
257  if(text_texture.get() != nullptr) {
258  int x_offset = text_offset_x;
259  int y_offset = text_offset_y;
261  x_offset = (width - text_texture->width) / 2;
262  } else if(text_align_x == UI::Align::END) {
263  x_offset = width - text_texture->width - text_offset_x;
264  }
265 
267  y_offset = (height - text_texture->height) / 2;
268  } else if(text_align_y == UI::Align::END) {
269  y_offset = height - text_texture->height - text_offset_y;
270  }
271 
272  g_ui_context->obj_shader->set_uniform("diffuse_color", glm::vec4(text_color.r, text_color.g, text_color.b, 1.f));
273  draw_rectangle(x_offset, y_offset, text_texture->width, text_texture->height, viewport, text_texture.get());
274  }
275 
276  // Semi-transparent over hover elements which can be clicked
277  bool hover_effect = is_hover == g_ui_context->hover_update;
278  if(clickable_effect && ((on_click && hover_effect) || is_clickable)) {
279  g_ui_context->obj_shader->set_texture(0, "diffuse_map", *Eng3D::State::get_instance().tex_man.get_white());
280  g_ui_context->obj_shader->set_uniform("diffuse_color", glm::vec4(0.5f, 0.5f, 0.5f, 0.5f));
281  draw_rect(nullptr, pos_rect, tex_rect, viewport);
282  }
283 }
284 
288 void UI::Widget::recalc_child_pos() {
289  if(flex == UI::Flex::NONE) return;
290  bool is_row = flex == UI::Flex::ROW;
291  size_t lenght = 0;
292  int movable_children = 0;
293  for(auto& child : children) {
294  if(!child->is_pinned) {
295  lenght += is_row ? child->width : child->height;
296  movable_children++;
297  }
298  }
299 
301  // Justify the children
302  size_t current_length = 0;
303  size_t off_x = this->padding.x, off_y = this->padding.y;
304  size_t max_wrap_height = 0, max_wrap_width = 0;
305  int size = 0, difference = 0;
306  switch(flex_justify) {
307  case FlexJustify::START:
308  current_length = is_row ? this->padding.x : this->padding.y;
309  for(auto& child : children) {
310  if(!child->is_pinned) {
311  if(is_row) {
312  child->x = current_length;
313  current_length += child->width + flex_gap;
314  } else {
315  child->y = current_length;
316  current_length += child->height + flex_gap;
317  }
318 
320  if(overflow == UI::Overflow::WRAP) {
321  if(is_row) {
322  child->y += off_y;
323  max_wrap_height = glm::max(max_wrap_height, child->height + flex_gap);
324  if(current_length >= this->width) {
325  off_y += max_wrap_height;
326  child->x = max_wrap_height = current_length = 0;
327  current_length += child->width + flex_gap;
328  }
329  } else {
330  child->x += off_x;
331  max_wrap_width = glm::max(max_wrap_width, child->width + flex_gap);
332  if(current_length >= this->height) {
333  off_x += max_wrap_width;
334  child->y = max_wrap_width = current_length = 0;
335  current_length += child->height + flex_gap;
336  }
337  }
338  }
339  }
340  }
341  break;
342  case FlexJustify::END:
343  current_length = is_row ? width : height;
344  for(const auto& child : reverse(children)) {
345  if(!child->is_pinned) {
346  if(is_row) {
347  child->x = current_length - child->width - flex_gap;
348  current_length -= child->width;
349  } else {
350  child->y = current_length - child->height - flex_gap;
351  current_length -= child->height;
352  }
353  assert(overflow != UI::Overflow::WRAP);
354  }
355  }
356  break;
358  current_length = is_row ? this->padding.x : this->padding.y;
359  size = is_row ? width : height;
360  difference = (size - lenght) / glm::max(movable_children - 1, 1);
361  for(auto& child : children) {
362  if(!child->is_pinned) {
363  if(is_row) {
364  child->x = current_length;
365  current_length += child->width + difference;
366  } else {
367  child->y = current_length;
368  current_length += child->height + difference;
369  }
370  assert(overflow != UI::Overflow::WRAP);
371  }
372  }
373  break;
375  size = is_row ? width : height;
376  difference = (size - lenght) / movable_children;
377  current_length = glm::max(difference / 2, 0);
378  for(auto& child : children) {
379  if(!child->is_pinned) {
380  if(is_row) {
381  child->x = current_length;
382  current_length += child->width + difference;
383  } else {
384  child->y = current_length;
385  current_length += child->height + difference;
386  }
387  assert(overflow != UI::Overflow::WRAP);
388  }
389  }
390  break;
391  }
392 
393  // Align the children
394  for(auto& child : children) {
395  if(!child->is_pinned) {
396  switch(flex_align) {
397  case UI::Align::START:
398  //if(is_row) child->y = 0;
399  //else child->x = 0;
400  break;
401  case UI::Align::END:
402  if(is_row) child->y = glm::max((int)height - (int)child->height, 0);
403  else child->x = glm::max((int)width - (int)child->width, 0);
404  break;
405  case UI::Align::CENTER:
406  if(is_row) child->y = glm::max((int)height - (int)child->height, 0) / 2;
407  else child->x = glm::max((int)width - (int)child->width, 0) / 2;
408  break;
409  default:
410  break;
411  }
412  }
413  }
414 
415  int child_index = 0;
416  for(auto& child : children) {
417  if(!child->is_pinned) {
418  if(child->on_pos_recalc)
419  child->on_pos_recalc(*child, child_index);
420  child_index++;
421  }
422  }
423 }
424 
428  // Add to list
429  children.push_back(std::move(std::unique_ptr<UI::Widget>(&child)));
430  child.parent = this;
431 
432  // Child changes means a recalculation of positions is in order
433  need_recalc = true;
434 }
435 
436 static inline unsigned int power_two_floor(const unsigned int val) {
437  unsigned int power = 2, nextVal = power * 2;
438  while((nextVal *= 2) <= val)
439  power *= 2;
440  return power * 2;
441 }
442 
445 void Widget::set_text(const std::string& _text) {
446  if(this->text_str == _text) return;
447  text_texture.reset();
448  // Copy _text to a local scope (SDL2 does not like references)
449  this->text_str = _text;
450  if(_text.empty()) return;
451  auto& text_font = font != nullptr ? *font : *g_ui_context->default_font;
452  text_texture = Eng3D::State::get_instance().tex_man.gen_text(text_font, text_color, this->text_str);
453 }
454 
459  this->tooltip = _tooltip;
460  this->tooltip->parent = this;
461 }
462 
466 void Widget::set_tooltip(const std::string& text) {
467  if(text.empty()) return;
468  this->set_tooltip(new UI::Tooltip(this, text));
469 }
470 
474 void Widget::set_tooltip(const std::function<std::string()> _tooltip_text_creator) {
475  this->tooltip_creator = [this, _tooltip_text_creator]() {
476  auto text = _tooltip_text_creator();
477  return new UI::Tooltip(this, text);
478  };
479 }
480 
483 glm::ivec2 UI::Widget::get_y_bounds() const {
484  int child_top = 0, child_bottom = this->height;
485  for(auto& child : this->children) {
486  if(!child->is_pinned) {
487  child_top = glm::min(child_top, child->y);
488  child_bottom = glm::max(child_bottom, child->y + static_cast<int>(child->height));
489  }
490  }
491  child_bottom -= this->height;
492  return glm::ivec2{ child_top, child_bottom };
493 }
494 
497 void UI::Widget::scroll(int _y) {
498  const auto y_bounds = this->get_y_bounds();
499  _y = glm::clamp<int>(_y, -y_bounds.y, -y_bounds.x);
500  for(auto& child : children)
501  if(!child->is_pinned)
502  child->y += _y;
503  this->scrolled_y += _y;
504 }
static State & get_instance()
Definition: state.cpp:514
Eng3D::TextureManager tex_man
Definition: state.hpp:124
std::shared_ptr< Eng3D::Texture > get_white()
Definition: texture.cpp:417
std::shared_ptr< Eng3D::Texture > gen_text(Eng3D::TrueType::Font &font, Eng3D::Color color, const std::string &msg)
Definition: texture.cpp:458
glm::ivec2 texture_size
Definition: widget.hpp:157
glm::ivec2 size
Definition: widget.hpp:156
std::shared_ptr< Eng3D::Texture > texture
Definition: widget.hpp:155
glm::ivec2 offset
Definition: widget.hpp:158
The UI context that handles all the ui widgets.
Definition: ui.hpp:63
std::shared_ptr< Eng3D::TrueType::Font > default_font
Definition: ui.hpp:157
std::unique_ptr< Eng3D::OpenGL::Program > obj_shader
Definition: ui.hpp:159
uint32_t hover_update
Definition: ui.hpp:109
std::shared_ptr< Eng3D::Texture > window_top
Definition: ui.hpp:149
void add_widget(UI::Widget *widget)
Definition: ui.cpp:108
Tooltip widget, used entirely for hovering purpouses, don't use any other widget for hovering unless ...
Definition: tooltip.hpp:38
The master widget all the other widgets inherit from, do not use directly instead use one of the many...
Definition: widget.hpp:176
UI::Border border
Definition: widget.hpp:334
void add_child(UI::Widget &child)
Adds a children to the widget.
Definition: widget.cpp:427
void draw_rectangle(int x, int y, unsigned w, unsigned h, Eng3D::Rect viewport, const Eng3D::Texture *tex)
Definition: widget.cpp:221
std::function< void(UI::Widget &)> on_click
Definition: widget.hpp:350
std::shared_ptr< Eng3D::Texture > text_texture
Definition: widget.hpp:328
void scroll(int y)
Scrolls all the children of this widget by a factor of y.
Definition: widget.cpp:497
void draw_rect(const Eng3D::Texture *tex, Eng3D::Rect rect_pos, Eng3D::Rect rect_tex, Eng3D::Rect viewport)
Definition: widget.cpp:90
virtual void set_tooltip(UI::Tooltip *tooltip)
Set the tooltip to be shown when this widget is hovered, overrides the previous tooltip.
Definition: widget.cpp:458
Eng3D::Color background_color
Definition: widget.hpp:335
UI::Align text_align_y
Definition: widget.hpp:330
bool clickable_effect
Definition: widget.hpp:199
int text_offset_y
Definition: widget.hpp:329
int text_offset_x
Definition: widget.hpp:329
std::vector< std::unique_ptr< UI::Widget > > children
Definition: widget.hpp:315
std::function< UI::Tooltip *()> tooltip_creator
Definition: widget.hpp:344
size_t width
Definition: widget.hpp:325
virtual ~Widget()
Definition: widget.cpp:83
virtual void on_render(Context &, Eng3D::Rect viewport)
Definition: widget.cpp:229
UI::Align text_align_x
Definition: widget.hpp:331
std::shared_ptr< Eng3D::Texture > current_texture
Definition: widget.hpp:327
Eng3D::Color text_color
Definition: widget.hpp:332
bool have_shadow
Definition: widget.hpp:317
glm::ivec2 padding
Definition: widget.hpp:323
glm::ivec2 get_y_bounds() const
Obtains the height of the top and bottom overflow in widget That is the space that the children of th...
Definition: widget.cpp:483
Widget()=default
std::shared_ptr< Eng3D::TrueType::Font > font
Definition: widget.hpp:333
virtual void set_text(const std::string &text)
Generates text for the widget and overrides the current text texture.
Definition: widget.cpp:445
size_t height
Definition: widget.hpp:325
uint32_t is_hover
Definition: widget.hpp:308
UI::Tooltip * tooltip
Definition: widget.hpp:343
UI::WidgetType type
Definition: widget.hpp:320
UI::Widget * parent
Definition: widget.hpp:314
WidgetType
The type of the widget, some widgets share types between them to keep simplicity.
Definition: widget.hpp:76
Context * g_ui_context
Definition: ui.cpp:63
float g
Definition: color.hpp:33
float b
Definition: color.hpp:33
float r
Definition: color.hpp:33
float a
Definition: color.hpp:34
void draw(int instances=0) const
Definition: mesh.hpp:205
constexpr glm::vec2 size() const
Obtains the current size of the rectangle.
Definition: rectangle.hpp:68
Range< It > reverse(ORange &&originalRange)
Definition: utils.hpp:115