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 | |
9 | namespace 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::() 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 | |