1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Core/Context.h"
26 #include "../Core/CoreEvents.h"
27 #include "../Core/Profiler.h"
28 #include "../Container/Sort.h"
29 #include "../Graphics/Graphics.h"
30 #include "../Graphics/GraphicsEvents.h"
31 #include "../Graphics/Shader.h"
32 #include "../Graphics/ShaderVariation.h"
33 #include "../Graphics/Texture2D.h"
34 #include "../Graphics/VertexBuffer.h"
35 #include "../Input/Input.h"
36 #include "../Input/InputEvents.h"
37 #include "../IO/Log.h"
38 #include "../Math/Matrix3x4.h"
39 #include "../Resource/ResourceCache.h"
40 #include "../UI/CheckBox.h"
41 #include "../UI/Cursor.h"
42 #include "../UI/DropDownList.h"
43 #include "../UI/FileSelector.h"
44 #include "../UI/Font.h"
45 #include "../UI/LineEdit.h"
46 #include "../UI/ListView.h"
47 #include "../UI/MessageBox.h"
48 #include "../UI/ProgressBar.h"
49 #include "../UI/ScrollBar.h"
50 #include "../UI/Slider.h"
51 #include "../UI/Sprite.h"
52 #include "../UI/Text.h"
53 #include "../UI/Text3D.h"
54 #include "../UI/ToolTip.h"
55 #include "../UI/UI.h"
56 #include "../UI/UIEvents.h"
57 #include "../UI/Window.h"
58 #include "../UI/View3D.h"
59 
60 #include <assert.h>
61 #include <SDL/SDL.h>
62 
63 #include "../DebugNew.h"
64 
65 #define TOUCHID_MASK(id) (1 << id)
66 
67 namespace Urho3D
68 {
69 
70 StringHash VAR_ORIGIN("Origin");
71 const StringHash VAR_ORIGINAL_PARENT("OriginalParent");
72 const StringHash VAR_ORIGINAL_CHILD_INDEX("OriginalChildIndex");
73 const StringHash VAR_PARENT_CHANGED("ParentChanged");
74 
75 const float DEFAULT_DOUBLECLICK_INTERVAL = 0.5f;
76 const float DEFAULT_DRAGBEGIN_INTERVAL = 0.5f;
77 const float DEFAULT_TOOLTIP_DELAY = 0.5f;
78 const int DEFAULT_DRAGBEGIN_DISTANCE = 5;
79 const int DEFAULT_FONT_TEXTURE_MAX_SIZE = 2048;
80 
81 const char* UI_CATEGORY = "UI";
82 
UI(Context * context)83 UI::UI(Context* context) :
84     Object(context),
85     rootElement_(new UIElement(context)),
86     rootModalElement_(new UIElement(context)),
87     doubleClickInterval_(DEFAULT_DOUBLECLICK_INTERVAL),
88     dragBeginInterval_(DEFAULT_DRAGBEGIN_INTERVAL),
89     defaultToolTipDelay_(DEFAULT_TOOLTIP_DELAY),
90     dragBeginDistance_(DEFAULT_DRAGBEGIN_DISTANCE),
91     mouseButtons_(0),
92     lastMouseButtons_(0),
93     qualifiers_(0),
94     maxFontTextureSize_(DEFAULT_FONT_TEXTURE_MAX_SIZE),
95     initialized_(false),
96     usingTouchInput_(false),
97 #ifdef _WIN32
98     nonFocusedMouseWheel_(false),    // Default MS Windows behaviour
99 #else
100     nonFocusedMouseWheel_(true),     // Default Mac OS X and Linux behaviour
101 #endif
102     useSystemClipboard_(false),
103 #if defined(__ANDROID__) || defined(IOS) || defined(TVOS)
104     useScreenKeyboard_(true),
105 #else
106     useScreenKeyboard_(false),
107 #endif
108     useMutableGlyphs_(false),
109     forceAutoHint_(false),
110     fontHintLevel_(FONT_HINT_LEVEL_NORMAL),
111     fontSubpixelThreshold_(12),
112     fontOversampling_(2),
113     uiRendered_(false),
114     nonModalBatchSize_(0),
115     dragElementsCount_(0),
116     dragConfirmedCount_(0),
117     uiScale_(1.0f),
118     customSize_(IntVector2::ZERO)
119 {
120     rootElement_->SetTraversalMode(TM_DEPTH_FIRST);
121     rootModalElement_->SetTraversalMode(TM_DEPTH_FIRST);
122 
123     // Register UI library object factories
124     RegisterUILibrary(context_);
125 
126     SubscribeToEvent(E_SCREENMODE, URHO3D_HANDLER(UI, HandleScreenMode));
127     SubscribeToEvent(E_MOUSEBUTTONDOWN, URHO3D_HANDLER(UI, HandleMouseButtonDown));
128     SubscribeToEvent(E_MOUSEBUTTONUP, URHO3D_HANDLER(UI, HandleMouseButtonUp));
129     SubscribeToEvent(E_MOUSEMOVE, URHO3D_HANDLER(UI, HandleMouseMove));
130     SubscribeToEvent(E_MOUSEWHEEL, URHO3D_HANDLER(UI, HandleMouseWheel));
131     SubscribeToEvent(E_TOUCHBEGIN, URHO3D_HANDLER(UI, HandleTouchBegin));
132     SubscribeToEvent(E_TOUCHEND, URHO3D_HANDLER(UI, HandleTouchEnd));
133     SubscribeToEvent(E_TOUCHMOVE, URHO3D_HANDLER(UI, HandleTouchMove));
134     SubscribeToEvent(E_KEYDOWN, URHO3D_HANDLER(UI, HandleKeyDown));
135     SubscribeToEvent(E_TEXTINPUT, URHO3D_HANDLER(UI, HandleTextInput));
136     SubscribeToEvent(E_DROPFILE, URHO3D_HANDLER(UI, HandleDropFile));
137 
138     // Try to initialize right now, but skip if screen mode is not yet set
139     Initialize();
140 }
141 
~UI()142 UI::~UI()
143 {
144 }
145 
SetCursor(Cursor * cursor)146 void UI::SetCursor(Cursor* cursor)
147 {
148     // Remove old cursor (if any) and set new
149     if (cursor_)
150     {
151         rootElement_->RemoveChild(cursor_);
152         cursor_.Reset();
153     }
154     if (cursor)
155     {
156         rootElement_->AddChild(cursor);
157         cursor_ = cursor;
158 
159         IntVector2 pos = cursor_->GetPosition();
160         const IntVector2& rootSize = rootElement_->GetSize();
161         const IntVector2& rootPos = rootElement_->GetPosition();
162         pos.x_ = Clamp(pos.x_, rootPos.x_, rootPos.x_ + rootSize.x_ - 1);
163         pos.y_ = Clamp(pos.y_, rootPos.y_, rootPos.y_ + rootSize.y_ - 1);
164         cursor_->SetPosition(pos);
165     }
166 }
167 
SetFocusElement(UIElement * element,bool byKey)168 void UI::SetFocusElement(UIElement* element, bool byKey)
169 {
170     using namespace FocusChanged;
171 
172     UIElement* originalElement = element;
173 
174     if (element)
175     {
176         // Return if already has focus
177         if (focusElement_ == element)
178             return;
179 
180         // Only allow child elements of the modal element to receive focus
181         if (HasModalElement())
182         {
183             UIElement* topLevel = element->GetParent();
184             while (topLevel && topLevel->GetParent() != rootElement_)
185                 topLevel = topLevel->GetParent();
186             if (topLevel)   // If parented to non-modal root then ignore
187                 return;
188         }
189 
190         // Search for an element in the hierarchy that can alter focus. If none found, exit
191         element = GetFocusableElement(element);
192         if (!element)
193             return;
194     }
195 
196     // Remove focus from the old element
197     if (focusElement_)
198     {
199         UIElement* oldFocusElement = focusElement_;
200         focusElement_.Reset();
201 
202         VariantMap& focusEventData = GetEventDataMap();
203         focusEventData[Defocused::P_ELEMENT] = oldFocusElement;
204         oldFocusElement->SendEvent(E_DEFOCUSED, focusEventData);
205     }
206 
207     // Then set focus to the new
208     if (element && element->GetFocusMode() >= FM_FOCUSABLE)
209     {
210         focusElement_ = element;
211 
212         VariantMap& focusEventData = GetEventDataMap();
213         focusEventData[Focused::P_ELEMENT] = element;
214         focusEventData[Focused::P_BYKEY] = byKey;
215         element->SendEvent(E_FOCUSED, focusEventData);
216     }
217 
218     VariantMap& eventData = GetEventDataMap();
219     eventData[P_CLICKEDELEMENT] = originalElement;
220     eventData[P_ELEMENT] = element;
221     SendEvent(E_FOCUSCHANGED, eventData);
222 }
223 
SetModalElement(UIElement * modalElement,bool enable)224 bool UI::SetModalElement(UIElement* modalElement, bool enable)
225 {
226     if (!modalElement)
227         return false;
228 
229     // Currently only allow modal window
230     if (modalElement->GetType() != Window::GetTypeStatic())
231         return false;
232 
233     assert(rootModalElement_);
234     UIElement* currParent = modalElement->GetParent();
235     if (enable)
236     {
237         // Make sure it is not already the child of the root modal element
238         if (currParent == rootModalElement_)
239             return false;
240 
241         // Adopt modal root as parent
242         modalElement->SetVar(VAR_ORIGINAL_PARENT, currParent);
243         modalElement->SetVar(VAR_ORIGINAL_CHILD_INDEX, currParent ? currParent->FindChild(modalElement) : M_MAX_UNSIGNED);
244         modalElement->SetParent(rootModalElement_);
245 
246         // If it is a popup element, bring along its top-level parent
247         UIElement* originElement = static_cast<UIElement*>(modalElement->GetVar(VAR_ORIGIN).GetPtr());
248         if (originElement)
249         {
250             UIElement* element = originElement;
251             while (element && element->GetParent() != rootElement_)
252                 element = element->GetParent();
253             if (element)
254             {
255                 originElement->SetVar(VAR_PARENT_CHANGED, element);
256                 UIElement* oriParent = element->GetParent();
257                 element->SetVar(VAR_ORIGINAL_PARENT, oriParent);
258                 element->SetVar(VAR_ORIGINAL_CHILD_INDEX, oriParent ? oriParent->FindChild(element) : M_MAX_UNSIGNED);
259                 element->SetParent(rootModalElement_);
260             }
261         }
262 
263         return true;
264     }
265     else
266     {
267         // Only the modal element can disable itself
268         if (currParent != rootModalElement_)
269             return false;
270 
271         // Revert back to original parent
272         modalElement->SetParent(static_cast<UIElement*>(modalElement->GetVar(VAR_ORIGINAL_PARENT).GetPtr()),
273             modalElement->GetVar(VAR_ORIGINAL_CHILD_INDEX).GetUInt());
274         VariantMap& vars = const_cast<VariantMap&>(modalElement->GetVars());
275         vars.Erase(VAR_ORIGINAL_PARENT);
276         vars.Erase(VAR_ORIGINAL_CHILD_INDEX);
277 
278         // If it is a popup element, revert back its top-level parent
279         UIElement* originElement = static_cast<UIElement*>(modalElement->GetVar(VAR_ORIGIN).GetPtr());
280         if (originElement)
281         {
282             UIElement* element = static_cast<UIElement*>(originElement->GetVar(VAR_PARENT_CHANGED).GetPtr());
283             if (element)
284             {
285                 const_cast<VariantMap&>(originElement->GetVars()).Erase(VAR_PARENT_CHANGED);
286                 element->SetParent(static_cast<UIElement*>(element->GetVar(VAR_ORIGINAL_PARENT).GetPtr()),
287                     element->GetVar(VAR_ORIGINAL_CHILD_INDEX).GetUInt());
288                 vars = const_cast<VariantMap&>(element->GetVars());
289                 vars.Erase(VAR_ORIGINAL_PARENT);
290                 vars.Erase(VAR_ORIGINAL_CHILD_INDEX);
291             }
292         }
293 
294         return true;
295     }
296 }
297 
Clear()298 void UI::Clear()
299 {
300     rootElement_->RemoveAllChildren();
301     rootModalElement_->RemoveAllChildren();
302     if (cursor_)
303         rootElement_->AddChild(cursor_);
304 }
305 
Update(float timeStep)306 void UI::Update(float timeStep)
307 {
308     assert(rootElement_ && rootModalElement_);
309 
310     URHO3D_PROFILE(UpdateUI);
311 
312     // Expire hovers
313     for (HashMap<WeakPtr<UIElement>, bool>::Iterator i = hoveredElements_.Begin(); i != hoveredElements_.End(); ++i)
314         i->second_ = false;
315 
316     Input* input = GetSubsystem<Input>();
317     bool mouseGrabbed = input->IsMouseGrabbed();
318 
319     IntVector2 cursorPos;
320     bool cursorVisible;
321     GetCursorPositionAndVisible(cursorPos, cursorVisible);
322 
323     // Drag begin based on time
324     if (dragElementsCount_ > 0 && !mouseGrabbed)
325     {
326         for (HashMap<WeakPtr<UIElement>, UI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
327         {
328             WeakPtr<UIElement> dragElement = i->first_;
329             UI::DragData* dragData = i->second_;
330 
331             if (!dragElement)
332             {
333                 i = DragElementErase(i);
334                 continue;
335             }
336 
337             if (!dragData->dragBeginPending)
338             {
339                 ++i;
340                 continue;
341             }
342 
343             if (dragData->dragBeginTimer.GetMSec(false) >= (unsigned)(dragBeginInterval_ * 1000))
344             {
345                 dragData->dragBeginPending = false;
346                 IntVector2 beginSendPos = dragData->dragBeginSumPos / dragData->numDragButtons;
347                 dragConfirmedCount_++;
348                 if (!usingTouchInput_)
349                     dragElement->OnDragBegin(dragElement->ScreenToElement(beginSendPos), beginSendPos, dragData->dragButtons,
350                         qualifiers_, cursor_);
351                 else
352                     dragElement->OnDragBegin(dragElement->ScreenToElement(beginSendPos), beginSendPos, dragData->dragButtons, 0, 0);
353 
354                 SendDragOrHoverEvent(E_DRAGBEGIN, dragElement, beginSendPos, IntVector2::ZERO, dragData);
355             }
356 
357             ++i;
358         }
359     }
360 
361     // Mouse hover
362     if (!mouseGrabbed && !input->GetTouchEmulation())
363     {
364         if (!usingTouchInput_ && cursorVisible)
365             ProcessHover(cursorPos, mouseButtons_, qualifiers_, cursor_);
366     }
367 
368     // Touch hover
369     unsigned numTouches = input->GetNumTouches();
370     for (unsigned i = 0; i < numTouches; ++i)
371     {
372         TouchState* touch = input->GetTouch(i);
373         IntVector2 touchPos = touch->position_;
374         touchPos.x_ = (int)(touchPos.x_ / uiScale_);
375         touchPos.y_ = (int)(touchPos.y_ / uiScale_);
376         ProcessHover(touchPos, TOUCHID_MASK(touch->touchID_), 0, 0);
377     }
378 
379     // End hovers that expired without refreshing
380     for (HashMap<WeakPtr<UIElement>, bool>::Iterator i = hoveredElements_.Begin(); i != hoveredElements_.End();)
381     {
382         if (i->first_.Expired() || !i->second_)
383         {
384             UIElement* element = i->first_;
385             if (element)
386             {
387                 using namespace HoverEnd;
388 
389                 VariantMap& eventData = GetEventDataMap();
390                 eventData[P_ELEMENT] = element;
391                 element->SendEvent(E_HOVEREND, eventData);
392             }
393             i = hoveredElements_.Erase(i);
394         }
395         else
396             ++i;
397     }
398 
399     Update(timeStep, rootElement_);
400     Update(timeStep, rootModalElement_);
401 }
402 
RenderUpdate()403 void UI::RenderUpdate()
404 {
405     assert(rootElement_ && rootModalElement_ && graphics_);
406 
407     URHO3D_PROFILE(GetUIBatches);
408 
409     uiRendered_ = false;
410 
411     // If the OS cursor is visible, do not render the UI's own cursor
412     bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
413 
414     // Get rendering batches from the non-modal UI elements
415     batches_.Clear();
416     vertexData_.Clear();
417     const IntVector2& rootSize = rootElement_->GetSize();
418     const IntVector2& rootPos = rootElement_->GetPosition();
419     // Note: the scissors operate on unscaled coordinates. Scissor scaling is only performed during render
420     IntRect currentScissor = IntRect(rootPos.x_, rootPos.y_, rootPos.x_ + rootSize.x_, rootPos.y_ + rootSize.y_);
421     if (rootElement_->IsVisible())
422         GetBatches(rootElement_, currentScissor);
423 
424     // Save the batch size of the non-modal batches for later use
425     nonModalBatchSize_ = batches_.Size();
426 
427     // Get rendering batches from the modal UI elements
428     GetBatches(rootModalElement_, currentScissor);
429 
430     // Get batches from the cursor (and its possible children) last to draw it on top of everything
431     if (cursor_ && cursor_->IsVisible() && !osCursorVisible)
432     {
433         currentScissor = IntRect(0, 0, rootSize.x_, rootSize.y_);
434         cursor_->GetBatches(batches_, vertexData_, currentScissor);
435         GetBatches(cursor_, currentScissor);
436     }
437 }
438 
Render(bool resetRenderTargets)439 void UI::Render(bool resetRenderTargets)
440 {
441     // Perform the default render only if not rendered yet
442     if (resetRenderTargets && uiRendered_)
443         return;
444 
445     URHO3D_PROFILE(RenderUI);
446 
447     // If the OS cursor is visible, apply its shape now if changed
448     bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
449     if (cursor_ && osCursorVisible)
450         cursor_->ApplyOSCursorShape();
451 
452     SetVertexData(vertexBuffer_, vertexData_);
453     SetVertexData(debugVertexBuffer_, debugVertexData_);
454 
455     // Render non-modal batches
456     Render(resetRenderTargets, vertexBuffer_, batches_, 0, nonModalBatchSize_);
457     // Render debug draw
458     Render(resetRenderTargets, debugVertexBuffer_, debugDrawBatches_, 0, debugDrawBatches_.Size());
459     // Render modal batches
460     Render(resetRenderTargets, vertexBuffer_, batches_, nonModalBatchSize_, batches_.Size());
461 
462     // Clear the debug draw batches and data
463     debugDrawBatches_.Clear();
464     debugVertexData_.Clear();
465 
466     uiRendered_ = true;
467 }
468 
DebugDraw(UIElement * element)469 void UI::DebugDraw(UIElement* element)
470 {
471     if (element)
472     {
473         const IntVector2& rootSize = rootElement_->GetSize();
474         const IntVector2& rootPos = rootElement_->GetPosition();
475         element->GetDebugDrawBatches(debugDrawBatches_, debugVertexData_, IntRect(rootPos.x_, rootPos.y_,
476                                                                                   rootPos.x_ + rootSize.x_,
477                                                                                   rootPos.y_ + rootSize.y_));
478     }
479 }
480 
LoadLayout(Deserializer & source,XMLFile * styleFile)481 SharedPtr<UIElement> UI::LoadLayout(Deserializer& source, XMLFile* styleFile)
482 {
483     SharedPtr<XMLFile> xml(new XMLFile(context_));
484     if (!xml->Load(source))
485         return SharedPtr<UIElement>();
486     else
487         return LoadLayout(xml, styleFile);
488 }
489 
LoadLayout(XMLFile * file,XMLFile * styleFile)490 SharedPtr<UIElement> UI::LoadLayout(XMLFile* file, XMLFile* styleFile)
491 {
492     URHO3D_PROFILE(LoadUILayout);
493 
494     SharedPtr<UIElement> root;
495 
496     if (!file)
497     {
498         URHO3D_LOGERROR("Null UI layout XML file");
499         return root;
500     }
501 
502     URHO3D_LOGDEBUG("Loading UI layout " + file->GetName());
503 
504     XMLElement rootElem = file->GetRoot("element");
505     if (!rootElem)
506     {
507         URHO3D_LOGERROR("No root UI element in " + file->GetName());
508         return root;
509     }
510 
511     String typeName = rootElem.GetAttribute("type");
512     if (typeName.Empty())
513         typeName = "UIElement";
514 
515     root = DynamicCast<UIElement>(context_->CreateObject(typeName));
516     if (!root)
517     {
518         URHO3D_LOGERROR("Could not create unknown UI element " + typeName);
519         return root;
520     }
521 
522     // Use default style file of the root element if it has one
523     if (!styleFile)
524         styleFile = rootElement_->GetDefaultStyle(false);
525     // Set it as default for later use by children elements
526     if (styleFile)
527         root->SetDefaultStyle(styleFile);
528 
529     root->LoadXML(rootElem, styleFile);
530     return root;
531 }
532 
SaveLayout(Serializer & dest,UIElement * element)533 bool UI::SaveLayout(Serializer& dest, UIElement* element)
534 {
535     URHO3D_PROFILE(SaveUILayout);
536 
537     return element && element->SaveXML(dest);
538 }
539 
SetClipboardText(const String & text)540 void UI::SetClipboardText(const String& text)
541 {
542     clipBoard_ = text;
543     if (useSystemClipboard_)
544         SDL_SetClipboardText(text.CString());
545 }
546 
SetDoubleClickInterval(float interval)547 void UI::SetDoubleClickInterval(float interval)
548 {
549     doubleClickInterval_ = Max(interval, 0.0f);
550 }
551 
SetDragBeginInterval(float interval)552 void UI::SetDragBeginInterval(float interval)
553 {
554     dragBeginInterval_ = Max(interval, 0.0f);
555 }
556 
SetDragBeginDistance(int pixels)557 void UI::SetDragBeginDistance(int pixels)
558 {
559     dragBeginDistance_ = Max(pixels, 0);
560 }
561 
SetDefaultToolTipDelay(float delay)562 void UI::SetDefaultToolTipDelay(float delay)
563 {
564     defaultToolTipDelay_ = Max(delay, 0.0f);
565 }
566 
SetMaxFontTextureSize(int size)567 void UI::SetMaxFontTextureSize(int size)
568 {
569     if (IsPowerOfTwo((unsigned)size) && size >= FONT_TEXTURE_MIN_SIZE)
570     {
571         if (size != maxFontTextureSize_)
572         {
573             maxFontTextureSize_ = size;
574             ReleaseFontFaces();
575         }
576     }
577 }
578 
SetNonFocusedMouseWheel(bool nonFocusedMouseWheel)579 void UI::SetNonFocusedMouseWheel(bool nonFocusedMouseWheel)
580 {
581     nonFocusedMouseWheel_ = nonFocusedMouseWheel;
582 }
583 
SetUseSystemClipboard(bool enable)584 void UI::SetUseSystemClipboard(bool enable)
585 {
586     useSystemClipboard_ = enable;
587 }
588 
SetUseScreenKeyboard(bool enable)589 void UI::SetUseScreenKeyboard(bool enable)
590 {
591     useScreenKeyboard_ = enable;
592 }
593 
SetUseMutableGlyphs(bool enable)594 void UI::SetUseMutableGlyphs(bool enable)
595 {
596     if (enable != useMutableGlyphs_)
597     {
598         useMutableGlyphs_ = enable;
599         ReleaseFontFaces();
600     }
601 }
602 
SetForceAutoHint(bool enable)603 void UI::SetForceAutoHint(bool enable)
604 {
605     if (enable != forceAutoHint_)
606     {
607         forceAutoHint_ = enable;
608         ReleaseFontFaces();
609     }
610 }
611 
SetFontHintLevel(FontHintLevel level)612 void UI::SetFontHintLevel(FontHintLevel level)
613 {
614     if (level != fontHintLevel_)
615     {
616         fontHintLevel_ = level;
617         ReleaseFontFaces();
618     }
619 }
620 
SetFontSubpixelThreshold(float threshold)621 void UI::SetFontSubpixelThreshold(float threshold)
622 {
623     assert(threshold >= 0);
624     if (threshold != fontSubpixelThreshold_)
625     {
626         fontSubpixelThreshold_ = threshold;
627         ReleaseFontFaces();
628     }
629 }
630 
SetFontOversampling(int oversampling)631 void UI::SetFontOversampling(int oversampling)
632 {
633     assert(oversampling >= 1);
634     oversampling = Clamp(oversampling, 1, 8);
635     if (oversampling != fontOversampling_)
636     {
637         fontOversampling_ = oversampling;
638         ReleaseFontFaces();
639     }
640 }
641 
SetScale(float scale)642 void UI::SetScale(float scale)
643 {
644     uiScale_ = Max(scale, M_EPSILON);
645     ResizeRootElement();
646 }
647 
SetWidth(float width)648 void UI::SetWidth(float width)
649 {
650     IntVector2 size = GetEffectiveRootElementSize(false);
651     SetScale((float)size.x_ / width);
652 }
653 
SetHeight(float height)654 void UI::SetHeight(float height)
655 {
656     IntVector2 size = GetEffectiveRootElementSize(false);
657     SetScale((float)size.y_ / height);
658 }
659 
SetCustomSize(const IntVector2 & size)660 void UI::SetCustomSize(const IntVector2& size)
661 {
662     customSize_ = IntVector2(Max(0, size.x_), Max(0, size.y_));
663     ResizeRootElement();
664 }
665 
SetCustomSize(int width,int height)666 void UI::SetCustomSize(int width, int height)
667 {
668     customSize_ = IntVector2(Max(0, width), Max(0, height));
669     ResizeRootElement();
670 }
671 
GetCursorPosition() const672 IntVector2 UI::GetCursorPosition() const
673 {
674     return cursor_ ? cursor_->GetPosition() : GetSubsystem<Input>()->GetMousePosition();
675 }
676 
GetElementAt(const IntVector2 & position,bool enabledOnly)677 UIElement* UI::GetElementAt(const IntVector2& position, bool enabledOnly)
678 {
679     IntVector2 positionCopy(position);
680     const IntVector2& rootSize = rootElement_->GetSize();
681     const IntVector2& rootPos = rootElement_->GetPosition();
682 
683     // If position is out of bounds of root element return null.
684     if (position.x_ < rootPos.x_ || position.x_ > rootPos.x_ + rootSize.x_)
685         return 0;
686 
687     if (position.y_ < rootPos.y_ || position.y_ > rootPos.y_ + rootSize.y_)
688         return 0;
689 
690     // If UI is smaller than the screen, wrap if necessary
691     if (rootSize.x_ > 0 && rootSize.y_ > 0)
692     {
693         if (positionCopy.x_ >= rootPos.x_ + rootSize.x_)
694             positionCopy.x_ = rootPos.x_ + ((positionCopy.x_ - rootPos.x_) % rootSize.x_);
695         if (positionCopy.y_ >= rootPos.y_ + rootSize.y_)
696             positionCopy.y_ = rootPos.y_ + ((positionCopy.y_ - rootPos.y_) % rootSize.y_);
697     }
698 
699     UIElement* result = 0;
700     GetElementAt(result, HasModalElement() ? rootModalElement_ : rootElement_, positionCopy, enabledOnly);
701     return result;
702 }
703 
GetElementAt(int x,int y,bool enabledOnly)704 UIElement* UI::GetElementAt(int x, int y, bool enabledOnly)
705 {
706     return GetElementAt(IntVector2(x, y), enabledOnly);
707 }
708 
GetFrontElement() const709 UIElement* UI::GetFrontElement() const
710 {
711     const Vector<SharedPtr<UIElement> >& rootChildren = rootElement_->GetChildren();
712     int maxPriority = M_MIN_INT;
713     UIElement* front = 0;
714 
715     for (unsigned i = 0; i < rootChildren.Size(); ++i)
716     {
717         // Do not take into account input-disabled elements, hidden elements or those that are always in the front
718         if (!rootChildren[i]->IsEnabled() || !rootChildren[i]->IsVisible() || !rootChildren[i]->GetBringToBack())
719             continue;
720 
721         int priority = rootChildren[i]->GetPriority();
722         if (priority > maxPriority)
723         {
724             maxPriority = priority;
725             front = rootChildren[i];
726         }
727     }
728 
729     return front;
730 }
731 
GetDragElements()732 const Vector<UIElement*> UI::GetDragElements()
733 {
734     // Do not return the element until drag begin event has actually been posted
735     if (!dragElementsConfirmed_.Empty())
736         return dragElementsConfirmed_;
737 
738     for (HashMap<WeakPtr<UIElement>, UI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
739     {
740         WeakPtr<UIElement> dragElement = i->first_;
741         UI::DragData* dragData = i->second_;
742 
743         if (!dragElement)
744         {
745             i = DragElementErase(i);
746             continue;
747         }
748 
749         if (!dragData->dragBeginPending)
750             dragElementsConfirmed_.Push(dragElement);
751 
752         ++i;
753     }
754 
755     return dragElementsConfirmed_;
756 }
757 
GetDragElement(unsigned index)758 UIElement* UI::GetDragElement(unsigned index)
759 {
760     GetDragElements();
761     if (index >= dragElementsConfirmed_.Size())
762         return (UIElement*)0;
763 
764     return dragElementsConfirmed_[index];
765 }
766 
GetClipboardText() const767 const String& UI::GetClipboardText() const
768 {
769     if (useSystemClipboard_)
770     {
771         char* text = SDL_GetClipboardText();
772         clipBoard_ = String(text);
773         if (text)
774             SDL_free(text);
775     }
776 
777     return clipBoard_;
778 }
779 
HasModalElement() const780 bool UI::HasModalElement() const
781 {
782     return rootModalElement_->GetNumChildren() > 0;
783 }
784 
Initialize()785 void UI::Initialize()
786 {
787     Graphics* graphics = GetSubsystem<Graphics>();
788 
789     if (!graphics || !graphics->IsInitialized())
790         return;
791 
792     URHO3D_PROFILE(InitUI);
793 
794     graphics_ = graphics;
795     UIBatch::posAdjust = Vector3(Graphics::GetPixelUVOffset(), 0.0f);
796 
797     // Set initial root element size
798     ResizeRootElement();
799 
800     vertexBuffer_ = new VertexBuffer(context_);
801     debugVertexBuffer_ = new VertexBuffer(context_);
802 
803     initialized_ = true;
804 
805     SubscribeToEvent(E_BEGINFRAME, URHO3D_HANDLER(UI, HandleBeginFrame));
806     SubscribeToEvent(E_POSTUPDATE, URHO3D_HANDLER(UI, HandlePostUpdate));
807     SubscribeToEvent(E_RENDERUPDATE, URHO3D_HANDLER(UI, HandleRenderUpdate));
808 
809     URHO3D_LOGINFO("Initialized user interface");
810 }
811 
Update(float timeStep,UIElement * element)812 void UI::Update(float timeStep, UIElement* element)
813 {
814     // Keep a weak pointer to the element in case it destroys itself on update
815     WeakPtr<UIElement> elementWeak(element);
816 
817     element->Update(timeStep);
818     if (elementWeak.Expired())
819         return;
820 
821     const Vector<SharedPtr<UIElement> >& children = element->GetChildren();
822     // Update of an element may modify its child vector. Use just index-based iteration to be safe
823     for (unsigned i = 0; i < children.Size(); ++i)
824         Update(timeStep, children[i]);
825 }
826 
SetVertexData(VertexBuffer * dest,const PODVector<float> & vertexData)827 void UI::SetVertexData(VertexBuffer* dest, const PODVector<float>& vertexData)
828 {
829     if (vertexData.Empty())
830         return;
831 
832     // Update quad geometry into the vertex buffer
833     // Resize the vertex buffer first if too small or much too large
834     unsigned numVertices = vertexData.Size() / UI_VERTEX_SIZE;
835     if (dest->GetVertexCount() < numVertices || dest->GetVertexCount() > numVertices * 2)
836         dest->SetSize(numVertices, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1, true);
837 
838     dest->SetData(&vertexData[0]);
839 }
840 
Render(bool resetRenderTargets,VertexBuffer * buffer,const PODVector<UIBatch> & batches,unsigned batchStart,unsigned batchEnd)841 void UI::Render(bool resetRenderTargets, VertexBuffer* buffer, const PODVector<UIBatch>& batches, unsigned batchStart,
842     unsigned batchEnd)
843 {
844     // Engine does not render when window is closed or device is lost
845     assert(graphics_ && graphics_->IsInitialized() && !graphics_->IsDeviceLost());
846 
847     if (batches.Empty())
848         return;
849 
850     if (resetRenderTargets)
851         graphics_->ResetRenderTargets();
852 
853     IntVector2 viewSize = graphics_->GetViewport().Size();
854     Vector2 invScreenSize(1.0f / (float)viewSize.x_, 1.0f / (float)viewSize.y_);
855     Vector2 scale(2.0f * invScreenSize.x_, -2.0f * invScreenSize.y_);
856     Vector2 offset(-1.0f, 1.0f);
857 
858     Matrix4 projection(Matrix4::IDENTITY);
859     projection.m00_ = scale.x_ * uiScale_;
860     projection.m03_ = offset.x_;
861     projection.m11_ = scale.y_ * uiScale_;
862     projection.m13_ = offset.y_;
863     projection.m22_ = 1.0f;
864     projection.m23_ = 0.0f;
865     projection.m33_ = 1.0f;
866 
867     graphics_->ClearParameterSources();
868     graphics_->SetColorWrite(true);
869     graphics_->SetCullMode(CULL_CCW);
870     graphics_->SetDepthTest(CMP_ALWAYS);
871     graphics_->SetDepthWrite(false);
872     graphics_->SetFillMode(FILL_SOLID);
873     graphics_->SetStencilTest(false);
874     graphics_->SetVertexBuffer(buffer);
875 
876     ShaderVariation* noTextureVS = graphics_->GetShader(VS, "Basic", "VERTEXCOLOR");
877     ShaderVariation* diffTextureVS = graphics_->GetShader(VS, "Basic", "DIFFMAP VERTEXCOLOR");
878     ShaderVariation* noTexturePS = graphics_->GetShader(PS, "Basic", "VERTEXCOLOR");
879     ShaderVariation* diffTexturePS = graphics_->GetShader(PS, "Basic", "DIFFMAP VERTEXCOLOR");
880     ShaderVariation* diffMaskTexturePS = graphics_->GetShader(PS, "Basic", "DIFFMAP ALPHAMASK VERTEXCOLOR");
881     ShaderVariation* alphaTexturePS = graphics_->GetShader(PS, "Basic", "ALPHAMAP VERTEXCOLOR");
882 
883     unsigned alphaFormat = Graphics::GetAlphaFormat();
884 
885     for (unsigned i = batchStart; i < batchEnd; ++i)
886     {
887         const UIBatch& batch = batches[i];
888         if (batch.vertexStart_ == batch.vertexEnd_)
889             continue;
890 
891         ShaderVariation* ps;
892         ShaderVariation* vs;
893 
894         if (!batch.texture_)
895         {
896             ps = noTexturePS;
897             vs = noTextureVS;
898         }
899         else
900         {
901             // If texture contains only an alpha channel, use alpha shader (for fonts)
902             vs = diffTextureVS;
903 
904             if (batch.texture_->GetFormat() == alphaFormat)
905                 ps = alphaTexturePS;
906             else if (batch.blendMode_ != BLEND_ALPHA && batch.blendMode_ != BLEND_ADDALPHA && batch.blendMode_ != BLEND_PREMULALPHA)
907                 ps = diffMaskTexturePS;
908             else
909                 ps = diffTexturePS;
910         }
911 
912         graphics_->SetShaders(vs, ps);
913         if (graphics_->NeedParameterUpdate(SP_OBJECT, this))
914             graphics_->SetShaderParameter(VSP_MODEL, Matrix3x4::IDENTITY);
915         if (graphics_->NeedParameterUpdate(SP_CAMERA, this))
916             graphics_->SetShaderParameter(VSP_VIEWPROJ, projection);
917         if (graphics_->NeedParameterUpdate(SP_MATERIAL, this))
918             graphics_->SetShaderParameter(PSP_MATDIFFCOLOR, Color(1.0f, 1.0f, 1.0f, 1.0f));
919 
920         float elapsedTime = GetSubsystem<Time>()->GetElapsedTime();
921         graphics_->SetShaderParameter(VSP_ELAPSEDTIME, elapsedTime);
922         graphics_->SetShaderParameter(PSP_ELAPSEDTIME, elapsedTime);
923 
924         IntRect scissor = batch.scissor_;
925         scissor.left_ = (int)(scissor.left_ * uiScale_);
926         scissor.top_ = (int)(scissor.top_ * uiScale_);
927         scissor.right_ = (int)(scissor.right_ * uiScale_);
928         scissor.bottom_ = (int)(scissor.bottom_ * uiScale_);
929 
930         graphics_->SetBlendMode(batch.blendMode_);
931         graphics_->SetScissorTest(true, scissor);
932         graphics_->SetTexture(0, batch.texture_);
933         graphics_->Draw(TRIANGLE_LIST, batch.vertexStart_ / UI_VERTEX_SIZE,
934             (batch.vertexEnd_ - batch.vertexStart_) / UI_VERTEX_SIZE);
935     }
936 }
937 
GetBatches(UIElement * element,IntRect currentScissor)938 void UI::GetBatches(UIElement* element, IntRect currentScissor)
939 {
940     // Set clipping scissor for child elements. No need to draw if zero size
941     element->AdjustScissor(currentScissor);
942     if (currentScissor.left_ == currentScissor.right_ || currentScissor.top_ == currentScissor.bottom_)
943         return;
944 
945     element->SortChildren();
946     const Vector<SharedPtr<UIElement> >& children = element->GetChildren();
947     if (children.Empty())
948         return;
949 
950     // For non-root elements draw all children of same priority before recursing into their children: assumption is that they have
951     // same renderstate
952     Vector<SharedPtr<UIElement> >::ConstIterator i = children.Begin();
953     if (element->GetTraversalMode() == TM_BREADTH_FIRST)
954     {
955         Vector<SharedPtr<UIElement> >::ConstIterator j = i;
956         while (i != children.End())
957         {
958             int currentPriority = (*i)->GetPriority();
959             while (j != children.End() && (*j)->GetPriority() == currentPriority)
960             {
961                 if ((*j)->IsWithinScissor(currentScissor) && (*j) != cursor_)
962                     (*j)->GetBatches(batches_, vertexData_, currentScissor);
963                 ++j;
964             }
965             // Now recurse into the children
966             while (i != j)
967             {
968                 if ((*i)->IsVisible() && (*i) != cursor_)
969                     GetBatches(*i, currentScissor);
970                 ++i;
971             }
972         }
973     }
974     // On the root level draw each element and its children immediately after to avoid artifacts
975     else
976     {
977         while (i != children.End())
978         {
979             if ((*i) != cursor_)
980             {
981                 if ((*i)->IsWithinScissor(currentScissor))
982                     (*i)->GetBatches(batches_, vertexData_, currentScissor);
983                 if ((*i)->IsVisible())
984                     GetBatches(*i, currentScissor);
985             }
986             ++i;
987         }
988     }
989 }
990 
GetElementAt(UIElement * & result,UIElement * current,const IntVector2 & position,bool enabledOnly)991 void UI::GetElementAt(UIElement*& result, UIElement* current, const IntVector2& position, bool enabledOnly)
992 {
993     if (!current)
994         return;
995 
996     current->SortChildren();
997     const Vector<SharedPtr<UIElement> >& children = current->GetChildren();
998     LayoutMode parentLayoutMode = current->GetLayoutMode();
999 
1000     for (unsigned i = 0; i < children.Size(); ++i)
1001     {
1002         UIElement* element = children[i];
1003         bool hasChildren = element->GetNumChildren() > 0;
1004 
1005         if (element != cursor_.Get() && element->IsVisible())
1006         {
1007             if (element->IsInside(position, true))
1008             {
1009                 // Store the current result, then recurse into its children. Because children
1010                 // are sorted from lowest to highest priority, the topmost match should remain
1011                 if (element->IsEnabled() || !enabledOnly)
1012                     result = element;
1013 
1014                 if (hasChildren)
1015                     GetElementAt(result, element, position, enabledOnly);
1016                 // Layout optimization: if the element has no children, can break out after the first match
1017                 else if (parentLayoutMode != LM_FREE)
1018                     break;
1019             }
1020             else
1021             {
1022                 if (hasChildren)
1023                 {
1024                     if (element->IsInsideCombined(position, true))
1025                         GetElementAt(result, element, position, enabledOnly);
1026                 }
1027                 // Layout optimization: if position is much beyond the visible screen, check how many elements we can skip,
1028                 // or if we already passed all visible elements
1029                 else if (parentLayoutMode != LM_FREE)
1030                 {
1031                     if (!i)
1032                     {
1033                         int screenPos = (parentLayoutMode == LM_HORIZONTAL) ? element->GetScreenPosition().x_ :
1034                             element->GetScreenPosition().y_;
1035                         int layoutMaxSize = current->GetLayoutElementMaxSize();
1036 
1037                         if (screenPos < 0 && layoutMaxSize > 0)
1038                         {
1039                             unsigned toSkip = (unsigned)(-screenPos / layoutMaxSize);
1040                             if (toSkip > 0)
1041                                 i += (toSkip - 1);
1042                         }
1043                     }
1044                     // Note: we cannot check for the up / left limits of positioning, since the element may be off the visible
1045                     // screen but some of its layouted children will yet be visible. In down & right directions we can terminate
1046                     // the loop, since all further children will be further down or right.
1047                     else if (parentLayoutMode == LM_HORIZONTAL)
1048                     {
1049                         if (element->GetScreenPosition().x_ >= rootElement_->GetPosition().x_ + rootElement_->GetSize().x_)
1050                             break;
1051                     }
1052                     else if (parentLayoutMode == LM_VERTICAL)
1053                     {
1054                         if (element->GetScreenPosition().y_ >= rootElement_->GetPosition().y_ + rootElement_->GetSize().y_)
1055                             break;
1056                     }
1057                 }
1058             }
1059         }
1060     }
1061 }
1062 
GetFocusableElement(UIElement * element)1063 UIElement* UI::GetFocusableElement(UIElement* element)
1064 {
1065     while (element)
1066     {
1067         if (element->GetFocusMode() != FM_NOTFOCUSABLE)
1068             break;
1069         element = element->GetParent();
1070     }
1071     return element;
1072 }
1073 
GetCursorPositionAndVisible(IntVector2 & pos,bool & visible)1074 void UI::GetCursorPositionAndVisible(IntVector2& pos, bool& visible)
1075 {
1076     // Prefer software cursor then OS-specific cursor
1077     if (cursor_ && cursor_->IsVisible())
1078     {
1079         pos = cursor_->GetPosition();
1080         visible = true;
1081     }
1082     else if (GetSubsystem<Input>()->GetMouseMode() == MM_RELATIVE)
1083         visible = true;
1084     else
1085     {
1086         Input* input = GetSubsystem<Input>();
1087         pos = input->GetMousePosition();
1088         visible = input->IsMouseVisible();
1089 
1090         if (!visible && cursor_)
1091             pos = cursor_->GetPosition();
1092     }
1093 
1094     pos.x_ = (int)(pos.x_ / uiScale_);
1095     pos.y_ = (int)(pos.y_ / uiScale_);
1096 }
1097 
SetCursorShape(CursorShape shape)1098 void UI::SetCursorShape(CursorShape shape)
1099 {
1100     if (cursor_)
1101         cursor_->SetShape(shape);
1102 }
1103 
ReleaseFontFaces()1104 void UI::ReleaseFontFaces()
1105 {
1106     URHO3D_LOGDEBUG("Reloading font faces");
1107 
1108     PODVector<Font*> fonts;
1109     GetSubsystem<ResourceCache>()->GetResources<Font>(fonts);
1110 
1111     for (unsigned i = 0; i < fonts.Size(); ++i)
1112         fonts[i]->ReleaseFaces();
1113 }
1114 
ProcessHover(const IntVector2 & cursorPos,int buttons,int qualifiers,Cursor * cursor)1115 void UI::ProcessHover(const IntVector2& cursorPos, int buttons, int qualifiers, Cursor* cursor)
1116 {
1117     WeakPtr<UIElement> element(GetElementAt(cursorPos));
1118 
1119     for (HashMap<WeakPtr<UIElement>, UI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
1120     {
1121         WeakPtr<UIElement> dragElement = i->first_;
1122         UI::DragData* dragData = i->second_;
1123 
1124         if (!dragElement)
1125         {
1126             i = DragElementErase(i);
1127             continue;
1128         }
1129 
1130         bool dragSource = dragElement && (dragElement->GetDragDropMode() & DD_SOURCE) != 0;
1131         bool dragTarget = element && (element->GetDragDropMode() & DD_TARGET) != 0;
1132         bool dragDropTest = dragSource && dragTarget && element != dragElement;
1133         // If drag start event has not been posted yet, do not do drag handling here
1134         if (dragData->dragBeginPending)
1135             dragSource = dragTarget = dragDropTest = false;
1136 
1137         // Hover effect
1138         // If a drag is going on, transmit hover only to the element being dragged, unless it's a drop target
1139         if (element && element->IsEnabled())
1140         {
1141             if (dragElement == element || dragDropTest)
1142             {
1143                 element->OnHover(element->ScreenToElement(cursorPos), cursorPos, buttons, qualifiers, cursor);
1144 
1145                 // Begin hover event
1146                 if (!hoveredElements_.Contains(element))
1147                 {
1148                     SendDragOrHoverEvent(E_HOVERBEGIN, element, cursorPos, IntVector2::ZERO, 0);
1149                     // Exit if element is destroyed by the event handling
1150                     if (!element)
1151                         return;
1152                 }
1153                 hoveredElements_[element] = true;
1154             }
1155         }
1156 
1157         // Drag and drop test
1158         if (dragDropTest)
1159         {
1160             bool accept = element->OnDragDropTest(dragElement);
1161             if (accept)
1162             {
1163                 using namespace DragDropTest;
1164 
1165                 VariantMap& eventData = GetEventDataMap();
1166                 eventData[P_SOURCE] = dragElement.Get();
1167                 eventData[P_TARGET] = element.Get();
1168                 eventData[P_ACCEPT] = accept;
1169                 SendEvent(E_DRAGDROPTEST, eventData);
1170                 accept = eventData[P_ACCEPT].GetBool();
1171             }
1172 
1173             if (cursor)
1174                 cursor->SetShape(accept ? CS_ACCEPTDROP : CS_REJECTDROP);
1175         }
1176         else if (dragSource && cursor)
1177             cursor->SetShape(dragElement == element ? CS_ACCEPTDROP : CS_REJECTDROP);
1178 
1179         ++i;
1180     }
1181 
1182     // Hover effect
1183     // If no drag is going on, transmit hover event.
1184     if (element && element->IsEnabled())
1185     {
1186         if (dragElementsCount_ == 0)
1187         {
1188             element->OnHover(element->ScreenToElement(cursorPos), cursorPos, buttons, qualifiers, cursor);
1189 
1190             // Begin hover event
1191             if (!hoveredElements_.Contains(element))
1192             {
1193                 SendDragOrHoverEvent(E_HOVERBEGIN, element, cursorPos, IntVector2::ZERO, 0);
1194                 // Exit if element is destroyed by the event handling
1195                 if (!element)
1196                     return;
1197             }
1198             hoveredElements_[element] = true;
1199         }
1200     }
1201 }
1202 
ProcessClickBegin(const IntVector2 & cursorPos,int button,int buttons,int qualifiers,Cursor * cursor,bool cursorVisible)1203 void UI::ProcessClickBegin(const IntVector2& cursorPos, int button, int buttons, int qualifiers, Cursor* cursor, bool cursorVisible)
1204 {
1205     if (cursorVisible)
1206     {
1207         WeakPtr<UIElement> element(GetElementAt(cursorPos));
1208 
1209         bool newButton;
1210         if (usingTouchInput_)
1211             newButton = (button & buttons) == 0;
1212         else
1213             newButton = true;
1214         buttons |= button;
1215 
1216         if (element)
1217             SetFocusElement(element);
1218 
1219         // Focus change events may destroy the element, check again.
1220         if (element)
1221         {
1222             // Handle focusing & bringing to front
1223             element->BringToFront();
1224 
1225             // Handle click
1226             element->OnClickBegin(element->ScreenToElement(cursorPos), cursorPos, button, buttons, qualifiers, cursor);
1227             SendClickEvent(E_UIMOUSECLICK, 0, element, cursorPos, button, buttons, qualifiers);
1228 
1229             // Fire double click event if element matches and is in time
1230             if (doubleClickElement_ && element == doubleClickElement_ &&
1231                 clickTimer_.GetMSec(true) < (unsigned)(doubleClickInterval_ * 1000) && lastMouseButtons_ == buttons)
1232             {
1233                 element->OnDoubleClick(element->ScreenToElement(cursorPos), cursorPos, button, buttons, qualifiers, cursor);
1234                 doubleClickElement_.Reset();
1235                 SendClickEvent(E_UIMOUSEDOUBLECLICK, 0, element, cursorPos, button, buttons, qualifiers);
1236             }
1237             else
1238             {
1239                 doubleClickElement_ = element;
1240                 clickTimer_.Reset();
1241             }
1242 
1243             // Handle start of drag. Click handling may have caused destruction of the element, so check the pointer again
1244             bool dragElementsContain = dragElements_.Contains(element);
1245             if (element && !dragElementsContain)
1246             {
1247                 DragData* dragData = new DragData();
1248                 dragElements_[element] = dragData;
1249                 dragData->dragBeginPending = true;
1250                 dragData->sumPos = cursorPos;
1251                 dragData->dragBeginSumPos = cursorPos;
1252                 dragData->dragBeginTimer.Reset();
1253                 dragData->dragButtons = button;
1254                 dragData->numDragButtons = CountSetBits((unsigned)dragData->dragButtons);
1255                 dragElementsCount_++;
1256 
1257                 dragElementsContain = dragElements_.Contains(element);
1258             }
1259             else if (element && dragElementsContain && newButton)
1260             {
1261                 DragData* dragData = dragElements_[element];
1262                 dragData->sumPos += cursorPos;
1263                 dragData->dragBeginSumPos += cursorPos;
1264                 dragData->dragButtons |= button;
1265                 dragData->numDragButtons = CountSetBits((unsigned)dragData->dragButtons);
1266             }
1267         }
1268         else
1269         {
1270             // If clicked over no element, or a disabled element, lose focus (but not if there is a modal element)
1271             if (!HasModalElement())
1272                 SetFocusElement(0);
1273             SendClickEvent(E_UIMOUSECLICK, 0, element, cursorPos, button, buttons, qualifiers);
1274 
1275             if (clickTimer_.GetMSec(true) < (unsigned)(doubleClickInterval_ * 1000) && lastMouseButtons_ == buttons)
1276                 SendClickEvent(E_UIMOUSEDOUBLECLICK, 0, element, cursorPos, button, buttons, qualifiers);
1277         }
1278 
1279         lastMouseButtons_ = buttons;
1280     }
1281 }
1282 
ProcessClickEnd(const IntVector2 & cursorPos,int button,int buttons,int qualifiers,Cursor * cursor,bool cursorVisible)1283 void UI::ProcessClickEnd(const IntVector2& cursorPos, int button, int buttons, int qualifiers, Cursor* cursor, bool cursorVisible)
1284 {
1285     WeakPtr<UIElement> element;
1286     if (cursorVisible)
1287         element = GetElementAt(cursorPos);
1288 
1289     // Handle end of drag
1290     for (HashMap<WeakPtr<UIElement>, UI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
1291     {
1292         WeakPtr<UIElement> dragElement = i->first_;
1293         UI::DragData* dragData = i->second_;
1294 
1295         if (!dragElement || !cursorVisible)
1296         {
1297             i = DragElementErase(i);
1298             continue;
1299         }
1300 
1301         if (dragData->dragButtons & button)
1302         {
1303             // Handle end of click
1304             if (element)
1305                 element->OnClickEnd(element->ScreenToElement(cursorPos), cursorPos, button, buttons, qualifiers, cursor,
1306                     dragElement);
1307 
1308             SendClickEvent(E_UIMOUSECLICKEND, dragElement, element, cursorPos, button, buttons, qualifiers);
1309 
1310             if (dragElement && dragElement->IsEnabled() && dragElement->IsVisible() && !dragData->dragBeginPending)
1311             {
1312                 dragElement->OnDragEnd(dragElement->ScreenToElement(cursorPos), cursorPos, dragData->dragButtons, buttons,
1313                     cursor);
1314                 SendDragOrHoverEvent(E_DRAGEND, dragElement, cursorPos, IntVector2::ZERO, dragData);
1315 
1316                 bool dragSource = dragElement && (dragElement->GetDragDropMode() & DD_SOURCE) != 0;
1317                 if (dragSource)
1318                 {
1319                     bool dragTarget = element && (element->GetDragDropMode() & DD_TARGET) != 0;
1320                     bool dragDropFinish = dragSource && dragTarget && element != dragElement;
1321 
1322                     if (dragDropFinish)
1323                     {
1324                         bool accept = element->OnDragDropFinish(dragElement);
1325 
1326                         // OnDragDropFinish() may have caused destruction of the elements, so check the pointers again
1327                         if (accept && dragElement && element)
1328                         {
1329                             using namespace DragDropFinish;
1330 
1331                             VariantMap& eventData = GetEventDataMap();
1332                             eventData[P_SOURCE] = dragElement.Get();
1333                             eventData[P_TARGET] = element.Get();
1334                             eventData[P_ACCEPT] = accept;
1335                             SendEvent(E_DRAGDROPFINISH, eventData);
1336                         }
1337                     }
1338                 }
1339             }
1340 
1341             i = DragElementErase(i);
1342         }
1343         else
1344             ++i;
1345     }
1346 }
1347 
ProcessMove(const IntVector2 & cursorPos,const IntVector2 & cursorDeltaPos,int buttons,int qualifiers,Cursor * cursor,bool cursorVisible)1348 void UI::ProcessMove(const IntVector2& cursorPos, const IntVector2& cursorDeltaPos, int buttons, int qualifiers, Cursor* cursor,
1349     bool cursorVisible)
1350 {
1351     if (cursorVisible && dragElementsCount_ > 0 && buttons)
1352     {
1353         Input* input = GetSubsystem<Input>();
1354         bool mouseGrabbed = input->IsMouseGrabbed();
1355         for (HashMap<WeakPtr<UIElement>, UI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
1356         {
1357             WeakPtr<UIElement> dragElement = i->first_;
1358             UI::DragData* dragData = i->second_;
1359 
1360             if (!dragElement)
1361             {
1362                 i = DragElementErase(i);
1363                 continue;
1364             }
1365 
1366             if (!(dragData->dragButtons & buttons))
1367             {
1368                 ++i;
1369                 continue;
1370             }
1371 
1372             // Calculate the position that we should send for this drag event.
1373             IntVector2 sendPos;
1374             if (usingTouchInput_)
1375             {
1376                 dragData->sumPos += cursorDeltaPos;
1377                 sendPos.x_ = dragData->sumPos.x_ / dragData->numDragButtons;
1378                 sendPos.y_ = dragData->sumPos.y_ / dragData->numDragButtons;
1379             }
1380             else
1381             {
1382                 dragData->sumPos = cursorPos;
1383                 sendPos = cursorPos;
1384             }
1385 
1386             if (dragElement->IsEnabled() && dragElement->IsVisible())
1387             {
1388                 // Signal drag begin if distance threshold was exceeded
1389 
1390                 if (dragData->dragBeginPending && !mouseGrabbed)
1391                 {
1392                     IntVector2 beginSendPos;
1393                     beginSendPos.x_ = dragData->dragBeginSumPos.x_ / dragData->numDragButtons;
1394                     beginSendPos.y_ = dragData->dragBeginSumPos.y_ / dragData->numDragButtons;
1395 
1396                     IntVector2 offset = cursorPos - beginSendPos;
1397                     if (Abs(offset.x_) >= dragBeginDistance_ || Abs(offset.y_) >= dragBeginDistance_)
1398                     {
1399                         dragData->dragBeginPending = false;
1400                         dragConfirmedCount_++;
1401                         dragElement->OnDragBegin(dragElement->ScreenToElement(beginSendPos), beginSendPos, buttons, qualifiers,
1402                             cursor);
1403                         SendDragOrHoverEvent(E_DRAGBEGIN, dragElement, beginSendPos, IntVector2::ZERO, dragData);
1404                     }
1405                 }
1406 
1407                 if (!dragData->dragBeginPending)
1408                 {
1409                     dragElement->OnDragMove(dragElement->ScreenToElement(sendPos), sendPos, cursorDeltaPos, buttons, qualifiers,
1410                         cursor);
1411                     SendDragOrHoverEvent(E_DRAGMOVE, dragElement, sendPos, cursorDeltaPos, dragData);
1412                 }
1413             }
1414             else
1415             {
1416                 dragElement->OnDragEnd(dragElement->ScreenToElement(sendPos), sendPos, dragData->dragButtons, buttons, cursor);
1417                 SendDragOrHoverEvent(E_DRAGEND, dragElement, sendPos, IntVector2::ZERO, dragData);
1418                 dragElement.Reset();
1419             }
1420 
1421             ++i;
1422         }
1423     }
1424 }
1425 
SendDragOrHoverEvent(StringHash eventType,UIElement * element,const IntVector2 & screenPos,const IntVector2 & deltaPos,UI::DragData * dragData)1426 void UI::SendDragOrHoverEvent(StringHash eventType, UIElement* element, const IntVector2& screenPos, const IntVector2& deltaPos,
1427     UI::DragData* dragData)
1428 {
1429     if (!element)
1430         return;
1431 
1432     IntVector2 relativePos = element->ScreenToElement(screenPos);
1433 
1434     using namespace DragMove;
1435 
1436     VariantMap& eventData = GetEventDataMap();
1437     eventData[P_ELEMENT] = element;
1438     eventData[P_X] = screenPos.x_;
1439     eventData[P_Y] = screenPos.y_;
1440     eventData[P_ELEMENTX] = relativePos.x_;
1441     eventData[P_ELEMENTY] = relativePos.y_;
1442 
1443     if (eventType == E_DRAGMOVE)
1444     {
1445         eventData[P_DX] = deltaPos.x_;
1446         eventData[P_DY] = deltaPos.y_;
1447     }
1448 
1449     if (dragData)
1450     {
1451         eventData[P_BUTTONS] = dragData->dragButtons;
1452         eventData[P_NUMBUTTONS] = dragData->numDragButtons;
1453     }
1454 
1455     element->SendEvent(eventType, eventData);
1456 }
1457 
SendClickEvent(StringHash eventType,UIElement * beginElement,UIElement * endElement,const IntVector2 & pos,int button,int buttons,int qualifiers)1458 void UI::SendClickEvent(StringHash eventType, UIElement* beginElement, UIElement* endElement, const IntVector2& pos, int button,
1459     int buttons, int qualifiers)
1460 {
1461     VariantMap& eventData = GetEventDataMap();
1462     eventData[UIMouseClick::P_ELEMENT] = endElement;
1463     eventData[UIMouseClick::P_X] = pos.x_;
1464     eventData[UIMouseClick::P_Y] = pos.y_;
1465     eventData[UIMouseClick::P_BUTTON] = button;
1466     eventData[UIMouseClick::P_BUTTONS] = buttons;
1467     eventData[UIMouseClick::P_QUALIFIERS] = qualifiers;
1468 
1469     // For click end events, send also the element the click began on
1470     if (eventType == E_UIMOUSECLICKEND)
1471         eventData[UIMouseClickEnd::P_BEGINELEMENT] = beginElement;
1472 
1473     if (endElement)
1474     {
1475         // Send also element version of the event
1476         if (eventType == E_UIMOUSECLICK)
1477             endElement->SendEvent(E_CLICK, eventData);
1478         else if (eventType == E_UIMOUSECLICKEND)
1479             endElement->SendEvent(E_CLICKEND, eventData);
1480         else if (eventType == E_UIMOUSEDOUBLECLICK)
1481             endElement->SendEvent(E_DOUBLECLICK, eventData);
1482     }
1483 
1484     // Send the global event from the UI subsystem last
1485     SendEvent(eventType, eventData);
1486 }
1487 
HandleScreenMode(StringHash eventType,VariantMap & eventData)1488 void UI::HandleScreenMode(StringHash eventType, VariantMap& eventData)
1489 {
1490     using namespace ScreenMode;
1491 
1492     if (!initialized_)
1493         Initialize();
1494     else
1495         ResizeRootElement();
1496 }
1497 
HandleMouseButtonDown(StringHash eventType,VariantMap & eventData)1498 void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
1499 {
1500     using namespace MouseButtonDown;
1501 
1502     mouseButtons_ = eventData[P_BUTTONS].GetInt();
1503     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
1504     usingTouchInput_ = false;
1505 
1506     IntVector2 cursorPos;
1507     bool cursorVisible;
1508     GetCursorPositionAndVisible(cursorPos, cursorVisible);
1509 
1510     // Handle drag cancelling
1511     ProcessDragCancel();
1512 
1513     Input* input = GetSubsystem<Input>();
1514 
1515     if (!input->IsMouseGrabbed())
1516         ProcessClickBegin(cursorPos, eventData[P_BUTTON].GetInt(), mouseButtons_, qualifiers_, cursor_, cursorVisible);
1517 }
1518 
HandleMouseButtonUp(StringHash eventType,VariantMap & eventData)1519 void UI::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
1520 {
1521     using namespace MouseButtonUp;
1522 
1523     mouseButtons_ = eventData[P_BUTTONS].GetInt();
1524     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
1525 
1526     IntVector2 cursorPos;
1527     bool cursorVisible;
1528     GetCursorPositionAndVisible(cursorPos, cursorVisible);
1529 
1530     ProcessClickEnd(cursorPos, eventData[P_BUTTON].GetInt(), mouseButtons_, qualifiers_, cursor_, cursorVisible);
1531 }
1532 
HandleMouseMove(StringHash eventType,VariantMap & eventData)1533 void UI::HandleMouseMove(StringHash eventType, VariantMap& eventData)
1534 {
1535     using namespace MouseMove;
1536 
1537     mouseButtons_ = eventData[P_BUTTONS].GetInt();
1538     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
1539     usingTouchInput_ = false;
1540 
1541     Input* input = GetSubsystem<Input>();
1542     const IntVector2& rootSize = rootElement_->GetSize();
1543     const IntVector2& rootPos = rootElement_->GetPosition();
1544 
1545     IntVector2 DeltaP = IntVector2(eventData[P_DX].GetInt(), eventData[P_DY].GetInt());
1546 
1547     if (cursor_)
1548     {
1549         if (!input->IsMouseVisible())
1550         {
1551             if (!input->IsMouseLocked())
1552                 cursor_->SetPosition(IntVector2(eventData[P_X].GetInt(), eventData[P_Y].GetInt()));
1553             else if (cursor_->IsVisible())
1554             {
1555                 // Relative mouse motion: move cursor only when visible
1556                 IntVector2 pos = cursor_->GetPosition();
1557                 pos.x_ += eventData[P_DX].GetInt();
1558                 pos.y_ += eventData[P_DY].GetInt();
1559                 pos.x_ = Clamp(pos.x_, rootPos.x_, rootPos.x_ + rootSize.x_ - 1);
1560                 pos.y_ = Clamp(pos.y_, rootPos.y_, rootPos.y_ + rootSize.y_ - 1);
1561                 cursor_->SetPosition(pos);
1562             }
1563         }
1564         else
1565         {
1566             // Absolute mouse motion: move always
1567             cursor_->SetPosition(IntVector2(eventData[P_X].GetInt(), eventData[P_Y].GetInt()));
1568         }
1569     }
1570 
1571     IntVector2 cursorPos;
1572     bool cursorVisible;
1573     GetCursorPositionAndVisible(cursorPos, cursorVisible);
1574 
1575     ProcessMove(cursorPos, DeltaP, mouseButtons_, qualifiers_, cursor_, cursorVisible);
1576 }
1577 
HandleMouseWheel(StringHash eventType,VariantMap & eventData)1578 void UI::HandleMouseWheel(StringHash eventType, VariantMap& eventData)
1579 {
1580     Input* input = GetSubsystem<Input>();
1581     if (input->IsMouseGrabbed())
1582         return;
1583 
1584     using namespace MouseWheel;
1585 
1586     mouseButtons_ = eventData[P_BUTTONS].GetInt();
1587     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
1588     int delta = eventData[P_WHEEL].GetInt();
1589     usingTouchInput_ = false;
1590 
1591     IntVector2 cursorPos;
1592     bool cursorVisible;
1593     GetCursorPositionAndVisible(cursorPos, cursorVisible);
1594 
1595     UIElement* element;
1596     if (!nonFocusedMouseWheel_ && (element = focusElement_))
1597         element->OnWheel(delta, mouseButtons_, qualifiers_);
1598     else
1599     {
1600         // If no element has actual focus or in non-focused mode, get the element at cursor
1601         if (cursorVisible)
1602         {
1603             element = GetElementAt(cursorPos);
1604             if (nonFocusedMouseWheel_)
1605             {
1606                 // Going up the hierarchy chain to find element that could handle mouse wheel
1607                 while (element)
1608                 {
1609                     if (element->GetType() == ListView::GetTypeStatic() ||
1610                         element->GetType() == ScrollView::GetTypeStatic())
1611                         break;
1612                     element = element->GetParent();
1613                 }
1614             }
1615             else
1616                 // If the element itself is not focusable, search for a focusable parent,
1617                 // although the focusable element may not actually handle mouse wheel
1618                 element = GetFocusableElement(element);
1619 
1620             if (element && (nonFocusedMouseWheel_ || element->GetFocusMode() >= FM_FOCUSABLE))
1621                 element->OnWheel(delta, mouseButtons_, qualifiers_);
1622         }
1623     }
1624 }
1625 
HandleTouchBegin(StringHash eventType,VariantMap & eventData)1626 void UI::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
1627 {
1628     Input* input = GetSubsystem<Input>();
1629     if (input->IsMouseGrabbed())
1630         return;
1631 
1632     using namespace TouchBegin;
1633 
1634     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
1635     pos.x_ = int(pos.x_ / uiScale_);
1636     pos.y_ = int(pos.y_ / uiScale_);
1637     usingTouchInput_ = true;
1638 
1639     int touchId = TOUCHID_MASK(eventData[P_TOUCHID].GetInt());
1640     WeakPtr<UIElement> element(GetElementAt(pos));
1641 
1642     if (element)
1643     {
1644         ProcessClickBegin(pos, touchId, touchDragElements_[element], 0, 0, true);
1645         touchDragElements_[element] |= touchId;
1646     }
1647     else
1648         ProcessClickBegin(pos, touchId, touchId, 0, 0, true);
1649 }
1650 
HandleTouchEnd(StringHash eventType,VariantMap & eventData)1651 void UI::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
1652 {
1653     using namespace TouchEnd;
1654 
1655     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
1656     pos.x_ = int(pos.x_ / uiScale_);
1657     pos.y_ = int(pos.y_ / uiScale_);
1658 
1659     // Get the touch index
1660     int touchId = TOUCHID_MASK(eventData[P_TOUCHID].GetInt());
1661 
1662     // Transmit hover end to the position where the finger was lifted
1663     WeakPtr<UIElement> element(GetElementAt(pos));
1664 
1665     // Clear any drag events that were using the touch id
1666     for (HashMap<WeakPtr<UIElement>, int>::Iterator i = touchDragElements_.Begin(); i != touchDragElements_.End();)
1667     {
1668         int touches = i->second_;
1669         if (touches & touchId)
1670             i = touchDragElements_.Erase(i);
1671         else
1672             ++i;
1673     }
1674 
1675     if (element && element->IsEnabled())
1676         element->OnHover(element->ScreenToElement(pos), pos, 0, 0, 0);
1677 
1678     ProcessClickEnd(pos, touchId, 0, 0, 0, true);
1679 }
1680 
HandleTouchMove(StringHash eventType,VariantMap & eventData)1681 void UI::HandleTouchMove(StringHash eventType, VariantMap& eventData)
1682 {
1683     using namespace TouchMove;
1684 
1685     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
1686     IntVector2 deltaPos(eventData[P_DX].GetInt(), eventData[P_DY].GetInt());
1687     pos.x_ = int(pos.x_ / uiScale_);
1688     pos.y_ = int(pos.y_ / uiScale_);
1689     deltaPos.x_ = int(deltaPos.x_ / uiScale_);
1690     deltaPos.y_ = int(deltaPos.y_ / uiScale_);
1691     usingTouchInput_ = true;
1692 
1693     int touchId = TOUCHID_MASK(eventData[P_TOUCHID].GetInt());
1694 
1695     ProcessMove(pos, deltaPos, touchId, 0, 0, true);
1696 }
1697 
HandleKeyDown(StringHash eventType,VariantMap & eventData)1698 void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
1699 {
1700     using namespace KeyDown;
1701 
1702     mouseButtons_ = eventData[P_BUTTONS].GetInt();
1703     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
1704     int key = eventData[P_KEY].GetInt();
1705 
1706     // Cancel UI dragging
1707     if (key == KEY_ESCAPE && dragElementsCount_ > 0)
1708     {
1709         ProcessDragCancel();
1710 
1711         return;
1712     }
1713 
1714     // Dismiss modal element if any when ESC key is pressed
1715     if (key == KEY_ESCAPE && HasModalElement())
1716     {
1717         UIElement* element = rootModalElement_->GetChild(rootModalElement_->GetNumChildren() - 1);
1718         if (element->GetVars().Contains(VAR_ORIGIN))
1719             // If it is a popup, dismiss by defocusing it
1720             SetFocusElement(0);
1721         else
1722         {
1723             // If it is a modal window, by resetting its modal flag
1724             Window* window = dynamic_cast<Window*>(element);
1725             if (window && window->GetModalAutoDismiss())
1726                 window->SetModal(false);
1727         }
1728 
1729         return;
1730     }
1731 
1732     UIElement* element = focusElement_;
1733     if (element)
1734     {
1735         // Switch focus between focusable elements in the same top level window
1736         if (key == KEY_TAB)
1737         {
1738             UIElement* topLevel = element->GetParent();
1739             while (topLevel && topLevel->GetParent() != rootElement_ && topLevel->GetParent() != rootModalElement_)
1740                 topLevel = topLevel->GetParent();
1741             if (topLevel)
1742             {
1743                 topLevel->GetChildren(tempElements_, true);
1744                 for (PODVector<UIElement*>::Iterator i = tempElements_.Begin(); i != tempElements_.End();)
1745                 {
1746                     if ((*i)->GetFocusMode() < FM_FOCUSABLE)
1747                         i = tempElements_.Erase(i);
1748                     else
1749                         ++i;
1750                 }
1751                 for (unsigned i = 0; i < tempElements_.Size(); ++i)
1752                 {
1753                     if (tempElements_[i] == element)
1754                     {
1755                         int dir = (qualifiers_ & QUAL_SHIFT) ? -1 : 1;
1756                         unsigned nextIndex = (tempElements_.Size() + i + dir) % tempElements_.Size();
1757                         UIElement* next = tempElements_[nextIndex];
1758                         SetFocusElement(next, true);
1759                         return;
1760                     }
1761                 }
1762             }
1763         }
1764         // Defocus the element
1765         else if (key == KEY_ESCAPE && element->GetFocusMode() == FM_FOCUSABLE_DEFOCUSABLE)
1766             element->SetFocus(false);
1767         // If none of the special keys, pass the key to the focused element
1768         else
1769             element->OnKey(key, mouseButtons_, qualifiers_);
1770     }
1771 }
1772 
HandleTextInput(StringHash eventType,VariantMap & eventData)1773 void UI::HandleTextInput(StringHash eventType, VariantMap& eventData)
1774 {
1775     using namespace TextInput;
1776 
1777     UIElement* element = focusElement_;
1778     if (element)
1779         element->OnTextInput(eventData[P_TEXT].GetString());
1780 }
1781 
HandleBeginFrame(StringHash eventType,VariantMap & eventData)1782 void UI::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
1783 {
1784     // If have a cursor, and a drag is not going on, reset the cursor shape. Application logic that wants to apply
1785     // custom shapes can do it after this, but needs to do it each frame
1786     if (cursor_ && dragElementsCount_ == 0)
1787         cursor_->SetShape(CS_NORMAL);
1788 }
1789 
HandlePostUpdate(StringHash eventType,VariantMap & eventData)1790 void UI::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
1791 {
1792     using namespace PostUpdate;
1793 
1794     Update(eventData[P_TIMESTEP].GetFloat());
1795 }
1796 
HandleRenderUpdate(StringHash eventType,VariantMap & eventData)1797 void UI::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
1798 {
1799     RenderUpdate();
1800 }
1801 
HandleDropFile(StringHash eventType,VariantMap & eventData)1802 void UI::HandleDropFile(StringHash eventType, VariantMap& eventData)
1803 {
1804     Input* input = GetSubsystem<Input>();
1805 
1806     // Sending the UI variant of the event only makes sense if the OS cursor is visible (not locked to window center)
1807     if (input->IsMouseVisible())
1808     {
1809         IntVector2 screenPos = input->GetMousePosition();
1810         screenPos.x_ = int(screenPos.x_ / uiScale_);
1811         screenPos.y_ = int(screenPos.y_ / uiScale_);
1812 
1813         UIElement* element = GetElementAt(screenPos);
1814 
1815         using namespace UIDropFile;
1816 
1817         VariantMap uiEventData;
1818         uiEventData[P_FILENAME] = eventData[P_FILENAME];
1819         uiEventData[P_X] = screenPos.x_;
1820         uiEventData[P_Y] = screenPos.y_;
1821         uiEventData[P_ELEMENT] = element;
1822 
1823         if (element)
1824         {
1825             IntVector2 relativePos = element->ScreenToElement(screenPos);
1826             uiEventData[P_ELEMENTX] = relativePos.x_;
1827             uiEventData[P_ELEMENTY] = relativePos.y_;
1828         }
1829 
1830         SendEvent(E_UIDROPFILE, uiEventData);
1831     }
1832 }
1833 
DragElementErase(HashMap<WeakPtr<UIElement>,UI::DragData * >::Iterator i)1834 HashMap<WeakPtr<UIElement>, UI::DragData*>::Iterator UI::DragElementErase(HashMap<WeakPtr<UIElement>, UI::DragData*>::Iterator i)
1835 {
1836     // If running the engine frame in response to an event (re-entering UI frame logic) the dragElements_ may already be empty
1837     if (dragElements_.Empty())
1838         return dragElements_.End();
1839 
1840     dragElementsConfirmed_.Clear();
1841 
1842     DragData* dragData = i->second_;
1843 
1844     if (!dragData->dragBeginPending)
1845         --dragConfirmedCount_;
1846     i = dragElements_.Erase(i);
1847     --dragElementsCount_;
1848 
1849     delete dragData;
1850     return i;
1851 }
1852 
ProcessDragCancel()1853 void UI::ProcessDragCancel()
1854 {
1855     // How to tell difference between drag cancel and new selection on multi-touch?
1856     if (usingTouchInput_)
1857         return;
1858 
1859     IntVector2 cursorPos;
1860     bool cursorVisible;
1861     GetCursorPositionAndVisible(cursorPos, cursorVisible);
1862 
1863     for (HashMap<WeakPtr<UIElement>, UI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
1864     {
1865         WeakPtr<UIElement> dragElement = i->first_;
1866         UI::DragData* dragData = i->second_;
1867 
1868         if (dragElement && dragElement->IsEnabled() && dragElement->IsVisible() && !dragData->dragBeginPending)
1869         {
1870             dragElement->OnDragCancel(dragElement->ScreenToElement(cursorPos), cursorPos, dragData->dragButtons, mouseButtons_,
1871                 cursor_);
1872             SendDragOrHoverEvent(E_DRAGCANCEL, dragElement, cursorPos, IntVector2::ZERO, dragData);
1873             i = DragElementErase(i);
1874         }
1875         else
1876             ++i;
1877     }
1878 }
1879 
SumTouchPositions(UI::DragData * dragData,const IntVector2 & oldSendPos)1880 IntVector2 UI::SumTouchPositions(UI::DragData* dragData, const IntVector2& oldSendPos)
1881 {
1882     IntVector2 sendPos = oldSendPos;
1883     if (usingTouchInput_)
1884     {
1885         int buttons = dragData->dragButtons;
1886         dragData->sumPos = IntVector2::ZERO;
1887         Input* input = GetSubsystem<Input>();
1888         for (int i = 0; (1 << i) <= buttons; i++)
1889         {
1890             if ((1 << i) & buttons)
1891             {
1892                 TouchState* ts = input->GetTouch((unsigned)i);
1893                 if (!ts)
1894                     break;
1895                 IntVector2 pos = ts->position_;
1896                 dragData->sumPos.x_ += (int)(pos.x_ / uiScale_);
1897                 dragData->sumPos.y_ += (int)(pos.y_ / uiScale_);
1898             }
1899         }
1900         sendPos.x_ = dragData->sumPos.x_ / dragData->numDragButtons;
1901         sendPos.y_ = dragData->sumPos.y_ / dragData->numDragButtons;
1902     }
1903     return sendPos;
1904 }
1905 
ResizeRootElement()1906 void UI::ResizeRootElement()
1907 {
1908     IntVector2 effectiveSize = GetEffectiveRootElementSize();
1909     rootElement_->SetSize(effectiveSize);
1910     rootModalElement_->SetSize(effectiveSize);
1911 }
1912 
GetEffectiveRootElementSize(bool applyScale) const1913 IntVector2 UI::GetEffectiveRootElementSize(bool applyScale) const
1914 {
1915     // Use a fake size in headless mode
1916     IntVector2 size = graphics_ ? IntVector2(graphics_->GetWidth(), graphics_->GetHeight()) : IntVector2(1024, 768);
1917     if (customSize_.x_ > 0 && customSize_.y_ > 0)
1918         size = customSize_;
1919 
1920     if (applyScale)
1921     {
1922         size.x_ = (int)((float)size.x_ / uiScale_ + 0.5f);
1923         size.y_ = (int)((float)size.y_ / uiScale_ + 0.5f);
1924     }
1925 
1926     return size;
1927 }
1928 
RegisterUILibrary(Context * context)1929 void RegisterUILibrary(Context* context)
1930 {
1931     Font::RegisterObject(context);
1932 
1933     UIElement::RegisterObject(context);
1934     BorderImage::RegisterObject(context);
1935     Sprite::RegisterObject(context);
1936     Button::RegisterObject(context);
1937     CheckBox::RegisterObject(context);
1938     Cursor::RegisterObject(context);
1939     Text::RegisterObject(context);
1940     Text3D::RegisterObject(context);
1941     Window::RegisterObject(context);
1942     View3D::RegisterObject(context);
1943     LineEdit::RegisterObject(context);
1944     Slider::RegisterObject(context);
1945     ScrollBar::RegisterObject(context);
1946     ScrollView::RegisterObject(context);
1947     ListView::RegisterObject(context);
1948     Menu::RegisterObject(context);
1949     DropDownList::RegisterObject(context);
1950     FileSelector::RegisterObject(context);
1951     MessageBox::RegisterObject(context);
1952     ProgressBar::RegisterObject(context);
1953     ToolTip::RegisterObject(context);
1954 }
1955 
1956 }
1957