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