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