1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIControl.h"
10 
11 #include "GUIAction.h"
12 #include "GUIComponent.h"
13 #include "GUIControlProfiler.h"
14 #include "GUIInfoManager.h"
15 #include "GUIMessage.h"
16 #include "GUITexture.h"
17 #include "GUIWindowManager.h"
18 #include "ServiceBroker.h"
19 #include "input/InputManager.h"
20 #include "input/Key.h"
21 #include "input/mouse/MouseStat.h"
22 #include "utils/log.h"
23 
24 using namespace KODI::GUILIB;
25 
CGUIControl()26 CGUIControl::CGUIControl()
27 {
28   m_hasProcessed = false;
29   m_bHasFocus = false;
30   m_controlID = 0;
31   m_parentID = 0;
32   m_visible = VISIBLE;
33   m_visibleFromSkinCondition = true;
34   m_forceHidden = false;
35   m_enabled = true;
36   m_posX = 0;
37   m_posY = 0;
38   m_width = 0;
39   m_height = 0;
40   ControlType = GUICONTROL_UNKNOWN;
41   m_bInvalidated = true;
42   m_bAllocated=false;
43   m_parentControl = NULL;
44   m_hasCamera = false;
45   m_pushedUpdates = false;
46   m_pulseOnSelect = false;
47   m_controlDirtyState = DIRTY_STATE_CONTROL;
48   m_stereo = 0.0f;
49   m_controlStats = nullptr;
50 }
51 
CGUIControl(int parentID,int controlID,float posX,float posY,float width,float height)52 CGUIControl::CGUIControl(int parentID, int controlID, float posX, float posY, float width, float height)
53 : m_hitRect(posX, posY, posX + width, posY + height),
54   m_diffuseColor(0xffffffff)
55 {
56   m_posX = posX;
57   m_posY = posY;
58   m_width = width;
59   m_height = height;
60   m_bHasFocus = false;
61   m_controlID = controlID;
62   m_parentID = parentID;
63   m_visible = VISIBLE;
64   m_visibleFromSkinCondition = true;
65   m_forceHidden = false;
66   m_enabled = true;
67   ControlType = GUICONTROL_UNKNOWN;
68   m_bInvalidated = true;
69   m_bAllocated=false;
70   m_hasProcessed = false;
71   m_parentControl = NULL;
72   m_hasCamera = false;
73   m_pushedUpdates = false;
74   m_pulseOnSelect = false;
75   m_controlDirtyState = DIRTY_STATE_CONTROL;
76   m_stereo = 0.0f;
77   m_controlStats = nullptr;
78 }
79 
80 CGUIControl::CGUIControl(const CGUIControl &) = default;
81 
82 CGUIControl::~CGUIControl(void) = default;
83 
AllocResources()84 void CGUIControl::AllocResources()
85 {
86   m_hasProcessed = false;
87   m_bInvalidated = true;
88   m_bAllocated=true;
89 }
90 
FreeResources(bool immediately)91 void CGUIControl::FreeResources(bool immediately)
92 {
93   if (m_bAllocated)
94   {
95     // Reset our animation states - not conditional anims though.
96     // I'm not sure if this is needed for most cases anyway.  I believe it's only here
97     // because some windows aren't loaded on demand
98     for (unsigned int i = 0; i < m_animations.size(); i++)
99     {
100       CAnimation &anim = m_animations[i];
101       if (anim.GetType() != ANIM_TYPE_CONDITIONAL)
102         anim.ResetAnimation();
103     }
104     m_bAllocated=false;
105   }
106   m_hasProcessed = false;
107 }
108 
DynamicResourceAlloc(bool bOnOff)109 void CGUIControl::DynamicResourceAlloc(bool bOnOff)
110 {
111 
112 }
113 
114 // the main processing routine.
115 // 1. animate and set animation transform
116 // 2. if visible, process
117 // 3. reset the animation transform
DoProcess(unsigned int currentTime,CDirtyRegionList & dirtyregions)118 void CGUIControl::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
119 {
120   CRect dirtyRegion = m_renderRegion;
121 
122   bool changed = (m_controlDirtyState & DIRTY_STATE_CONTROL) != 0 || (m_bInvalidated && IsVisible());
123   m_controlDirtyState = 0;
124 
125   if (Animate(currentTime))
126     MarkDirtyRegion();
127 
128   if (IsVisible())
129   {
130     m_cachedTransform = CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(m_transform);
131     if (m_hasCamera)
132       CServiceBroker::GetWinSystem()->GetGfxContext().SetCameraPosition(m_camera);
133 
134     Process(currentTime, dirtyregions);
135     m_bInvalidated = false;
136 
137     if (dirtyRegion != m_renderRegion)
138     {
139       dirtyRegion.Union(m_renderRegion);
140       changed = true;
141     }
142 
143     if (m_hasCamera)
144       CServiceBroker::GetWinSystem()->GetGfxContext().RestoreCameraPosition();
145     CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
146   }
147 
148   UpdateControlStats();
149 
150   changed |= (m_controlDirtyState & DIRTY_STATE_CONTROL) != 0;
151 
152   if (changed)
153   {
154     dirtyregions.emplace_back(dirtyRegion);
155   }
156 }
157 
Process(unsigned int currentTime,CDirtyRegionList & dirtyregions)158 void CGUIControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
159 {
160   // update our render region
161   m_renderRegion = CServiceBroker::GetWinSystem()->GetGfxContext().GenerateAABB(CalcRenderRegion());
162   m_hasProcessed = true;
163 }
164 
165 // the main render routine.
166 // 1. set the animation transform
167 // 2. if visible, paint
168 // 3. reset the animation transform
DoRender()169 void CGUIControl::DoRender()
170 {
171   if (IsVisible())
172   {
173     bool hasStereo = m_stereo != 0.0
174                   && CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RENDER_STEREO_MODE_MONO
175                   && CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RENDER_STEREO_MODE_OFF;
176 
177     CServiceBroker::GetWinSystem()->GetGfxContext().SetTransform(m_cachedTransform);
178     if (m_hasCamera)
179       CServiceBroker::GetWinSystem()->GetGfxContext().SetCameraPosition(m_camera);
180     if (hasStereo)
181       CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoFactor(m_stereo);
182 
183     GUIPROFILER_RENDER_BEGIN(this);
184 
185     if (m_hitColor != 0xffffffff)
186     {
187       UTILS::Color color = CServiceBroker::GetWinSystem()->GetGfxContext().MergeAlpha(m_hitColor);
188       CGUITexture::DrawQuad(CServiceBroker::GetWinSystem()->GetGfxContext().GenerateAABB(m_hitRect), color);
189     }
190 
191     Render();
192 
193     GUIPROFILER_RENDER_END(this);
194 
195     if (hasStereo)
196       CServiceBroker::GetWinSystem()->GetGfxContext().RestoreStereoFactor();
197     if (m_hasCamera)
198       CServiceBroker::GetWinSystem()->GetGfxContext().RestoreCameraPosition();
199     CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
200   }
201 }
202 
OnAction(const CAction & action)203 bool CGUIControl::OnAction(const CAction &action)
204 {
205   if (HasFocus())
206   {
207     switch (action.GetID())
208     {
209     case ACTION_MOVE_DOWN:
210       OnDown();
211       return true;
212 
213     case ACTION_MOVE_UP:
214       OnUp();
215       return true;
216 
217     case ACTION_MOVE_LEFT:
218       OnLeft();
219       return true;
220 
221     case ACTION_MOVE_RIGHT:
222       OnRight();
223       return true;
224 
225     case ACTION_SHOW_INFO:
226       return OnInfo();
227 
228     case ACTION_NAV_BACK:
229       return OnBack();
230 
231     case ACTION_NEXT_CONTROL:
232       OnNextControl();
233       return true;
234 
235     case ACTION_PREV_CONTROL:
236       OnPrevControl();
237       return true;
238     }
239   }
240   return false;
241 }
242 
Navigate(int direction) const243 bool CGUIControl::Navigate(int direction) const
244 {
245   if (HasFocus())
246   {
247     CGUIMessage msg(GUI_MSG_MOVE, GetParentID(), GetID(), direction);
248     return SendWindowMessage(msg);
249   }
250   return false;
251 }
252 
253 // Movement controls (derived classes can override)
OnUp()254 void CGUIControl::OnUp()
255 {
256   Navigate(ACTION_MOVE_UP);
257 }
258 
OnDown()259 void CGUIControl::OnDown()
260 {
261   Navigate(ACTION_MOVE_DOWN);
262 }
263 
OnLeft()264 void CGUIControl::OnLeft()
265 {
266   Navigate(ACTION_MOVE_LEFT);
267 }
268 
OnRight()269 void CGUIControl::OnRight()
270 {
271   Navigate(ACTION_MOVE_RIGHT);
272 }
273 
OnBack()274 bool CGUIControl::OnBack()
275 {
276   return Navigate(ACTION_NAV_BACK);
277 }
278 
OnInfo()279 bool CGUIControl::OnInfo()
280 {
281   CGUIAction action = GetAction(ACTION_SHOW_INFO);
282   if (action.HasAnyActions())
283     return action.ExecuteActions(GetID(), GetParentID());
284   return false;
285 }
286 
OnNextControl()287 void CGUIControl::OnNextControl()
288 {
289   Navigate(ACTION_NEXT_CONTROL);
290 }
291 
OnPrevControl()292 void CGUIControl::OnPrevControl()
293 {
294   Navigate(ACTION_PREV_CONTROL);
295 }
296 
SendWindowMessage(CGUIMessage & message) const297 bool CGUIControl::SendWindowMessage(CGUIMessage &message) const
298 {
299   CGUIWindow *pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(GetParentID());
300   if (pWindow)
301     return pWindow->OnMessage(message);
302   return CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message);
303 }
304 
GetID(void) const305 int CGUIControl::GetID(void) const
306 {
307   return m_controlID;
308 }
309 
310 
GetParentID(void) const311 int CGUIControl::GetParentID(void) const
312 {
313   return m_parentID;
314 }
315 
HasFocus(void) const316 bool CGUIControl::HasFocus(void) const
317 {
318   return m_bHasFocus;
319 }
320 
SetFocus(bool focus)321 void CGUIControl::SetFocus(bool focus)
322 {
323   if (m_bHasFocus && !focus)
324     QueueAnimation(ANIM_TYPE_UNFOCUS);
325   else if (!m_bHasFocus && focus)
326     QueueAnimation(ANIM_TYPE_FOCUS);
327   m_bHasFocus = focus;
328 }
329 
OnMessage(CGUIMessage & message)330 bool CGUIControl::OnMessage(CGUIMessage& message)
331 {
332   if ( message.GetControlId() == GetID() )
333   {
334     switch (message.GetMessage() )
335     {
336     case GUI_MSG_SETFOCUS:
337       // if control is disabled then move 2 the next control
338       if ( !CanFocus() )
339       {
340         CLog::Log(LOGERROR, "Control %u in window %u has been asked to focus, "
341                             "but it can't",
342                   GetID(), GetParentID());
343         return false;
344       }
345       SetFocus(true);
346       {
347         // inform our parent window that this has happened
348         CGUIMessage message(GUI_MSG_FOCUSED, GetParentID(), GetID());
349         if (m_parentControl)
350           m_parentControl->OnMessage(message);
351       }
352       return true;
353       break;
354 
355     case GUI_MSG_LOSTFOCUS:
356       {
357         SetFocus(false);
358         // and tell our parent so it can unfocus
359         if (m_parentControl)
360           m_parentControl->OnMessage(message);
361         return true;
362       }
363       break;
364 
365     case GUI_MSG_VISIBLE:
366       SetVisible(true, true);
367       return true;
368       break;
369 
370     case GUI_MSG_HIDDEN:
371       SetVisible(false);
372       return true;
373 
374       // Note that the skin <enable> tag will override these messages
375     case GUI_MSG_ENABLED:
376       SetEnabled(true);
377       return true;
378 
379     case GUI_MSG_DISABLED:
380       SetEnabled(false);
381       return true;
382 
383     case GUI_MSG_WINDOW_RESIZE:
384       // invalidate controls to get them to recalculate sizing information
385       SetInvalid();
386       return true;
387     }
388   }
389   return false;
390 }
391 
CanFocus() const392 bool CGUIControl::CanFocus() const
393 {
394   if (!IsVisible() && !m_allowHiddenFocus) return false;
395   if (IsDisabled()) return false;
396   return true;
397 }
398 
IsVisible() const399 bool CGUIControl::IsVisible() const
400 {
401   if (m_forceHidden) return false;
402   return m_visible == VISIBLE;
403 }
404 
IsDisabled() const405 bool CGUIControl::IsDisabled() const
406 {
407   return !m_enabled;
408 }
409 
SetEnabled(bool bEnable)410 void CGUIControl::SetEnabled(bool bEnable)
411 {
412   if (bEnable != m_enabled)
413   {
414     m_enabled = bEnable;
415     SetInvalid();
416   }
417 }
418 
SetEnableCondition(const std::string & expression)419 void CGUIControl::SetEnableCondition(const std::string &expression)
420 {
421   if (expression == "true")
422     m_enabled = true;
423   else if (expression == "false")
424     m_enabled = false;
425   else
426     m_enableCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(expression, GetParentID());
427 }
428 
SetPosition(float posX,float posY)429 void CGUIControl::SetPosition(float posX, float posY)
430 {
431   if ((m_posX != posX) || (m_posY != posY))
432   {
433     MarkDirtyRegion();
434 
435     m_hitRect += CPoint(posX - m_posX, posY - m_posY);
436     m_posX = posX;
437     m_posY = posY;
438 
439     SetInvalid();
440   }
441 }
442 
SetColorDiffuse(const GUIINFO::CGUIInfoColor & color)443 bool CGUIControl::SetColorDiffuse(const GUIINFO::CGUIInfoColor &color)
444 {
445   bool changed = m_diffuseColor != color;
446   m_diffuseColor = color;
447   return changed;
448 }
449 
GetXPosition() const450 float CGUIControl::GetXPosition() const
451 {
452   return m_posX;
453 }
454 
GetYPosition() const455 float CGUIControl::GetYPosition() const
456 {
457   return m_posY;
458 }
459 
GetWidth() const460 float CGUIControl::GetWidth() const
461 {
462   return m_width;
463 }
464 
GetHeight() const465 float CGUIControl::GetHeight() const
466 {
467   return m_height;
468 }
469 
MarkDirtyRegion(const unsigned int dirtyState)470 void CGUIControl::MarkDirtyRegion(const unsigned int dirtyState)
471 {
472   if (!m_controlDirtyState && m_parentControl)
473     m_parentControl->MarkDirtyRegion(DIRTY_STATE_CHILD);
474 
475   m_controlDirtyState |= dirtyState;
476 }
477 
CalcRenderRegion() const478 CRect CGUIControl::CalcRenderRegion() const
479 {
480   CPoint tl(GetXPosition(), GetYPosition());
481   CPoint br(tl.x + GetWidth(), tl.y + GetHeight());
482 
483   return CRect(tl.x, tl.y, br.x, br.y);
484 }
485 
SetActions(const ActionMap & actions)486 void CGUIControl::SetActions(const ActionMap &actions)
487 {
488   m_actions = actions;
489 }
490 
SetAction(int actionID,const CGUIAction & action,bool replace)491 void CGUIControl::SetAction(int actionID, const CGUIAction &action, bool replace /*= true*/)
492 {
493   ActionMap::iterator i = m_actions.find(actionID);
494   if (i == m_actions.end() || !i->second.HasAnyActions() || replace)
495     m_actions[actionID] = action;
496 }
497 
SetWidth(float width)498 void CGUIControl::SetWidth(float width)
499 {
500   if (m_width != width)
501   {
502     MarkDirtyRegion();
503     m_width = width;
504     m_hitRect.x2 = m_hitRect.x1 + width;
505     SetInvalid();
506   }
507 }
508 
SetHeight(float height)509 void CGUIControl::SetHeight(float height)
510 {
511   if (m_height != height)
512   {
513     MarkDirtyRegion();
514     m_height = height;
515     m_hitRect.y2 = m_hitRect.y1 + height;
516     SetInvalid();
517   }
518 }
519 
SetVisible(bool bVisible,bool setVisState)520 void CGUIControl::SetVisible(bool bVisible, bool setVisState)
521 {
522   if (bVisible && setVisState)
523   {  //! @todo currently we only update m_visible from GUI_MSG_VISIBLE (SET_CONTROL_VISIBLE)
524      //!       otherwise we just set m_forceHidden
525     GUIVISIBLE visible;
526     if (m_visibleCondition)
527       visible = m_visibleCondition->Get() ? VISIBLE : HIDDEN;
528     else
529       visible = VISIBLE;
530     if (visible != m_visible)
531     {
532       m_visible = visible;
533       SetInvalid();
534     }
535   }
536   if (m_forceHidden == bVisible)
537   {
538     m_forceHidden = !bVisible;
539     SetInvalid();
540     if (m_forceHidden)
541       MarkDirtyRegion();
542   }
543   if (m_forceHidden)
544   { // reset any visible animations that are in process
545     if (IsAnimating(ANIM_TYPE_VISIBLE))
546     {
547 //        CLog::Log(LOGDEBUG, "Resetting visible animation on control %i (we are %s)", m_controlID, m_visible ? "visible" : "hidden");
548       CAnimation *visibleAnim = GetAnimation(ANIM_TYPE_VISIBLE);
549       if (visibleAnim) visibleAnim->ResetAnimation();
550     }
551   }
552 }
553 
HitTest(const CPoint & point) const554 bool CGUIControl::HitTest(const CPoint &point) const
555 {
556   return m_hitRect.PtInRect(point);
557 }
558 
SendMouseEvent(const CPoint & point,const CMouseEvent & event)559 EVENT_RESULT CGUIControl::SendMouseEvent(const CPoint &point, const CMouseEvent &event)
560 {
561   CPoint childPoint(point);
562   m_transform.InverseTransformPosition(childPoint.x, childPoint.y);
563   if (!CanFocusFromPoint(childPoint))
564     return EVENT_RESULT_UNHANDLED;
565 
566   bool handled = event.m_id != ACTION_MOUSE_MOVE || OnMouseOver(childPoint);
567   EVENT_RESULT ret = OnMouseEvent(childPoint, event);
568   if (ret)
569     return ret;
570   return (handled && (event.m_id == ACTION_MOUSE_MOVE)) ? EVENT_RESULT_HANDLED : EVENT_RESULT_UNHANDLED;
571 }
572 
573 // override this function to implement custom mouse behaviour
OnMouseOver(const CPoint & point)574 bool CGUIControl::OnMouseOver(const CPoint &point)
575 {
576   if (CServiceBroker::GetInputManager().GetMouseState() != MOUSE_STATE_DRAG)
577     CServiceBroker::GetInputManager().SetMouseState(MOUSE_STATE_FOCUS);
578   if (!CanFocus()) return false;
579   if (!HasFocus())
580   {
581     CGUIMessage msg(GUI_MSG_SETFOCUS, GetParentID(), GetID());
582     OnMessage(msg);
583   }
584   return true;
585 }
586 
UpdateVisibility(const CGUIListItem * item)587 void CGUIControl::UpdateVisibility(const CGUIListItem *item)
588 {
589   if (m_visibleCondition)
590   {
591     bool bWasVisible = m_visibleFromSkinCondition;
592     m_visibleFromSkinCondition = m_visibleCondition->Get(item);
593     if (!bWasVisible && m_visibleFromSkinCondition)
594     { // automatic change of visibility - queue the in effect
595   //    CLog::Log(LOGDEBUG, "Visibility changed to visible for control id %i", m_controlID);
596       QueueAnimation(ANIM_TYPE_VISIBLE);
597     }
598     else if (bWasVisible && !m_visibleFromSkinCondition)
599     { // automatic change of visibility - do the out effect
600   //    CLog::Log(LOGDEBUG, "Visibility changed to hidden for control id %i", m_controlID);
601       QueueAnimation(ANIM_TYPE_HIDDEN);
602     }
603   }
604   // check for conditional animations
605   for (unsigned int i = 0; i < m_animations.size(); i++)
606   {
607     CAnimation &anim = m_animations[i];
608     if (anim.GetType() == ANIM_TYPE_CONDITIONAL)
609       anim.UpdateCondition(item);
610   }
611   // and check for conditional enabling - note this overrides SetEnabled() from the code currently
612   // this may need to be reviewed at a later date
613   bool enabled = m_enabled;
614   if (m_enableCondition)
615     m_enabled = m_enableCondition->Get(item);
616 
617   if (m_enabled != enabled)
618     MarkDirtyRegion();
619 
620   m_allowHiddenFocus.Update(item);
621   if (UpdateColors())
622     MarkDirtyRegion();
623   // and finally, update our control information (if not pushed)
624   if (!m_pushedUpdates)
625     UpdateInfo(item);
626 }
627 
UpdateColors()628 bool CGUIControl::UpdateColors()
629 {
630   return m_diffuseColor.Update();
631 }
632 
SetInitialVisibility()633 void CGUIControl::SetInitialVisibility()
634 {
635   if (m_visibleCondition)
636   {
637     m_visibleFromSkinCondition = m_visibleCondition->Get();
638     m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
639   //  CLog::Log(LOGDEBUG, "Set initial visibility for control %i: %s", m_controlID, m_visible == VISIBLE ? "visible" : "hidden");
640   }
641   else if (m_visible == DELAYED)
642     m_visible = VISIBLE;
643   // and handle animation conditions as well
644   for (unsigned int i = 0; i < m_animations.size(); i++)
645   {
646     CAnimation &anim = m_animations[i];
647     if (anim.GetType() == ANIM_TYPE_CONDITIONAL)
648       anim.SetInitialCondition();
649   }
650   // and check for conditional enabling - note this overrides SetEnabled() from the code currently
651   // this may need to be reviewed at a later date
652   if (m_enableCondition)
653     m_enabled = m_enableCondition->Get();
654   m_allowHiddenFocus.Update();
655   UpdateColors();
656 
657   MarkDirtyRegion();
658 }
659 
SetVisibleCondition(const std::string & expression,const std::string & allowHiddenFocus)660 void CGUIControl::SetVisibleCondition(const std::string &expression, const std::string &allowHiddenFocus)
661 {
662   if (expression == "true")
663     m_visible = VISIBLE;
664   else if (expression == "false")
665     m_visible = HIDDEN;
666   else  // register with the infomanager for updates
667     m_visibleCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(expression, GetParentID());
668   m_allowHiddenFocus.Parse(allowHiddenFocus, GetParentID());
669 }
670 
SetAnimations(const std::vector<CAnimation> & animations)671 void CGUIControl::SetAnimations(const std::vector<CAnimation> &animations)
672 {
673   m_animations = animations;
674   MarkDirtyRegion();
675 }
676 
ResetAnimation(ANIMATION_TYPE type)677 void CGUIControl::ResetAnimation(ANIMATION_TYPE type)
678 {
679   MarkDirtyRegion();
680 
681   for (unsigned int i = 0; i < m_animations.size(); i++)
682   {
683     if (m_animations[i].GetType() == type)
684       m_animations[i].ResetAnimation();
685   }
686 }
687 
ResetAnimations()688 void CGUIControl::ResetAnimations()
689 {
690   MarkDirtyRegion();
691 
692   for (unsigned int i = 0; i < m_animations.size(); i++)
693     m_animations[i].ResetAnimation();
694 
695   MarkDirtyRegion();
696 }
697 
CheckAnimation(ANIMATION_TYPE animType)698 bool CGUIControl::CheckAnimation(ANIMATION_TYPE animType)
699 {
700   // rule out the animations we shouldn't perform
701   if (!IsVisible() || !HasProcessed())
702   { // hidden or never processed - don't allow exit or entry animations for this control
703     if (animType == ANIM_TYPE_WINDOW_CLOSE)
704     { // could be animating a (delayed) window open anim, so reset it
705       ResetAnimation(ANIM_TYPE_WINDOW_OPEN);
706       return false;
707     }
708   }
709   if (!IsVisible())
710   { // hidden - only allow hidden anims if we're animating a visible anim
711     if (animType == ANIM_TYPE_HIDDEN && !IsAnimating(ANIM_TYPE_VISIBLE))
712     {
713       // update states to force it hidden
714       UpdateStates(animType, ANIM_PROCESS_NORMAL, ANIM_STATE_APPLIED);
715       return false;
716     }
717     if (animType == ANIM_TYPE_WINDOW_OPEN)
718       return false;
719   }
720   return true;
721 }
722 
QueueAnimation(ANIMATION_TYPE animType)723 void CGUIControl::QueueAnimation(ANIMATION_TYPE animType)
724 {
725   if (!CheckAnimation(animType))
726     return;
727 
728   MarkDirtyRegion();
729 
730   CAnimation *reverseAnim = GetAnimation((ANIMATION_TYPE)-animType, false);
731   CAnimation *forwardAnim = GetAnimation(animType);
732   // we first check whether the reverse animation is in progress (and reverse it)
733   // then we check for the normal animation, and queue it
734   if (reverseAnim && reverseAnim->IsReversible() && (reverseAnim->GetState() == ANIM_STATE_IN_PROCESS || reverseAnim->GetState() == ANIM_STATE_DELAYED))
735   {
736     reverseAnim->QueueAnimation(ANIM_PROCESS_REVERSE);
737     if (forwardAnim) forwardAnim->ResetAnimation();
738   }
739   else if (forwardAnim)
740   {
741     forwardAnim->QueueAnimation(ANIM_PROCESS_NORMAL);
742     if (reverseAnim) reverseAnim->ResetAnimation();
743   }
744   else
745   { // hidden and visible animations delay the change of state.  If there is no animations
746     // to perform, then we should just change the state straightaway
747     if (reverseAnim) reverseAnim->ResetAnimation();
748     UpdateStates(animType, ANIM_PROCESS_NORMAL, ANIM_STATE_APPLIED);
749   }
750 }
751 
GetAnimation(ANIMATION_TYPE type,bool checkConditions)752 CAnimation *CGUIControl::GetAnimation(ANIMATION_TYPE type, bool checkConditions /* = true */)
753 {
754   for (unsigned int i = 0; i < m_animations.size(); i++)
755   {
756     CAnimation &anim = m_animations[i];
757     if (anim.GetType() == type)
758     {
759       if (!checkConditions || anim.CheckCondition())
760         return &anim;
761     }
762   }
763   return NULL;
764 }
765 
HasAnimation(ANIMATION_TYPE type)766 bool CGUIControl::HasAnimation(ANIMATION_TYPE type)
767 {
768   return (NULL != GetAnimation(type, true));
769 }
770 
UpdateStates(ANIMATION_TYPE type,ANIMATION_PROCESS currentProcess,ANIMATION_STATE currentState)771 void CGUIControl::UpdateStates(ANIMATION_TYPE type, ANIMATION_PROCESS currentProcess, ANIMATION_STATE currentState)
772 {
773   // Make sure control is hidden or visible at the appropriate times
774   // while processing a visible or hidden animation it needs to be visible,
775   // but when finished a hidden operation it needs to be hidden
776   if (type == ANIM_TYPE_VISIBLE)
777   {
778     if (currentProcess == ANIM_PROCESS_REVERSE)
779     {
780       if (currentState == ANIM_STATE_APPLIED)
781         m_visible = HIDDEN;
782     }
783     else if (currentProcess == ANIM_PROCESS_NORMAL)
784     {
785       if (currentState == ANIM_STATE_DELAYED)
786         m_visible = DELAYED;
787       else
788         m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
789     }
790   }
791   else if (type == ANIM_TYPE_HIDDEN)
792   {
793     if (currentProcess == ANIM_PROCESS_NORMAL)  // a hide animation
794     {
795       if (currentState == ANIM_STATE_APPLIED)
796         m_visible = HIDDEN; // finished
797       else
798         m_visible = VISIBLE; // have to be visible until we are finished
799     }
800     else if (currentProcess == ANIM_PROCESS_REVERSE)  // a visible animation
801     { // no delay involved here - just make sure it's visible
802       m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
803     }
804   }
805   else if (type == ANIM_TYPE_WINDOW_OPEN)
806   {
807     if (currentProcess == ANIM_PROCESS_NORMAL)
808     {
809       if (currentState == ANIM_STATE_DELAYED)
810         m_visible = DELAYED; // delayed
811       else
812         m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
813     }
814   }
815   else if (type == ANIM_TYPE_FOCUS)
816   {
817     // call the focus function if we have finished a focus animation
818     // (buttons can "click" on focus)
819     if (currentProcess == ANIM_PROCESS_NORMAL && currentState == ANIM_STATE_APPLIED)
820       OnFocus();
821   }
822   else if (type == ANIM_TYPE_UNFOCUS)
823   {
824     // call the unfocus function if we have finished a focus animation
825     // (buttons can "click" on focus)
826     if (currentProcess == ANIM_PROCESS_NORMAL && currentState == ANIM_STATE_APPLIED)
827       OnUnFocus();
828   }
829 }
830 
Animate(unsigned int currentTime)831 bool CGUIControl::Animate(unsigned int currentTime)
832 {
833   // check visible state outside the loop, as it could change
834   GUIVISIBLE visible = m_visible;
835 
836   m_transform.Reset();
837   bool changed = false;
838 
839   CPoint center(GetXPosition() + GetWidth() * 0.5f, GetYPosition() + GetHeight() * 0.5f);
840   for (unsigned int i = 0; i < m_animations.size(); i++)
841   {
842     CAnimation &anim = m_animations[i];
843     anim.Animate(currentTime, HasProcessed() || visible == DELAYED);
844     // Update the control states (such as visibility)
845     UpdateStates(anim.GetType(), anim.GetProcess(), anim.GetState());
846     // and render the animation effect
847     changed |= (anim.GetProcess() != ANIM_PROCESS_NONE);
848     anim.RenderAnimation(m_transform, center);
849 
850     // debug stuff
851     //if (anim.GetProcess() != ANIM_PROCESS_NONE && IsVisible())
852     //{
853     //  CLog::Log(LOGDEBUG, "Animating control %d", m_controlID);
854     //}
855   }
856 
857   return changed;
858 }
859 
IsAnimating(ANIMATION_TYPE animType)860 bool CGUIControl::IsAnimating(ANIMATION_TYPE animType)
861 {
862   for (unsigned int i = 0; i < m_animations.size(); i++)
863   {
864     CAnimation &anim = m_animations[i];
865     if (anim.GetType() == animType)
866     {
867       if (anim.GetQueuedProcess() == ANIM_PROCESS_NORMAL)
868         return true;
869       if (anim.GetProcess() == ANIM_PROCESS_NORMAL)
870         return true;
871     }
872     else if (anim.GetType() == -animType)
873     {
874       if (anim.GetQueuedProcess() == ANIM_PROCESS_REVERSE)
875         return true;
876       if (anim.GetProcess() == ANIM_PROCESS_REVERSE)
877         return true;
878     }
879   }
880   return false;
881 }
882 
GetAction(int actionID) const883 CGUIAction CGUIControl::GetAction(int actionID) const
884 {
885   ActionMap::const_iterator i = m_actions.find(actionID);
886   if (i != m_actions.end())
887     return i->second;
888   return CGUIAction();
889 }
890 
CanFocusFromPoint(const CPoint & point) const891 bool CGUIControl::CanFocusFromPoint(const CPoint &point) const
892 {
893   return CanFocus() && HitTest(point);
894 }
895 
UnfocusFromPoint(const CPoint & point)896 void CGUIControl::UnfocusFromPoint(const CPoint &point)
897 {
898   if (HasFocus())
899   {
900     CPoint controlPoint(point);
901     m_transform.InverseTransformPosition(controlPoint.x, controlPoint.y);
902     if (!HitTest(controlPoint))
903     {
904       SetFocus(false);
905 
906       // and tell our parent so it can unfocus
907       if (m_parentControl)
908       {
909         CGUIMessage msgLostFocus(GUI_MSG_LOSTFOCUS, GetID(), GetID());
910         m_parentControl->OnMessage(msgLostFocus);
911       }
912     }
913   }
914 }
915 
SaveStates(std::vector<CControlState> & states)916 void CGUIControl::SaveStates(std::vector<CControlState> &states)
917 {
918   // empty for now - do nothing with the majority of controls
919 }
920 
GetControl(int iControl,std::vector<CGUIControl * > * idCollector)921 CGUIControl *CGUIControl::GetControl(int iControl, std::vector<CGUIControl*> *idCollector)
922 {
923   return (iControl == m_controlID) ? this : nullptr;
924 }
925 
926 
UpdateControlStats()927 void CGUIControl::UpdateControlStats()
928 {
929   if (m_controlStats)
930   {
931     ++m_controlStats->nCountTotal;
932     if (IsVisible() && IsVisibleFromSkin())
933       ++m_controlStats->nCountVisible;
934   }
935 }
936 
SetHitRect(const CRect & rect,const UTILS::Color & color)937 void CGUIControl::SetHitRect(const CRect &rect, const UTILS::Color &color)
938 {
939   m_hitRect = rect;
940   m_hitColor = color;
941 }
942 
SetCamera(const CPoint & camera)943 void CGUIControl::SetCamera(const CPoint &camera)
944 {
945   m_camera = camera;
946   m_hasCamera = true;
947 }
948 
GetRenderPosition() const949 CPoint CGUIControl::GetRenderPosition() const
950 {
951   float z = 0;
952   CPoint point(GetPosition());
953   m_transform.TransformPosition(point.x, point.y, z);
954   if (m_parentControl)
955     point += m_parentControl->GetRenderPosition();
956   return point;
957 }
958 
SetStereoFactor(const float & factor)959 void CGUIControl::SetStereoFactor(const float &factor)
960 {
961   m_stereo = factor;
962 }
963