| 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 | #pragma once |
| 4 | |
| 5 | #include "BsPrerequisites.h" |
| 6 | #include "GUI/BsGUIElement.h" |
| 7 | #include "2D/BsImageSprite.h" |
| 8 | #include "2D/BsTextSprite.h" |
| 9 | #include "Input/BsVirtualInput.h" |
| 10 | |
| 11 | namespace bs |
| 12 | { |
| 13 | /** @addtogroup GUI |
| 14 | * @{ |
| 15 | */ |
| 16 | |
| 17 | /** |
| 18 | * Input box is a GUI element that accepts Unicode textual input. It can be single or multi-line and handles various |
| 19 | * types of text manipulation. |
| 20 | */ |
| 21 | class BS_EXPORT GUIInputBox : public GUIElement |
| 22 | { |
| 23 | /** Possible visual states the input box can be in. */ |
| 24 | enum class State |
| 25 | { |
| 26 | Normal, |
| 27 | Hover, |
| 28 | Focused |
| 29 | }; |
| 30 | |
| 31 | public: |
| 32 | /** Returns type name of the GUI element used for finding GUI element styles. */ |
| 33 | static const String& getGUITypeName(); |
| 34 | |
| 35 | /** |
| 36 | * Creates a new input box. |
| 37 | * |
| 38 | * @param[in] multiline If true the input box can be of arbitrary height and will accept multiple lines of |
| 39 | * text. |
| 40 | * @param[in] styleName Optional style to use for the element. Style will be retrieved from GUISkin of the |
| 41 | * GUIWidget the element is used on. If not specified default style for this element |
| 42 | * is used. |
| 43 | */ |
| 44 | static GUIInputBox* create(bool multiline = false, const String& styleName = StringUtil::BLANK); |
| 45 | |
| 46 | /** |
| 47 | * Creates a new input box. |
| 48 | * |
| 49 | * @param[in] multiline If true the input box can be of arbitrary height and will accept multiple lines of |
| 50 | * text. |
| 51 | * @param[in] options Options that allow you to control how is the element positioned and sized. This will |
| 52 | * override any similar options set by style. |
| 53 | * @param[in] styleName Optional style to use for the element. Style will be retrieved from GUISkin of the |
| 54 | * GUIWidget the element is used on. If not specified default button style is used. |
| 55 | */ |
| 56 | static GUIInputBox* create(bool multiline, const GUIOptions& options, const String& styleName = StringUtil::BLANK); |
| 57 | |
| 58 | |
| 59 | /** |
| 60 | * Creates a new single-line input box. |
| 61 | * |
| 62 | * @param[in] options Options that allow you to control how is the element positioned and sized. This will |
| 63 | * override any similar options set by style. |
| 64 | * @param[in] styleName Optional style to use for the element. Style will be retrieved from GUISkin of the |
| 65 | * GUIWidget the element is used on. If not specified default button style is used. |
| 66 | */ |
| 67 | static GUIInputBox* create(const GUIOptions& options, const String& styleName = StringUtil::BLANK); |
| 68 | |
| 69 | /** Returns the text currently entered in the input box. */ |
| 70 | const String& getText() const { return mText; } |
| 71 | |
| 72 | /** Sets the text inside the input box. This will replace any current text. */ |
| 73 | void setText(const String& text); |
| 74 | |
| 75 | /** |
| 76 | * Sets an optional filter that can control what is allowed to be entered into the input box. Filter should return |
| 77 | * true if the provided string is valid and false otherwise. Set the filter to null to deactivate filtering. |
| 78 | */ |
| 79 | void setFilter(std::function<bool(const String&)> filter) { mFilter = filter; } |
| 80 | |
| 81 | /** Triggered whenever input text has changed. */ |
| 82 | Event<void(const String&)> onValueChanged; |
| 83 | |
| 84 | /** Triggered when the user hits the Enter key with the input box in focus. */ |
| 85 | Event<void()> onConfirm; |
| 86 | |
| 87 | public: // ***** INTERNAL ****** |
| 88 | /** @name Internal |
| 89 | * @{ |
| 90 | */ |
| 91 | |
| 92 | /** @copydoc GUIElement::_getElementType */ |
| 93 | ElementType _getElementType() const override { return ElementType::InputBox; } |
| 94 | |
| 95 | /** @copydoc GUIElement::_getOptimalSize */ |
| 96 | Vector2I _getOptimalSize() const override; |
| 97 | |
| 98 | /** @} */ |
| 99 | protected: |
| 100 | GUIInputBox(const String& styleName, const GUIDimensions& dimensions, bool multiline); |
| 101 | virtual ~GUIInputBox(); |
| 102 | |
| 103 | /** @copydoc GUIElement::_getNumRenderElements() */ |
| 104 | UINT32 _getNumRenderElements() const override; |
| 105 | |
| 106 | /** @copydoc GUIElement::_getMaterial() */ |
| 107 | const SpriteMaterialInfo& _getMaterial(UINT32 renderElementIdx, SpriteMaterial** material) const override; |
| 108 | |
| 109 | /** @copydoc GUIElement::_getMeshInfo() */ |
| 110 | void _getMeshInfo(UINT32 renderElementIdx, UINT32& numVertices, UINT32& numIndices, GUIMeshType& type) const override; |
| 111 | |
| 112 | /** @copydoc GUIElement::_fillBuffer() */ |
| 113 | void _fillBuffer(UINT8* vertices, UINT32* indices, UINT32 vertexOffset, UINT32 indexOffset, |
| 114 | UINT32 maxNumVerts, UINT32 maxNumIndices, UINT32 renderElementIdx) const override; |
| 115 | |
| 116 | /** @copydoc GUIElement::updateRenderElementsInternal() */ |
| 117 | void updateRenderElementsInternal() override; |
| 118 | |
| 119 | /** @copydoc GUIElement::updateClippedBounds() */ |
| 120 | void updateClippedBounds() override; |
| 121 | |
| 122 | /** @copydoc GUIElement::_mouseEvent */ |
| 123 | bool _mouseEvent(const GUIMouseEvent& ev) override; |
| 124 | |
| 125 | /** @copydoc GUIElement::_textInputEvent */ |
| 126 | bool _textInputEvent(const GUITextInputEvent& ev) override; |
| 127 | |
| 128 | /** @copydoc GUIElement::_commandEvent */ |
| 129 | bool _commandEvent(const GUICommandEvent& ev) override; |
| 130 | |
| 131 | /** @copydoc GUIElement::_virtualButtonEvent */ |
| 132 | bool _virtualButtonEvent(const GUIVirtualButtonEvent& ev) override; |
| 133 | |
| 134 | /** |
| 135 | * Returns how much to offset text due to scrolling. |
| 136 | * |
| 137 | * @note |
| 138 | * This is used when text is larger than the input box itself. As the caret moves the text will scroll so that the |
| 139 | * caret remains visible, and how much scroll is applied is determined by this value. |
| 140 | */ |
| 141 | Vector2I _getTextInputOffset() const override; |
| 142 | |
| 143 | /** Returns rectangle in which the text can be displayed, in local coordinates (text will start at 0, 0). */ |
| 144 | Rect2I _getTextInputRect() const override; |
| 145 | |
| 146 | /** @copydoc GUIElement::_getRenderElementDepth */ |
| 147 | UINT32 _getRenderElementDepth(UINT32 renderElementIdx) const override; |
| 148 | |
| 149 | /** @copydoc GUIElement::_getRenderElementDepthRange */ |
| 150 | UINT32 _getRenderElementDepthRange() const override; |
| 151 | |
| 152 | /** @copydoc GUIElement::_hasCustomCursor */ |
| 153 | bool _hasCustomCursor(const Vector2I position, CursorType& type) const override; |
| 154 | |
| 155 | /** @copydoc GUIElement::_getContextMenu */ |
| 156 | SPtr<GUIContextMenu> () const override; |
| 157 | private: |
| 158 | /** |
| 159 | * Retrieves a sprite from a render element index, and a local render element index that represents render element |
| 160 | * within the returned sprite. |
| 161 | */ |
| 162 | Sprite* renderElemToSprite(UINT32 renderElemIdx, UINT32& localRenderElemIdx) const; |
| 163 | |
| 164 | /** |
| 165 | * Returns offset at which is the element with the provided render element index. Offset is relative to parent |
| 166 | * widget. |
| 167 | */ |
| 168 | Vector2I renderElemToOffset(UINT32 renderElemIdx) const; |
| 169 | |
| 170 | /** |
| 171 | * Returns a clip rectangle that can be used for clipping the render element with the provided index. Rectangle is |
| 172 | * in local coordiantes relative to element origin. |
| 173 | */ |
| 174 | Rect2I renderElemToClipRect(UINT32 renderElemIdx) const; |
| 175 | |
| 176 | /** Inserts a new string into the current text at the specified index. */ |
| 177 | void insertString(UINT32 charIdx, const String& string); |
| 178 | |
| 179 | /** Inserts a new character into the current text at the specified index. */ |
| 180 | void insertChar(UINT32 charIdx, UINT32 charCode); |
| 181 | |
| 182 | /** Erases a single character at the specified index. */ |
| 183 | void eraseChar(UINT32 charIdx); |
| 184 | |
| 185 | /** |
| 186 | * Deletes text that is currently selected. |
| 187 | * |
| 188 | * @param[in] internal If internal no filter will be applied after the text is deleted, and no event will be |
| 189 | * triggered either. |
| 190 | */ |
| 191 | void deleteSelectedText(bool internal = false); |
| 192 | |
| 193 | /** Returns currently selected text. */ |
| 194 | String getSelectedText(); |
| 195 | |
| 196 | /** Shows the input caret. You must position the caret manually after showing it. */ |
| 197 | void showCaret(); |
| 198 | |
| 199 | /** Hides the input caret. */ |
| 200 | void hideCaret(); |
| 201 | |
| 202 | /** |
| 203 | * Shows selection with the specified anchor position. You must position selection start and end before selection |
| 204 | * will actually render. Anchor position determines selection behavior as the user moves the selection with the |
| 205 | * keyboard. |
| 206 | */ |
| 207 | void showSelection(UINT32 anchorCaretPos); |
| 208 | |
| 209 | /** Removes any active selection. */ |
| 210 | void clearSelection(); |
| 211 | |
| 212 | /** Adjusts the text offset (scroll) so that the caret is visible. */ |
| 213 | void scrollTextToCaret(); |
| 214 | |
| 215 | /** Clamps the text offset (scroll) so that the text fits in the provided bounds nicely with minimal white space. */ |
| 216 | void clampScrollToBounds(Rect2I unclippedTextBounds); |
| 217 | |
| 218 | /** Returns offset at which to render the text. Relative to parent widget. */ |
| 219 | Vector2I getTextOffset() const; |
| 220 | |
| 221 | /** Returns rectangle used for clipping the text. Relative to element. */ |
| 222 | Rect2I getTextClipRect() const; |
| 223 | |
| 224 | /** Returns text sprite descriptor determining how is text sprite created. */ |
| 225 | TEXT_SPRITE_DESC getTextDesc() const; |
| 226 | |
| 227 | /** Returns currently active input box texture, depending on active state. */ |
| 228 | const HSpriteTexture& getActiveTexture() const; |
| 229 | |
| 230 | /** Returns currently active input box text color, depending on active state. */ |
| 231 | Color getActiveTextColor() const; |
| 232 | |
| 233 | /** Cuts currently selected text to clipboard. */ |
| 234 | void cutText(); |
| 235 | |
| 236 | /** Copies currently selected text to clipboard. */ |
| 237 | void copyText(); |
| 238 | |
| 239 | /** Inserts text from clipboard to current caret location. */ |
| 240 | void pasteText(); |
| 241 | |
| 242 | private: |
| 243 | static VirtualButton mCopyVB; |
| 244 | static VirtualButton mPasteVB; |
| 245 | static VirtualButton mCutVB; |
| 246 | static VirtualButton mSelectAllVB; |
| 247 | |
| 248 | // Sprites |
| 249 | ImageSprite* mImageSprite; |
| 250 | TextSprite* mTextSprite; |
| 251 | bool mIsMultiline; |
| 252 | Vector2I mTextOffset; |
| 253 | bool mHasFocus = false; |
| 254 | UINT64 mFocusGainedFrame = (UINT64)-1; |
| 255 | bool mIsMouseOver = false; |
| 256 | State mState = State::Normal; |
| 257 | |
| 258 | IMAGE_SPRITE_DESC mImageDesc; |
| 259 | String mText; |
| 260 | UINT32 mNumChars = 0; |
| 261 | std::function<bool(const String&)> mFilter; |
| 262 | |
| 263 | bool mCaretShown = false; |
| 264 | bool mSelectionShown = false; |
| 265 | bool mDragInProgress = false; |
| 266 | }; |
| 267 | |
| 268 | /** @} */ |
| 269 | } |