| 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 | |