TGUI  0.10-beta
Components.hpp
1
2//
3// TGUI - Texus' Graphical User Interface
4// Copyright (C) 2012-2022 Bruno Van de Velde (vdv_b@tgui.eu)
5//
6// This software is provided 'as-is', without any express or implied warranty.
7// In no event will the authors be held liable for any damages arising from the use of this software.
8//
9// Permission is granted to anyone to use this software for any purpose,
10// including commercial applications, and to alter it and redistribute it freely,
11// subject to the following restrictions:
12//
13// 1. The origin of this software must not be misrepresented;
14// you must not claim that you wrote the original software.
15// If you use this software in a product, an acknowledgment
16// in the product documentation would be appreciated but is not required.
17//
18// 2. Altered source versions must be plainly marked as such,
19// and must not be misrepresented as being the original software.
20//
21// 3. This notice may not be removed or altered from any source distribution.
22//
24
25
26#ifndef TGUI_COMPONENTS_HPP
27#define TGUI_COMPONENTS_HPP
28
29#include <TGUI/Text.hpp>
30#include <TGUI/Sprite.hpp>
31#include <TGUI/Texture.hpp>
32#include <TGUI/Outline.hpp>
33
34#include <unordered_map>
35#include <memory>
36#include <set>
37
39
40namespace tgui
41{
42 class BackendRenderTarget;
43
44namespace priv
45{
46namespace dev
47{
49
51 enum class ComponentState : std::uint8_t
52 {
53 Normal = 0,
54 Hover = 1,
55 Active = 2,
56 ActiveHover = 3,
57 Focused = 4,
58 FocusedHover = 5,
59 FocusedActive = 6,
60 FocusedActiveHover = 7,
61 Disabled = 8,
62 DisabledActive = 10
63 };
64
66
68 enum class AlignLayout
69 {
70 None,
71 Top,
72 Left,
73 Right,
74 Bottom,
75 Leftmost,
76 Rightmost,
77 Fill
78 };
79
81
83 enum class PositionAlignment
84 {
85 None,
86 TopLeft,
87 Top,
88 TopRight,
89 Right,
90 BottomRight,
91 Bottom,
92 BottomLeft,
93 Left,
94 Center
95 };
96
98
99 class TGUI_API MessageBroker
100 {
101 public:
102
103 static std::uint64_t createTopic();
104
105 static void destroyTopic(std::uint64_t topicId);
106
107 static std::uint64_t subscribe(std::uint64_t topicId, std::function<void()> func);
108
109 static void unsubscribe(std::uint64_t callbackId);
110
111 static void sendEvent(std::uint64_t topicId);
112
113 private:
114 static std::unordered_map<std::uint64_t, std::set<std::uint64_t>> m_topicIdToCallbackIds;
115 static std::unordered_map<std::uint64_t, std::uint64_t> m_callbackIdToTopicId;
116 static std::unordered_map<std::uint64_t, std::function<void()>> m_listeners; // CallbackId -> Func
117
118 // All topic and callback ids are unique and non-overlapping
119 static std::uint64_t m_lastId;
120 };
121
123
124 class TGUI_API StylePropertyBase
125 {
126 public:
127 virtual ~StylePropertyBase() = default;
128 };
129
131
132 template <typename ValueType>
133 class TGUI_API StyleProperty : public StylePropertyBase
134 {
135 public:
136
137 StyleProperty() :
138 m_defaultValue {},
139 m_propertyData {0},
140 m_messageTopicId{MessageBroker::createTopic()}
141 {
142 }
143
144 explicit StyleProperty(const ValueType& defaultValue) :
145 m_defaultValue {defaultValue},
146 m_propertyData {0},
147 m_messageTopicId{MessageBroker::createTopic()}
148 {
149 }
150
151 StyleProperty(const StyleProperty& other) :
152 m_defaultValue {other.m_defaultValue},
153 m_propertyData {0},
154 m_messageTopicId{MessageBroker::createTopic()},
155 m_globalValues {other.m_globalValues}
156 {
157 unsetValue();
158
159 const std::uint64_t baseIndex = m_propertyData & 0xFFFFFFFFFFFF0000;
160 const std::uint64_t oldBaseIndex = other.m_propertyData & 0xFFFFFFFFFFFF0000;
161 const std::uint16_t oldStoredStates = static_cast<std::uint16_t>(other.m_propertyData & 0xFFFF);
162
163 std::uint16_t total = 0;
164 std::uint8_t bitIndex = 0;
165 while (total < oldStoredStates)
166 {
167 if (oldStoredStates & (1 << bitIndex))
168 {
169 m_globalValues[baseIndex + bitIndex] = m_globalValues[oldBaseIndex + bitIndex];
170 total += (1 << bitIndex);
171 }
172 ++bitIndex;
173 }
174
175 m_propertyData = baseIndex | oldStoredStates;
176 }
177
178 StyleProperty(StyleProperty&& other) :
179 m_defaultValue {std::move(other.m_defaultValue)},
180 m_propertyData {std::move(other.m_propertyData)},
181 m_messageTopicId{std::move(other.m_messageTopicId)},
182 m_globalValues {std::move(other.m_globalValues)}
183 {
184 other.m_messageTopicId = 0;
185 }
186
187 ~StyleProperty()
188 {
189 if (m_messageTopicId) // Can be 0 on moved object
190 MessageBroker::destroyTopic(m_messageTopicId);
191 unsetValueImpl();
192 }
193
194 StyleProperty& operator=(const StyleProperty& other)
195 {
196 if (&other != this)
197 {
198 StyleProperty temp(other);
199 std::swap(m_defaultValue, temp.m_defaultValue);
200 std::swap(m_propertyData, temp.m_propertyData);
201 std::swap(m_messageTopicId, temp.m_messageTopicId);
202 std::swap(m_globalValues, temp.m_globalValues);
203 }
204
205 return *this;
206 }
207
208 StyleProperty& operator=(StyleProperty&& other)
209 {
210 if (&other != this)
211 {
212 m_defaultValue = std::move(other.m_defaultValue);
213 m_propertyData = std::move(other.m_propertyData);
214 m_messageTopicId = std::move(other.m_messageTopicId);
215 m_globalValues = std::move(other.m_globalValues);
216
217 other.m_messageTopicId = 0;
218 }
219
220 return *this;
221 }
222
223 StyleProperty& operator=(const ValueType& value)
224 {
225 unsetValueImpl();
226 setValue(value, ComponentState::Normal);
227 return *this;
228 }
229
230 void setValue(const ValueType& value, ComponentState state = ComponentState::Normal)
231 {
232 const std::uint64_t baseIndex = m_propertyData & 0xFFFFFFFFFFFF0000;
233 m_propertyData |= static_cast<std::uint64_t>(1) << static_cast<std::uint8_t>(state);
234 m_globalValues[baseIndex + static_cast<std::uint8_t>(state)] = value;
235
236 MessageBroker::sendEvent(m_messageTopicId);
237 }
238
239 void unsetValue(ComponentState state)
240 {
241 const std::uint64_t baseIndex = m_propertyData & 0xFFFFFFFFFFFF0000;
242 m_propertyData &= ~(1 << static_cast<std::uint8_t>(state));
243 m_globalValues.erase(baseIndex + static_cast<std::uint8_t>(state));
244
245 MessageBroker::sendEvent(m_messageTopicId);
246 }
247
248 void unsetValue()
249 {
250 unsetValueImpl();
251 MessageBroker::sendEvent(m_messageTopicId);
252 }
253
254 const ValueType& getValue(ComponentState state = ComponentState::Normal) const
255 {
256 const std::uint64_t baseIndex = m_propertyData & 0xFFFFFFFFFFFF0000;
257 const std::uint16_t storedStates = static_cast<std::uint16_t>(m_propertyData & 0xFFFF);
258
259 // If we don't have a value for any state then we can just return the default value
260 if (storedStates == 0)
261 return m_defaultValue;
262
263 // If we only have a value for the Normal state then always use this value
264 if (storedStates == 1)
265 return m_globalValues.at(baseIndex);
266
267 if (static_cast<std::uint8_t>(state) & static_cast<std::uint8_t>(ComponentState::Disabled))
268 {
269 if ((static_cast<std::uint8_t>(state) & static_cast<std::uint8_t>(ComponentState::Active)) && (storedStates & (1 << static_cast<std::uint8_t>(ComponentState::DisabledActive))))
270 return m_globalValues.at(baseIndex + static_cast<std::uint8_t>(ComponentState::DisabledActive));
271 if (storedStates & (1 << static_cast<std::uint8_t>(ComponentState::Disabled)))
272 return m_globalValues.at(baseIndex + static_cast<std::uint8_t>(ComponentState::Disabled));
273 }
274
275 if (static_cast<std::uint8_t>(state) & static_cast<std::uint8_t>(ComponentState::Active))
276 {
277 if (static_cast<std::uint8_t>(state) & static_cast<std::uint8_t>(ComponentState::Hover))
278 {
279 if ((static_cast<std::uint8_t>(state) & static_cast<std::uint8_t>(ComponentState::Focused)) && (storedStates & (1 << static_cast<std::uint8_t>(ComponentState::FocusedActiveHover))))
280 return m_globalValues.at(baseIndex + static_cast<std::uint8_t>(ComponentState::FocusedActiveHover));
281 if (storedStates & (1 << static_cast<std::uint8_t>(ComponentState::ActiveHover)))
282 return m_globalValues.at(baseIndex + static_cast<std::uint8_t>(ComponentState::ActiveHover));
283 }
284
285 if ((static_cast<std::uint8_t>(state) & static_cast<std::uint8_t>(ComponentState::Focused)) && (storedStates & (1 << static_cast<std::uint8_t>(ComponentState::FocusedActive))))
286 return m_globalValues.at(baseIndex + static_cast<std::uint8_t>(ComponentState::FocusedActive));
287 if (storedStates & (1 << static_cast<std::uint8_t>(ComponentState::Active)))
288 return m_globalValues.at(baseIndex + static_cast<std::uint8_t>(ComponentState::Active));
289 }
290
291 if (static_cast<std::uint8_t>(state) & static_cast<std::uint8_t>(ComponentState::Hover))
292 {
293 if ((static_cast<std::uint8_t>(state) & static_cast<std::uint8_t>(ComponentState::Focused)) && (storedStates & (1 << static_cast<std::uint8_t>(ComponentState::FocusedHover))))
294 return m_globalValues.at(baseIndex + static_cast<std::uint8_t>(ComponentState::FocusedHover));
295 if (storedStates & (1 << static_cast<std::uint8_t>(ComponentState::Hover)))
296 return m_globalValues.at(baseIndex + static_cast<std::uint8_t>(ComponentState::Hover));
297 }
298
299 if (static_cast<std::uint8_t>(state) & static_cast<std::uint8_t>(ComponentState::Focused))
300 {
301 if (storedStates & (1 << static_cast<std::uint8_t>(ComponentState::Focused)))
302 return m_globalValues.at(baseIndex + static_cast<std::uint8_t>(ComponentState::Focused));
303 }
304
305 if (storedStates & 1)
306 {
307 // We have a value for the Normal state, so return it. It is possible to pass here while storedStates != 1 when there
308 // is e.g. a value for both Normal and Disabled state and the widget is enabled.
309 return m_globalValues.at(baseIndex + static_cast<std::uint8_t>(ComponentState::Normal));
310 }
311 else
312 {
313 // We don't have any relevant values, so return the default value. It is possible to pass here while storedStates > 0 when
314 // there is e.g. only a value for the Disabled state and the widget is enabled.
315 return m_defaultValue;
316 }
317 }
318
319 std::uint64_t connectCallback(std::function<void()> func)
320 {
321 return MessageBroker::subscribe(m_messageTopicId, std::move(func));
322 }
323
324 void disconnectCallback(std::uint64_t id)
325 {
326 return MessageBroker::unsubscribe(id);
327 }
328
329 private:
330
331 void unsetValueImpl()
332 {
333 const std::uint64_t baseIndex = m_propertyData & 0xFFFFFFFFFFFF0000;
334 const std::uint16_t storedStates = static_cast<std::uint16_t>(m_propertyData & 0xFFFF);
335
336 std::uint16_t total = 0;
337 std::uint8_t bitIndex = 0;
338 while (total < storedStates)
339 {
340 if (storedStates & (1 << bitIndex))
341 {
342 m_globalValues.erase(baseIndex + bitIndex);
343 total += (1 << bitIndex);
344 }
345 ++bitIndex;
346 }
347
348 m_propertyData = baseIndex; // Forget about the states that were stored
349 }
350
351 private:
352
353 ValueType m_defaultValue;
354
355 // The highest 48 bits store the index in a static map of values (see below).
356 // The next 16 bits are used to indicate the set of states that are present for this property.
357 std::uint64_t m_propertyData = 0;
358
359 // Index of the event that we publish to when the property changes.
360 std::uint64_t m_messageTopicId = 0;
361
362 // All values are stored in a static map, which can be seen as a very large sparse list.
363 // Each instance holds a unique 48-bit id that can be seen as the high bits of the index in the list.
364 // The lowest 4 bits of the index contain the widget state. The remaining 12 bits inbetween are always 0.
367
370 std::unordered_map<std::uint64_t, ValueType> m_globalValues;
371 };
372
374
375 struct TGUI_API StylePropertyBackground
376 {
377 StyleProperty<Color> borderColor{Color::Black};
378 StyleProperty<Color> color{Color::White};
379 StyleProperty<Texture> texture;
380 StyleProperty<Outline> borders;
381 StyleProperty<Outline> padding;
382
383 float roundedBorderRadius = 0;
384 };
385
387
388 struct TGUI_API StylePropertyText
389 {
390 StyleProperty<Color> color{Color::Black};
391 StyleProperty<TextStyles> style;
392 };
393
395
396 class GroupComponent;
397
398 class TGUI_API Component
399 {
400 public:
401
402 Component(const Component&);
403 Component& operator=(const Component&);
404
405 Component(Component&&) = default;
406 Component& operator=(Component&&) = default;
407
408 void setPosition(Vector2f position);
409
410 Vector2f getPosition() const;
411
412 Vector2f getSize() const;
413
414 void setPositionAlignment(PositionAlignment alignment);
415
416 void setVisible(bool visible);
417
418 bool isVisible() const;
419
420 void setParent(GroupComponent* parent);
421
422 virtual void draw(BackendRenderTarget& target, RenderStates states) const = 0;
423
424 virtual void updateLayout();
425
426 virtual std::shared_ptr<Component> clone() const = 0;
427
428 protected:
429
430 Component() = default;
431
432 virtual ~Component() = default;
433
434 friend void swap(Component& first, Component& second);
435
436 protected:
437
438 ComponentState m_state = ComponentState::Normal;
439 PositionAlignment m_positionAlignment = PositionAlignment::None;
440 Vector2f m_position;
441 Vector2f m_size;
442 bool m_visible = true;
443 float m_opacity = 1;
444 GroupComponent* m_parent = nullptr;
445 };
446
447 class TGUI_API GroupComponent : public Component
448 {
449 public:
450
451 GroupComponent(const GroupComponent&);
452 GroupComponent& operator=(const GroupComponent&);
453
454 GroupComponent(GroupComponent&&) = default;
455 GroupComponent& operator=(GroupComponent&&) = default;
456
457 Vector2f getClientSize() const;
458
459 void addComponent(const std::shared_ptr<Component>& component);
460
461 const std::vector<std::shared_ptr<Component>>& getComponents() const;
462
463 void draw(BackendRenderTarget& target, RenderStates states) const override;
464
465 void updateLayout() override;
466
467 std::shared_ptr<Component> clone() const override;
468
469 friend void swap(GroupComponent& first, GroupComponent& second);
470
471 protected:
472
473 GroupComponent() = default;
474
475 protected:
476 std::vector<std::shared_ptr<Component>> m_children;
477 Vector2f m_clientSize;
478 };
479
481
482 class TGUI_API BackgroundComponent : public GroupComponent
483 {
484 public:
485
486 BackgroundComponent(StylePropertyBackground* backgroundStyle);
487
488 ~BackgroundComponent();
489
490 BackgroundComponent(const BackgroundComponent& other, StylePropertyBackground* backgroundStyle = nullptr);
491 BackgroundComponent& operator=(const BackgroundComponent& other);
492
493 void init();
494
495 void setSize(Vector2f size);
496
497 void setBorders(const Outline& border);
498
499 const Outline& getBorders() const;
500
501 void setPadding(const Outline& padding);
502
503 const Outline& getPadding() const;
504
505 void setOpacity(float opacity);
506
507 void setComponentState(ComponentState state);
508
509 bool isTransparentPixel(Vector2f pos, bool transparentTexture) const;
510
511 void draw(BackendRenderTarget& target, RenderStates states) const override;
512
513 Vector2f getSizeWithoutBorders() const;
514
515 void updateLayout() override;
516
517 std::shared_ptr<Component> clone() const override;
518
519 private:
520
521 struct ColorRect
522 {
523 Color color;
524 FloatRect rect;
525 };
526
527 StylePropertyBackground* m_backgroundStyle;
528
529 ColorRect m_background{Color::White, {}};
530 Color m_borderColor = Color::Black;
531 Sprite m_sprite;
532 Outline m_borders;
533 Outline m_padding;
534
535 std::uint64_t m_borderColorCallbackId = 0;
536 std::uint64_t m_backgroundColorCallbackId = 0;
537 std::uint64_t m_textureCallbackId = 0;
538 std::uint64_t m_bordersCallbackId = 0;
539 std::uint64_t m_paddingCallbackId = 0;
540 };
541
543
544 class TGUI_API TextComponent : public Component
545 {
546 public:
547
548 TextComponent(StylePropertyText* textStyle);
549
550 ~TextComponent();
551
552 TextComponent(const TextComponent& other, StylePropertyText* textStyle = nullptr);
553 TextComponent& operator=(const TextComponent& other);
554
555 void init();
556
557 void setString(const String& caption);
558
559 const String& getString() const;
560
561 void setCharacterSize(unsigned int size);
562
563 unsigned int getCharacterSize() const;
564
565 void setFont(Font font);
566
567 Font getFont() const;
568
569 void setOutlineColor(Color color);
570
571 Color getOutlineColor() const;
572
573 void setOutlineThickness(float thickness);
574
575 float getOutlineThickness() const;
576
577 float getLineHeight() const;
578
579 void setOpacity(float opacity);
580
581 void updateLayout() override;
582
583 void setComponentState(ComponentState state);
584
585 void draw(BackendRenderTarget& target, RenderStates states) const override;
586
587 std::shared_ptr<Component> clone() const override;
588
589 private:
590 Text m_text;
591 StylePropertyText* m_textStyle;
592
593 Color m_color = Color::Black;
594 TextStyles m_style = TextStyle::Regular;
595
596 std::uint64_t m_colorCallbackId = 0;
597 std::uint64_t m_styleCallbackId = 0;
598 };
599
601
602 class TGUI_API ImageComponent : public Component
603 {
604 public:
605
606 ImageComponent(StyleProperty<Texture>* textureStyle);
607
608 ~ImageComponent();
609
610 ImageComponent(const ImageComponent& other, StyleProperty<Texture>* textureStyle = nullptr);
611 ImageComponent& operator=(const ImageComponent& other);
612
613 void init();
614
615 void setSize(Vector2f size);
616
617 void setOpacity(float opacity);
618
619 void setComponentState(ComponentState state);
620
621 bool isTransparentPixel(Vector2f pos, bool transparentTexture) const;
622
623 void draw(BackendRenderTarget& target, RenderStates states) const override;
624
625 std::shared_ptr<Component> clone() const override;
626
627 private:
628
629 StyleProperty<Texture>* m_textureStyle;
630 Sprite m_sprite;
631
632 std::uint64_t m_textureCallbackId = 0;
633 };
634
636
637 inline ComponentState getStateFromFlags(bool hover, bool active, bool focused = false, bool enabled = true)
638 {
639 if (!enabled)
640 {
641 if (active)
642 return ComponentState::DisabledActive;
643 else
644 return ComponentState::Disabled;
645 }
646 else if (focused)
647 {
648 if (active)
649 {
650 if (hover)
651 return ComponentState::FocusedActiveHover;
652 else
653 return ComponentState::FocusedActive;
654 }
655 else if (hover)
656 return ComponentState::FocusedHover;
657 else
658 return ComponentState::Focused;
659 }
660 else if (active)
661 {
662 if (hover)
663 return ComponentState::ActiveHover;
664 else
665 return ComponentState::Active;
666 }
667 else if (hover)
668 return ComponentState::Hover;
669 else
670 return ComponentState::Normal;
671 }
672
674
675 inline void setOptionalPropertyValue(StyleProperty<Color>& property, const Color& color, ComponentState state)
676 {
677 if (color.isSet())
678 property.setValue(color, state);
679 else
680 property.unsetValue(state);
681 }
682
684
685 inline void setOptionalPropertyValue(StyleProperty<TextStyles>& property, const TextStyles& style, ComponentState state)
686 {
687 if (style.isSet())
688 property.setValue(style, state);
689 else
690 property.unsetValue(state);
691 }
692
694
695 inline void setOptionalPropertyValue(StyleProperty<Texture>& property, const Texture& texture, ComponentState state)
696 {
697 if (texture.getData())
698 property.setValue(texture, state);
699 else
700 property.unsetValue(state);
701 }
702
704
705} // namespace dev
706} // namespace priv
707} // namespace tgui
708
710
711#endif // TGUI_COMPONENTS_HPP
static const Color Black
Black predefined color.
Definition: Color.hpp:253
static const Color White
White predefined color.
Definition: Color.hpp:254
Namespace that contains all TGUI functions and classes.
Definition: AbsoluteOrRelativeValue.hpp:36