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