1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "GUI/BsGUIElement.h"
4#include "GUI/BsGUIWidget.h"
5#include "GUI/BsGUISkin.h"
6#include "GUI/BsGUIManager.h"
7#include "BsGUINavGroup.h"
8
9namespace bs
10{
11 const Color GUIElement::DISABLED_COLOR = Color(0.5f, 0.5f, 0.5f, 1.0f);
12
13 GUIElement::GUIElement(String styleName, const GUIDimensions& dimensions, GUIElementOptions options)
14 :GUIElementBase(dimensions), mOptionFlags(options), mStyle(&GUISkin::DefaultStyle), mStyleName(std::move(styleName))
15 {
16 // Style is set to default here, and the proper one is assigned once GUI element
17 // is assigned to a parent (that's when the active GUI skin becomes known)
18 }
19
20 void GUIElement::_updateRenderElements()
21 {
22 updateRenderElementsInternal();
23 }
24
25 void GUIElement::updateRenderElementsInternal()
26 {
27 updateClippedBounds();
28 }
29
30 void GUIElement::updateClippedBounds()
31 {
32 mClippedBounds = mLayoutData.area;
33 mClippedBounds.clip(mLayoutData.clipRect);
34 }
35
36 void GUIElement::setStyle(const String& styleName)
37 {
38 mStyleName = styleName;
39 _refreshStyle();
40 }
41
42 bool GUIElement::_mouseEvent(const GUIMouseEvent& ev)
43 {
44 return false;
45 }
46
47 bool GUIElement::_textInputEvent(const GUITextInputEvent& ev)
48 {
49 return false;
50 }
51
52 bool GUIElement::_commandEvent(const GUICommandEvent& ev)
53 {
54 if (ev.getType() == GUICommandEventType::FocusGained)
55 {
56 onFocusChanged(true);
57 return !mOptionFlags.isSet(GUIElementOption::ClickThrough);
58 }
59 else if (ev.getType() == GUICommandEventType::FocusLost)
60 {
61 onFocusChanged(false);
62 return !mOptionFlags.isSet(GUIElementOption::ClickThrough);
63 }
64
65 return false;
66 }
67
68 bool GUIElement::_virtualButtonEvent(const GUIVirtualButtonEvent& ev)
69 {
70 return false;
71 }
72
73 void GUIElement::setTint(const Color& color)
74 {
75 mColor = color;
76
77 _markContentAsDirty();
78 }
79
80 void GUIElement::_setElementDepth(UINT8 depth)
81 {
82 mLayoutData.depth = depth | (mLayoutData.depth & 0xFFFFFF00);
83 _markMeshAsDirty();
84 }
85
86 UINT8 GUIElement::_getElementDepth() const
87 {
88 return mLayoutData.depth & 0xFF;
89 }
90
91 void GUIElement::_setLayoutData(const GUILayoutData& data)
92 {
93 // Preserve element depth as that is not controlled by layout but is stored
94 // there only for convenience
95 UINT8 elemDepth = _getElementDepth();
96 GUIElementBase::_setLayoutData(data);
97 _setElementDepth(elemDepth);
98
99 updateClippedBounds();
100 }
101
102 void GUIElement::_changeParentWidget(GUIWidget* widget)
103 {
104 if (_isDestroyed())
105 return;
106
107 bool widgetChanged = false;
108 if(mParentWidget != widget)
109 {
110 // Unregister from current widget's nav-group
111 if(!mNavGroup && mParentWidget)
112 mParentWidget->_getDefaultNavGroup()->unregisterElement(this);
113
114 widgetChanged = true;
115 }
116
117 GUIElementBase::_changeParentWidget(widget);
118
119 if(widgetChanged)
120 {
121 // Register with the new widget's nav-group
122 if(!mNavGroup && mParentWidget)
123 mParentWidget->_getDefaultNavGroup()->registerElement(this);
124
125 _refreshStyle();
126 }
127 }
128
129 const RectOffset& GUIElement::_getPadding() const
130 {
131 if(mStyle != nullptr)
132 return mStyle->padding;
133 else
134 {
135 static RectOffset padding;
136
137 return padding;
138 }
139 }
140
141 void GUIElement::setNavGroup(const SPtr<GUINavGroup>& navGroup)
142 {
143 SPtr<GUINavGroup> currentNavGroup = _getNavGroup();
144 if(currentNavGroup == navGroup)
145 return;
146
147 if(currentNavGroup)
148 currentNavGroup->unregisterElement(this);
149
150 if(navGroup)
151 navGroup->registerElement(this);
152
153 mNavGroup = navGroup;
154 }
155
156 void GUIElement::setNavGroupIndex(INT32 index)
157 {
158 SPtr<GUINavGroup> navGroup = _getNavGroup();
159 if(navGroup != nullptr)
160 navGroup->setIndex(this, index);
161 }
162
163 SPtr<GUINavGroup> GUIElement::_getNavGroup() const
164 {
165 if(mNavGroup)
166 return mNavGroup;
167
168 if(mParentWidget)
169 return mParentWidget->_getDefaultNavGroup();
170
171 return nullptr;
172 }
173
174 void GUIElement::setFocus(bool enabled, bool clear)
175 {
176 GUIManager::instance().setFocus(this, enabled, clear);
177 }
178
179 void GUIElement::resetDimensions()
180 {
181 mDimensions = GUIDimensions::create();
182 mDimensions.updateWithStyle(mStyle);
183
184 _markLayoutAsDirty();
185 }
186
187 Rect2I GUIElement::getCachedVisibleBounds() const
188 {
189 Rect2I bounds = _getClippedBounds();
190
191 bounds.x += mStyle->margins.left;
192 bounds.y += mStyle->margins.top;
193 bounds.width = (UINT32)std::max(0, (INT32)bounds.width - (INT32)(mStyle->margins.left + mStyle->margins.right));
194 bounds.height = (UINT32)std::max(0, (INT32)bounds.height - (INT32)(mStyle->margins.top + mStyle->margins.bottom));
195
196 return bounds;
197 }
198
199 Rect2I GUIElement::getCachedContentBounds() const
200 {
201 Rect2I bounds;
202
203 bounds.x = mLayoutData.area.x + mStyle->margins.left + mStyle->contentOffset.left;
204 bounds.y = mLayoutData.area.y + mStyle->margins.top + mStyle->contentOffset.top;
205 bounds.width = (UINT32)std::max(0, (INT32)mLayoutData.area.width -
206 (INT32)(mStyle->margins.left + mStyle->margins.right + mStyle->contentOffset.left + mStyle->contentOffset.right));
207 bounds.height = (UINT32)std::max(0, (INT32)mLayoutData.area.height -
208 (INT32)(mStyle->margins.top + mStyle->margins.bottom + mStyle->contentOffset.top + mStyle->contentOffset.bottom));
209
210 return bounds;
211 }
212
213 Rect2I GUIElement::getCachedContentClipRect() const
214 {
215 Rect2I contentBounds = getCachedContentBounds();
216
217 // Transform into element space so we can clip it using the element clip rectangle
218 Vector2I offsetDiff = Vector2I(contentBounds.x - mLayoutData.area.x, contentBounds.y - mLayoutData.area.y);
219 Rect2I contentClipRect(offsetDiff.x, offsetDiff.y, contentBounds.width, contentBounds.height);
220 contentClipRect.clip(mLayoutData.getLocalClipRect());
221
222 // Transform into content sprite space
223 contentClipRect.x -= offsetDiff.x;
224 contentClipRect.y -= offsetDiff.y;
225
226 return contentClipRect;
227 }
228
229 Color GUIElement::getTint() const
230 {
231 if (!_isDisabled())
232 return mColor;
233
234 return mColor * DISABLED_COLOR;
235 }
236
237 bool GUIElement::_isInBounds(const Vector2I position) const
238 {
239 Rect2I contentBounds = getCachedVisibleBounds();
240
241 return contentBounds.contains(position);
242 }
243
244 SPtr<GUIContextMenu> GUIElement::_getContextMenu() const
245 {
246 if (!_isDisabled())
247 return mContextMenu;
248
249 return nullptr;
250 }
251
252 void GUIElement::_refreshStyle()
253 {
254 const GUIElementStyle* newStyle = nullptr;
255 if(_getParentWidget() != nullptr && !mStyleName.empty())
256 newStyle = _getParentWidget()->getSkin().getStyle(mStyleName);
257 else
258 newStyle = &GUISkin::DefaultStyle;
259
260 if(newStyle != mStyle)
261 {
262 mStyle = newStyle;
263 mDimensions.updateWithStyle(mStyle);
264 styleUpdated();
265
266 _markLayoutAsDirty();
267 }
268 }
269
270 const String& GUIElement::getSubStyleName(const String& subStyleTypeName) const
271 {
272 auto iterFind = mStyle->subStyles.find(subStyleTypeName);
273
274 if (iterFind != mStyle->subStyles.end())
275 return iterFind->second;
276 else
277 return StringUtil::BLANK;
278 }
279
280 void GUIElement::destroy(GUIElement* element)
281 {
282 if(element->mIsDestroyed)
283 return;
284
285 SPtr<GUINavGroup> currentNavGroup = element->_getNavGroup();
286 if(currentNavGroup)
287 currentNavGroup->unregisterElement(element);
288
289 if (element->mParentElement != nullptr)
290 element->mParentElement->_unregisterChildElement(element);
291
292 element->mIsDestroyed = true;
293
294 GUIManager::instance().queueForDestroy(element);
295 }
296
297 Rect2I GUIElement::getVisibleBounds()
298 {
299 Rect2I bounds = getBounds();
300
301 bounds.x += mStyle->margins.left;
302 bounds.y += mStyle->margins.top;
303 bounds.width = (UINT32)std::max(0, (INT32)bounds.width - (INT32)(mStyle->margins.left + mStyle->margins.right));
304 bounds.height = (UINT32)std::max(0, (INT32)bounds.height - (INT32)(mStyle->margins.top + mStyle->margins.bottom));
305
306 return bounds;
307 }
308}
309