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