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