TGUI 1.11
Loading...
Searching...
No Matches
Components.hpp
1
2//
3// TGUI - Texus' Graphical User Interface
4// Copyright (C) 2012-2025 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
68 {
69 None,
70 Top,
71 Left,
72 Right,
73 Bottom,
74 Leftmost,
75 Rightmost,
76 Fill
77 };
78
80
82 enum class PositionAlignment
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 std::uint16_t 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 std::uint16_t 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 else
308 {
309 // We don't have any relevant values, so return the default value. It is possible to pass here while storedStates > 0 when
310 // there is e.g. only a value for the Disabled state and the widget is enabled.
311 return m_defaultValue;
312 }
313 }
314
315 TGUI_NODISCARD std::uint64_t connectCallback(std::function<void()> func)
316 {
317 return MessageBroker::subscribe(m_messageTopicId, std::move(func));
318 }
319
320 void disconnectCallback(std::uint64_t id)
321 {
322 return MessageBroker::unsubscribe(id);
323 }
324
325 private:
326
327 void unsetValueImpl()
328 {
329 const std::uint64_t baseIndex = m_propertyData & 0xFFFFFFFFFFFF0000;
330 const std::uint16_t storedStates = static_cast<std::uint16_t>(m_propertyData & 0xFFFF);
331
332 std::uint16_t total = 0;
333 std::uint8_t bitIndex = 0;
334 while (total < storedStates)
335 {
336 if (storedStates & (1 << bitIndex))
337 {
338 m_globalValues.erase(baseIndex + bitIndex);
339 total += static_cast<std::uint16_t>(1 << bitIndex);
340 }
341 ++bitIndex;
342 }
343
344 m_propertyData = baseIndex; // Forget about the states that were stored
345 }
346
347 private:
348
349 ValueType m_defaultValue;
350
351 // The highest 48 bits store the index in a static map of values (see below).
352 // The next 16 bits are used to indicate the set of states that are present for this property.
353 std::uint64_t m_propertyData = 0;
354
355 // Index of the event that we publish to when the property changes.
356 std::uint64_t m_messageTopicId = 0;
357
358 // All values are stored in a static map, which can be seen as a very large sparse list.
359 // Each instance holds a unique 48-bit id that can be seen as the high bits of the index in the list.
360 // The lowest 4 bits of the index contain the widget state. The remaining 12 bits inbetween are always 0.
363
366 std::unordered_map<std::uint64_t, ValueType> m_globalValues;
367 };
368
370
371 struct TGUI_API StylePropertyBackground
372 {
373 StyleProperty<Color> borderColor{Color::Black};
374 StyleProperty<Color> color{Color::White};
375 StyleProperty<Texture> texture;
376 StyleProperty<Outline> borders;
377 StyleProperty<Outline> padding;
378
379 float roundedBorderRadius = 0;
380 };
381
383
384 struct TGUI_API StylePropertyText
385 {
386 StyleProperty<Color> color{Color::Black};
387 StyleProperty<TextStyles> style;
388 };
389
391
392 class GroupComponent;
393
394 class TGUI_API Component
395 {
396 public:
397
398 Component() = default;
399 virtual ~Component() = default;
400
401 Component(const Component&);
402 Component& operator=(const Component&);
403
404 Component(Component&&) = default;
405 Component& operator=(Component&&) = default;
406
407 void setPosition(Vector2f position);
408
409 TGUI_NODISCARD Vector2f getPosition() const;
410
411 TGUI_NODISCARD Vector2f getSize() const;
412
413 void setPositionAlignment(PositionAlignment alignment);
414
415 void setVisible(bool visible);
416
417 TGUI_NODISCARD bool isVisible() const;
418
419 void setParent(GroupComponent* parent);
420
421 virtual void draw(BackendRenderTarget& target, RenderStates states) const = 0;
422
423 virtual void updateLayout();
424
425 TGUI_NODISCARD virtual std::shared_ptr<Component> clone() const = 0;
426
427 protected:
428
429 friend void swap(Component& first, Component& second);
430
431 protected:
432
433 ComponentState m_state = ComponentState::Normal;
434 PositionAlignment m_positionAlignment = PositionAlignment::None;
435 Vector2f m_position;
436 Vector2f m_size;
437 bool m_visible = true;
438 float m_opacity = 1;
439 GroupComponent* m_parent = nullptr;
440 };
441
442 class TGUI_API GroupComponent : public Component
443 {
444 public:
445
446 GroupComponent(const GroupComponent&);
447 GroupComponent& operator=(const GroupComponent&);
448
449 GroupComponent(GroupComponent&&) = default;
450 GroupComponent& operator=(GroupComponent&&) = default;
451
452 TGUI_NODISCARD Vector2f getClientSize() const;
453
454 void addComponent(const std::shared_ptr<Component>& component);
455
456 TGUI_NODISCARD const std::vector<std::shared_ptr<Component>>& getComponents() const;
457
458 void draw(BackendRenderTarget& target, RenderStates states) const override;
459
460 void updateLayout() override;
461
462 TGUI_NODISCARD std::shared_ptr<Component> clone() const override;
463
464 friend void swap(GroupComponent& first, GroupComponent& second);
465
466 protected:
467
468 GroupComponent() = default;
469
470 protected:
471 std::vector<std::shared_ptr<Component>> m_children;
472 Vector2f m_clientSize;
473 };
474
476
477 class TGUI_API BackgroundComponent : public GroupComponent
478 {
479 public:
480
481 BackgroundComponent(StylePropertyBackground* backgroundStyle);
482
483 ~BackgroundComponent() override;
484
485 BackgroundComponent(const BackgroundComponent& other, StylePropertyBackground* backgroundStyle = nullptr);
486 BackgroundComponent& operator=(const BackgroundComponent& other);
487
488 void init();
489
490 void setSize(Vector2f size);
491
492 void setBorders(const Outline& border);
493
494 TGUI_NODISCARD const Outline& getBorders() const;
495
496 void setPadding(const Outline& padding);
497
498 TGUI_NODISCARD const Outline& getPadding() const;
499
500 void setOpacity(float opacity);
501
502 void setComponentState(ComponentState state);
503
504 TGUI_NODISCARD bool isTransparentPixel(Vector2f pos, bool transparentTexture) const;
505
506 void draw(BackendRenderTarget& target, RenderStates states) const override;
507
508 TGUI_NODISCARD Vector2f getSizeWithoutBorders() const;
509
510 void updateLayout() override;
511
512 TGUI_NODISCARD std::shared_ptr<Component> clone() const override;
513
514 private:
515
516 struct ColorRect
517 {
518 Color color;
519 FloatRect rect;
520 };
521
522 StylePropertyBackground* m_backgroundStyle;
523
524 ColorRect m_background{Color::White, {}};
525 Color m_borderColor = Color::Black;
526 Sprite m_sprite;
527 Outline m_borders;
528 Outline m_padding;
529
530 std::uint64_t m_borderColorCallbackId = 0;
531 std::uint64_t m_backgroundColorCallbackId = 0;
532 std::uint64_t m_textureCallbackId = 0;
533 std::uint64_t m_bordersCallbackId = 0;
534 std::uint64_t m_paddingCallbackId = 0;
535 };
536
538
539 class TGUI_API TextComponent : public Component
540 {
541 public:
542
543 TextComponent(StylePropertyText* textStyle);
544
545 ~TextComponent() override;
546
547 TextComponent(const TextComponent& other, StylePropertyText* textStyle = nullptr);
548 TextComponent& operator=(const TextComponent& other);
549
550 void init();
551
552 void setString(const String& caption);
553
554 TGUI_NODISCARD const String& getString() const;
555
556 void setCharacterSize(unsigned int size);
557
558 TGUI_NODISCARD unsigned int getCharacterSize() const;
559
560 void setFont(const Font& font);
561
562 TGUI_NODISCARD Font getFont() const;
563
564 void setOutlineColor(Color color);
565
566 TGUI_NODISCARD Color getOutlineColor() const;
567
568 void setOutlineThickness(float thickness);
569
570 TGUI_NODISCARD float getOutlineThickness() const;
571
572 TGUI_NODISCARD float getLineHeight() const;
573
574 void setOpacity(float opacity);
575
576 void updateLayout() override;
577
578 void setComponentState(ComponentState state);
579
580 void draw(BackendRenderTarget& target, RenderStates states) const override;
581
582 TGUI_NODISCARD std::shared_ptr<Component> clone() const override;
583
584 private:
585 Text m_text;
586 StylePropertyText* m_textStyle;
587
588 Color m_color = Color::Black;
589 TextStyles m_style = TextStyle::Regular;
590
591 std::uint64_t m_colorCallbackId = 0;
592 std::uint64_t m_styleCallbackId = 0;
593 };
594
596
597 class TGUI_API ImageComponent : public Component
598 {
599 public:
600
601 ImageComponent(StyleProperty<Texture>* textureStyle);
602
603 ~ImageComponent() override;
604
605 ImageComponent(const ImageComponent& other, StyleProperty<Texture>* textureStyle = nullptr);
606 ImageComponent& operator=(const ImageComponent& other);
607
608 void init();
609
610 void setSize(Vector2f size);
611
612 void setOpacity(float opacity);
613
614 void setComponentState(ComponentState state);
615
616 TGUI_NODISCARD bool isTransparentPixel(Vector2f pos, bool transparentTexture) const;
617
618 void draw(BackendRenderTarget& target, RenderStates states) const override;
619
620 TGUI_NODISCARD std::shared_ptr<Component> clone() const override;
621
622 private:
623
624 StyleProperty<Texture>* m_textureStyle;
625 Sprite m_sprite;
626
627 std::uint64_t m_textureCallbackId = 0;
628 };
629
631
632 TGUI_NODISCARD inline ComponentState getStateFromFlags(bool hover, bool active, bool focused = false, bool enabled = true)
633 {
634 if (!enabled)
635 {
636 if (active)
637 return ComponentState::DisabledActive;
638 else
639 return ComponentState::Disabled;
640 }
641 else if (focused)
642 {
643 if (active)
644 {
645 if (hover)
646 return ComponentState::FocusedActiveHover;
647 else
648 return ComponentState::FocusedActive;
649 }
650 else if (hover)
651 return ComponentState::FocusedHover;
652 else
653 return ComponentState::Focused;
654 }
655 else if (active)
656 {
657 if (hover)
658 return ComponentState::ActiveHover;
659 else
660 return ComponentState::Active;
661 }
662 else if (hover)
663 return ComponentState::Hover;
664 else
665 return ComponentState::Normal;
666 }
667
669
670 inline void setOptionalPropertyValue(StyleProperty<Color>& property, const Color& color, ComponentState state)
671 {
672 if (color.isSet())
673 property.setValue(color, state);
674 else
675 property.unsetValue(state);
676 }
677
679
680 inline void setOptionalPropertyValue(StyleProperty<TextStyles>& property, const TextStyles& style, ComponentState state)
681 {
682 if (style.isSet())
683 property.setValue(style, state);
684 else
685 property.unsetValue(state);
686 }
687
689
690 inline void setOptionalPropertyValue(StyleProperty<Texture>& property, const Texture& texture, ComponentState state)
691 {
692 if (texture.getData())
693 property.setValue(texture, state);
694 else
695 property.unsetValue(state);
696 }
697
699
700} // namespace dev
701} // namespace priv
702} // namespace tgui
703
705
706#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