summaryrefslogtreecommitdiffstats
path: root/xbmc/guilib/GUIControl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/guilib/GUIControl.cpp')
-rw-r--r--xbmc/guilib/GUIControl.cpp979
1 files changed, 979 insertions, 0 deletions
diff --git a/xbmc/guilib/GUIControl.cpp b/xbmc/guilib/GUIControl.cpp
new file mode 100644
index 0000000..bd40475
--- /dev/null
+++ b/xbmc/guilib/GUIControl.cpp
@@ -0,0 +1,979 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIControl.h"
+
+#include "GUIAction.h"
+#include "GUIComponent.h"
+#include "GUIControlProfiler.h"
+#include "GUIInfoManager.h"
+#include "GUIMessage.h"
+#include "GUITexture.h"
+#include "GUIWindowManager.h"
+#include "ServiceBroker.h"
+#include "input/InputManager.h"
+#include "input/Key.h"
+#include "input/mouse/MouseStat.h"
+#include "utils/log.h"
+
+using namespace KODI::GUILIB;
+
+CGUIControl::CGUIControl()
+{
+ m_hasProcessed = false;
+ m_bHasFocus = false;
+ m_controlID = 0;
+ m_parentID = 0;
+ m_visible = VISIBLE;
+ m_visibleFromSkinCondition = true;
+ m_forceHidden = false;
+ m_enabled = true;
+ m_posX = 0;
+ m_posY = 0;
+ m_width = 0;
+ m_height = 0;
+ ControlType = GUICONTROL_UNKNOWN;
+ m_bInvalidated = true;
+ m_bAllocated=false;
+ m_parentControl = NULL;
+ m_hasCamera = false;
+ m_pushedUpdates = false;
+ m_pulseOnSelect = false;
+ m_controlDirtyState = DIRTY_STATE_CONTROL;
+ m_stereo = 0.0f;
+ m_controlStats = nullptr;
+}
+
+CGUIControl::CGUIControl(int parentID, int controlID, float posX, float posY, float width, float height)
+: m_hitRect(posX, posY, posX + width, posY + height),
+ m_diffuseColor(0xffffffff)
+{
+ m_posX = posX;
+ m_posY = posY;
+ m_width = width;
+ m_height = height;
+ m_bHasFocus = false;
+ m_controlID = controlID;
+ m_parentID = parentID;
+ m_visible = VISIBLE;
+ m_visibleFromSkinCondition = true;
+ m_forceHidden = false;
+ m_enabled = true;
+ ControlType = GUICONTROL_UNKNOWN;
+ m_bInvalidated = true;
+ m_bAllocated=false;
+ m_hasProcessed = false;
+ m_parentControl = NULL;
+ m_hasCamera = false;
+ m_pushedUpdates = false;
+ m_pulseOnSelect = false;
+ m_controlDirtyState = DIRTY_STATE_CONTROL;
+ m_stereo = 0.0f;
+ m_controlStats = nullptr;
+}
+
+CGUIControl::CGUIControl(const CGUIControl &) = default;
+
+CGUIControl::~CGUIControl(void) = default;
+
+void CGUIControl::AllocResources()
+{
+ m_hasProcessed = false;
+ m_bInvalidated = true;
+ m_bAllocated=true;
+}
+
+void CGUIControl::FreeResources(bool immediately)
+{
+ if (m_bAllocated)
+ {
+ // Reset our animation states - not conditional anims though.
+ // I'm not sure if this is needed for most cases anyway. I believe it's only here
+ // because some windows aren't loaded on demand
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ if (anim.GetType() != ANIM_TYPE_CONDITIONAL)
+ anim.ResetAnimation();
+ }
+ m_bAllocated=false;
+ }
+ m_hasProcessed = false;
+}
+
+void CGUIControl::DynamicResourceAlloc(bool bOnOff)
+{
+
+}
+
+// the main processing routine.
+// 1. animate and set animation transform
+// 2. if visible, process
+// 3. reset the animation transform
+void CGUIControl::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ CRect dirtyRegion = m_renderRegion;
+
+ bool changed = (m_controlDirtyState & DIRTY_STATE_CONTROL) != 0 || (m_bInvalidated && IsVisible());
+ m_controlDirtyState = 0;
+
+ if (Animate(currentTime))
+ MarkDirtyRegion();
+
+ // if the control changed culling state from true to false, mark it
+ const bool culled = m_transform.alpha <= 0.01f;
+ if (m_isCulled != culled)
+ {
+ m_isCulled = false;
+ MarkDirtyRegion();
+ }
+ m_isCulled = culled;
+
+ if (IsVisible())
+ {
+ m_cachedTransform = CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(m_transform);
+ if (m_hasCamera)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetCameraPosition(m_camera);
+
+ Process(currentTime, dirtyregions);
+ m_bInvalidated = false;
+
+ if (dirtyRegion != m_renderRegion)
+ {
+ dirtyRegion.Union(m_renderRegion);
+ changed = true;
+ }
+
+ if (m_hasCamera)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreCameraPosition();
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+ }
+
+ UpdateControlStats();
+
+ changed |= (m_controlDirtyState & DIRTY_STATE_CONTROL) != 0;
+
+ if (changed)
+ {
+ dirtyregions.emplace_back(dirtyRegion);
+ }
+}
+
+void CGUIControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ // update our render region
+ m_renderRegion = CServiceBroker::GetWinSystem()->GetGfxContext().GenerateAABB(CalcRenderRegion());
+ m_hasProcessed = true;
+}
+
+// the main render routine.
+// 1. set the animation transform
+// 2. if visible, paint
+// 3. reset the animation transform
+void CGUIControl::DoRender()
+{
+ if (IsVisible() && !m_isCulled)
+ {
+ bool hasStereo =
+ m_stereo != 0.0f &&
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() !=
+ RENDER_STEREO_MODE_MONO &&
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RENDER_STEREO_MODE_OFF;
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetTransform(m_cachedTransform);
+ if (m_hasCamera)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetCameraPosition(m_camera);
+ if (hasStereo)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoFactor(m_stereo);
+
+ GUIPROFILER_RENDER_BEGIN(this);
+
+ if (m_hitColor != 0xffffffff)
+ {
+ UTILS::COLOR::Color color =
+ CServiceBroker::GetWinSystem()->GetGfxContext().MergeAlpha(m_hitColor);
+ CGUITexture::DrawQuad(CServiceBroker::GetWinSystem()->GetGfxContext().GenerateAABB(m_hitRect), color);
+ }
+
+ Render();
+
+ GUIPROFILER_RENDER_END(this);
+
+ if (hasStereo)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreStereoFactor();
+ if (m_hasCamera)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreCameraPosition();
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+ }
+}
+
+bool CGUIControl::OnAction(const CAction &action)
+{
+ if (HasFocus())
+ {
+ switch (action.GetID())
+ {
+ case ACTION_MOVE_DOWN:
+ OnDown();
+ return true;
+
+ case ACTION_MOVE_UP:
+ OnUp();
+ return true;
+
+ case ACTION_MOVE_LEFT:
+ OnLeft();
+ return true;
+
+ case ACTION_MOVE_RIGHT:
+ OnRight();
+ return true;
+
+ case ACTION_SHOW_INFO:
+ return OnInfo();
+
+ case ACTION_NAV_BACK:
+ return OnBack();
+
+ case ACTION_NEXT_CONTROL:
+ OnNextControl();
+ return true;
+
+ case ACTION_PREV_CONTROL:
+ OnPrevControl();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIControl::Navigate(int direction) const
+{
+ if (HasFocus())
+ {
+ CGUIMessage msg(GUI_MSG_MOVE, GetParentID(), GetID(), direction);
+ return SendWindowMessage(msg);
+ }
+ return false;
+}
+
+// Movement controls (derived classes can override)
+void CGUIControl::OnUp()
+{
+ Navigate(ACTION_MOVE_UP);
+}
+
+void CGUIControl::OnDown()
+{
+ Navigate(ACTION_MOVE_DOWN);
+}
+
+void CGUIControl::OnLeft()
+{
+ Navigate(ACTION_MOVE_LEFT);
+}
+
+void CGUIControl::OnRight()
+{
+ Navigate(ACTION_MOVE_RIGHT);
+}
+
+bool CGUIControl::OnBack()
+{
+ return Navigate(ACTION_NAV_BACK);
+}
+
+bool CGUIControl::OnInfo()
+{
+ CGUIAction action = GetAction(ACTION_SHOW_INFO);
+ if (action.HasAnyActions())
+ return action.ExecuteActions(GetID(), GetParentID());
+ return false;
+}
+
+void CGUIControl::OnNextControl()
+{
+ Navigate(ACTION_NEXT_CONTROL);
+}
+
+void CGUIControl::OnPrevControl()
+{
+ Navigate(ACTION_PREV_CONTROL);
+}
+
+bool CGUIControl::SendWindowMessage(CGUIMessage &message) const
+{
+ CGUIWindow *pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(GetParentID());
+ if (pWindow)
+ return pWindow->OnMessage(message);
+ return CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message);
+}
+
+int CGUIControl::GetID(void) const
+{
+ return m_controlID;
+}
+
+
+int CGUIControl::GetParentID(void) const
+{
+ return m_parentID;
+}
+
+bool CGUIControl::HasFocus(void) const
+{
+ return m_bHasFocus;
+}
+
+void CGUIControl::SetFocus(bool focus)
+{
+ if (m_bHasFocus && !focus)
+ QueueAnimation(ANIM_TYPE_UNFOCUS);
+ else if (!m_bHasFocus && focus)
+ QueueAnimation(ANIM_TYPE_FOCUS);
+ m_bHasFocus = focus;
+}
+
+bool CGUIControl::OnMessage(CGUIMessage& message)
+{
+ if ( message.GetControlId() == GetID() )
+ {
+ switch (message.GetMessage() )
+ {
+ case GUI_MSG_SETFOCUS:
+ // if control is disabled then move 2 the next control
+ if ( !CanFocus() )
+ {
+ CLog::Log(LOGERROR,
+ "Control {} in window {} has been asked to focus, "
+ "but it can't",
+ GetID(), GetParentID());
+ return false;
+ }
+ SetFocus(true);
+ {
+ // inform our parent window that this has happened
+ CGUIMessage message(GUI_MSG_FOCUSED, GetParentID(), GetID());
+ if (m_parentControl)
+ m_parentControl->OnMessage(message);
+ }
+ return true;
+ break;
+
+ case GUI_MSG_LOSTFOCUS:
+ {
+ SetFocus(false);
+ // and tell our parent so it can unfocus
+ if (m_parentControl)
+ m_parentControl->OnMessage(message);
+ return true;
+ }
+ break;
+
+ case GUI_MSG_VISIBLE:
+ SetVisible(true, true);
+ return true;
+ break;
+
+ case GUI_MSG_HIDDEN:
+ SetVisible(false);
+ return true;
+
+ // Note that the skin <enable> tag will override these messages
+ case GUI_MSG_ENABLED:
+ SetEnabled(true);
+ return true;
+
+ case GUI_MSG_DISABLED:
+ SetEnabled(false);
+ return true;
+
+ case GUI_MSG_WINDOW_RESIZE:
+ // invalidate controls to get them to recalculate sizing information
+ SetInvalid();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIControl::CanFocus() const
+{
+ if (!IsVisible() && !m_allowHiddenFocus) return false;
+ if (IsDisabled()) return false;
+ return true;
+}
+
+bool CGUIControl::IsVisible() const
+{
+ if (m_forceHidden)
+ return false;
+ return m_visible == VISIBLE;
+}
+
+bool CGUIControl::IsDisabled() const
+{
+ return !m_enabled;
+}
+
+void CGUIControl::SetEnabled(bool bEnable)
+{
+ if (bEnable != m_enabled)
+ {
+ m_enabled = bEnable;
+ SetInvalid();
+ }
+}
+
+void CGUIControl::SetEnableCondition(const std::string &expression)
+{
+ if (expression == "true")
+ m_enabled = true;
+ else if (expression == "false")
+ m_enabled = false;
+ else
+ m_enableCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(expression, GetParentID());
+}
+
+void CGUIControl::SetPosition(float posX, float posY)
+{
+ if ((m_posX != posX) || (m_posY != posY))
+ {
+ MarkDirtyRegion();
+
+ m_hitRect += CPoint(posX - m_posX, posY - m_posY);
+ m_posX = posX;
+ m_posY = posY;
+
+ SetInvalid();
+ }
+}
+
+bool CGUIControl::SetColorDiffuse(const GUIINFO::CGUIInfoColor &color)
+{
+ bool changed = m_diffuseColor != color;
+ m_diffuseColor = color;
+ return changed;
+}
+
+float CGUIControl::GetXPosition() const
+{
+ return m_posX;
+}
+
+float CGUIControl::GetYPosition() const
+{
+ return m_posY;
+}
+
+float CGUIControl::GetWidth() const
+{
+ return m_width;
+}
+
+float CGUIControl::GetHeight() const
+{
+ return m_height;
+}
+
+void CGUIControl::MarkDirtyRegion(const unsigned int dirtyState)
+{
+ // if the control is culled, bail
+ if (dirtyState == DIRTY_STATE_CONTROL && m_isCulled)
+ return;
+ if (!m_controlDirtyState && m_parentControl)
+ m_parentControl->MarkDirtyRegion(DIRTY_STATE_CHILD);
+
+ m_controlDirtyState |= dirtyState;
+}
+
+CRect CGUIControl::CalcRenderRegion() const
+{
+ CPoint tl(GetXPosition(), GetYPosition());
+ CPoint br(tl.x + GetWidth(), tl.y + GetHeight());
+
+ return CRect(tl.x, tl.y, br.x, br.y);
+}
+
+void CGUIControl::SetActions(const ActionMap &actions)
+{
+ m_actions = actions;
+}
+
+void CGUIControl::SetAction(int actionID, const CGUIAction &action, bool replace /*= true*/)
+{
+ ActionMap::iterator i = m_actions.find(actionID);
+ if (i == m_actions.end() || !i->second.HasAnyActions() || replace)
+ m_actions[actionID] = action;
+}
+
+void CGUIControl::SetWidth(float width)
+{
+ if (m_width != width)
+ {
+ MarkDirtyRegion();
+ m_width = width;
+ m_hitRect.x2 = m_hitRect.x1 + width;
+ SetInvalid();
+ }
+}
+
+void CGUIControl::SetHeight(float height)
+{
+ if (m_height != height)
+ {
+ MarkDirtyRegion();
+ m_height = height;
+ m_hitRect.y2 = m_hitRect.y1 + height;
+ SetInvalid();
+ }
+}
+
+void CGUIControl::SetVisible(bool bVisible, bool setVisState)
+{
+ if (bVisible && setVisState)
+ { //! @todo currently we only update m_visible from GUI_MSG_VISIBLE (SET_CONTROL_VISIBLE)
+ //! otherwise we just set m_forceHidden
+ GUIVISIBLE visible;
+ if (m_visibleCondition)
+ visible = m_visibleCondition->Get(INFO::DEFAULT_CONTEXT) ? VISIBLE : HIDDEN;
+ else
+ visible = VISIBLE;
+ if (visible != m_visible)
+ {
+ m_visible = visible;
+ SetInvalid();
+ }
+ }
+ if (m_forceHidden == bVisible)
+ {
+ m_forceHidden = !bVisible;
+ SetInvalid();
+ if (m_forceHidden)
+ MarkDirtyRegion();
+ }
+ if (m_forceHidden)
+ { // reset any visible animations that are in process
+ if (IsAnimating(ANIM_TYPE_VISIBLE))
+ {
+ // CLog::Log(LOGDEBUG, "Resetting visible animation on control {} (we are {})", m_controlID, m_visible ? "visible" : "hidden");
+ CAnimation *visibleAnim = GetAnimation(ANIM_TYPE_VISIBLE);
+ if (visibleAnim) visibleAnim->ResetAnimation();
+ }
+ }
+}
+
+bool CGUIControl::HitTest(const CPoint &point) const
+{
+ return m_hitRect.PtInRect(point);
+}
+
+EVENT_RESULT CGUIControl::SendMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ CPoint childPoint(point);
+ m_transform.InverseTransformPosition(childPoint.x, childPoint.y);
+ if (!CanFocusFromPoint(childPoint))
+ return EVENT_RESULT_UNHANDLED;
+
+ bool handled = event.m_id != ACTION_MOUSE_MOVE || OnMouseOver(childPoint);
+ EVENT_RESULT ret = OnMouseEvent(childPoint, event);
+ if (ret)
+ return ret;
+ return (handled && (event.m_id == ACTION_MOUSE_MOVE)) ? EVENT_RESULT_HANDLED : EVENT_RESULT_UNHANDLED;
+}
+
+// override this function to implement custom mouse behaviour
+bool CGUIControl::OnMouseOver(const CPoint &point)
+{
+ if (CServiceBroker::GetInputManager().GetMouseState() != MOUSE_STATE_DRAG)
+ CServiceBroker::GetInputManager().SetMouseState(MOUSE_STATE_FOCUS);
+ if (!CanFocus()) return false;
+ if (!HasFocus())
+ {
+ CGUIMessage msg(GUI_MSG_SETFOCUS, GetParentID(), GetID());
+ OnMessage(msg);
+ }
+ return true;
+}
+
+void CGUIControl::UpdateVisibility(const CGUIListItem *item)
+{
+ if (m_visibleCondition)
+ {
+ bool bWasVisible = m_visibleFromSkinCondition;
+ m_visibleFromSkinCondition = m_visibleCondition->Get(INFO::DEFAULT_CONTEXT, item);
+ if (!bWasVisible && m_visibleFromSkinCondition)
+ { // automatic change of visibility - queue the in effect
+ // CLog::Log(LOGDEBUG, "Visibility changed to visible for control id {}", m_controlID);
+ QueueAnimation(ANIM_TYPE_VISIBLE);
+ }
+ else if (bWasVisible && !m_visibleFromSkinCondition)
+ { // automatic change of visibility - do the out effect
+ // CLog::Log(LOGDEBUG, "Visibility changed to hidden for control id {}", m_controlID);
+ QueueAnimation(ANIM_TYPE_HIDDEN);
+ }
+ }
+ // check for conditional animations
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ if (anim.GetType() == ANIM_TYPE_CONDITIONAL)
+ anim.UpdateCondition(item);
+ }
+ // and check for conditional enabling - note this overrides SetEnabled() from the code currently
+ // this may need to be reviewed at a later date
+ bool enabled = m_enabled;
+ if (m_enableCondition)
+ m_enabled = m_enableCondition->Get(INFO::DEFAULT_CONTEXT, item);
+
+ if (m_enabled != enabled)
+ MarkDirtyRegion();
+
+ m_allowHiddenFocus.Update(INFO::DEFAULT_CONTEXT, item);
+ if (UpdateColors(item))
+ MarkDirtyRegion();
+ // and finally, update our control information (if not pushed)
+ if (!m_pushedUpdates)
+ UpdateInfo(item);
+}
+
+bool CGUIControl::UpdateColors(const CGUIListItem* item)
+{
+ return m_diffuseColor.Update(item);
+}
+
+void CGUIControl::SetInitialVisibility()
+{
+ if (m_visibleCondition)
+ {
+ m_visibleFromSkinCondition = m_visibleCondition->Get(INFO::DEFAULT_CONTEXT);
+ m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
+ // CLog::Log(LOGDEBUG, "Set initial visibility for control {}: {}", m_controlID, m_visible == VISIBLE ? "visible" : "hidden");
+ }
+ else if (m_visible == DELAYED)
+ m_visible = VISIBLE;
+ // and handle animation conditions as well
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ if (anim.GetType() == ANIM_TYPE_CONDITIONAL)
+ anim.SetInitialCondition();
+ }
+ // and check for conditional enabling - note this overrides SetEnabled() from the code currently
+ // this may need to be reviewed at a later date
+ if (m_enableCondition)
+ m_enabled = m_enableCondition->Get(INFO::DEFAULT_CONTEXT);
+ m_allowHiddenFocus.Update(INFO::DEFAULT_CONTEXT);
+ UpdateColors(nullptr);
+
+ MarkDirtyRegion();
+}
+
+void CGUIControl::SetVisibleCondition(const std::string &expression, const std::string &allowHiddenFocus)
+{
+ if (expression == "true")
+ m_visible = VISIBLE;
+ else if (expression == "false")
+ m_visible = HIDDEN;
+ else // register with the infomanager for updates
+ m_visibleCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(expression, GetParentID());
+ m_allowHiddenFocus.Parse(allowHiddenFocus, GetParentID());
+}
+
+void CGUIControl::SetAnimations(const std::vector<CAnimation> &animations)
+{
+ m_animations = animations;
+ MarkDirtyRegion();
+}
+
+void CGUIControl::ResetAnimation(ANIMATION_TYPE type)
+{
+ MarkDirtyRegion();
+
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ if (m_animations[i].GetType() == type)
+ m_animations[i].ResetAnimation();
+ }
+}
+
+void CGUIControl::ResetAnimations()
+{
+ MarkDirtyRegion();
+
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ m_animations[i].ResetAnimation();
+
+ MarkDirtyRegion();
+}
+
+bool CGUIControl::CheckAnimation(ANIMATION_TYPE animType)
+{
+ // rule out the animations we shouldn't perform
+ if (!IsVisible() || !HasProcessed())
+ { // hidden or never processed - don't allow exit or entry animations for this control
+ if (animType == ANIM_TYPE_WINDOW_CLOSE)
+ { // could be animating a (delayed) window open anim, so reset it
+ ResetAnimation(ANIM_TYPE_WINDOW_OPEN);
+ return false;
+ }
+ }
+ if (!IsVisible())
+ { // hidden - only allow hidden anims if we're animating a visible anim
+ if (animType == ANIM_TYPE_HIDDEN && !IsAnimating(ANIM_TYPE_VISIBLE))
+ {
+ // update states to force it hidden
+ UpdateStates(animType, ANIM_PROCESS_NORMAL, ANIM_STATE_APPLIED);
+ return false;
+ }
+ if (animType == ANIM_TYPE_WINDOW_OPEN)
+ return false;
+ }
+ return true;
+}
+
+void CGUIControl::QueueAnimation(ANIMATION_TYPE animType)
+{
+ if (!CheckAnimation(animType))
+ return;
+
+ MarkDirtyRegion();
+
+ CAnimation *reverseAnim = GetAnimation((ANIMATION_TYPE)-animType, false);
+ CAnimation *forwardAnim = GetAnimation(animType);
+ // we first check whether the reverse animation is in progress (and reverse it)
+ // then we check for the normal animation, and queue it
+ if (reverseAnim && reverseAnim->IsReversible() && (reverseAnim->GetState() == ANIM_STATE_IN_PROCESS || reverseAnim->GetState() == ANIM_STATE_DELAYED))
+ {
+ reverseAnim->QueueAnimation(ANIM_PROCESS_REVERSE);
+ if (forwardAnim) forwardAnim->ResetAnimation();
+ }
+ else if (forwardAnim)
+ {
+ forwardAnim->QueueAnimation(ANIM_PROCESS_NORMAL);
+ if (reverseAnim) reverseAnim->ResetAnimation();
+ }
+ else
+ { // hidden and visible animations delay the change of state. If there is no animations
+ // to perform, then we should just change the state straightaway
+ if (reverseAnim) reverseAnim->ResetAnimation();
+ UpdateStates(animType, ANIM_PROCESS_NORMAL, ANIM_STATE_APPLIED);
+ }
+}
+
+CAnimation *CGUIControl::GetAnimation(ANIMATION_TYPE type, bool checkConditions /* = true */)
+{
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ if (anim.GetType() == type)
+ {
+ if (!checkConditions || anim.CheckCondition())
+ return &anim;
+ }
+ }
+ return NULL;
+}
+
+bool CGUIControl::HasAnimation(ANIMATION_TYPE type)
+{
+ return (NULL != GetAnimation(type, true));
+}
+
+void CGUIControl::UpdateStates(ANIMATION_TYPE type, ANIMATION_PROCESS currentProcess, ANIMATION_STATE currentState)
+{
+ // Make sure control is hidden or visible at the appropriate times
+ // while processing a visible or hidden animation it needs to be visible,
+ // but when finished a hidden operation it needs to be hidden
+ if (type == ANIM_TYPE_VISIBLE)
+ {
+ if (currentProcess == ANIM_PROCESS_REVERSE)
+ {
+ if (currentState == ANIM_STATE_APPLIED)
+ m_visible = HIDDEN;
+ }
+ else if (currentProcess == ANIM_PROCESS_NORMAL)
+ {
+ if (currentState == ANIM_STATE_DELAYED)
+ m_visible = DELAYED;
+ else
+ m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
+ }
+ }
+ else if (type == ANIM_TYPE_HIDDEN)
+ {
+ if (currentProcess == ANIM_PROCESS_NORMAL) // a hide animation
+ {
+ if (currentState == ANIM_STATE_APPLIED)
+ m_visible = HIDDEN; // finished
+ else
+ m_visible = VISIBLE; // have to be visible until we are finished
+ }
+ else if (currentProcess == ANIM_PROCESS_REVERSE) // a visible animation
+ { // no delay involved here - just make sure it's visible
+ m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
+ }
+ }
+ else if (type == ANIM_TYPE_WINDOW_OPEN)
+ {
+ if (currentProcess == ANIM_PROCESS_NORMAL)
+ {
+ if (currentState == ANIM_STATE_DELAYED)
+ m_visible = DELAYED; // delayed
+ else
+ m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
+ }
+ }
+ else if (type == ANIM_TYPE_FOCUS)
+ {
+ // call the focus function if we have finished a focus animation
+ // (buttons can "click" on focus)
+ if (currentProcess == ANIM_PROCESS_NORMAL && currentState == ANIM_STATE_APPLIED)
+ OnFocus();
+ }
+ else if (type == ANIM_TYPE_UNFOCUS)
+ {
+ // call the unfocus function if we have finished a focus animation
+ // (buttons can "click" on focus)
+ if (currentProcess == ANIM_PROCESS_NORMAL && currentState == ANIM_STATE_APPLIED)
+ OnUnFocus();
+ }
+}
+
+bool CGUIControl::Animate(unsigned int currentTime)
+{
+ // check visible state outside the loop, as it could change
+ GUIVISIBLE visible = m_visible;
+
+ m_transform.Reset();
+ bool changed = false;
+
+ CPoint center(GetXPosition() + GetWidth() * 0.5f, GetYPosition() + GetHeight() * 0.5f);
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ anim.Animate(currentTime, HasProcessed() || visible == DELAYED);
+ // Update the control states (such as visibility)
+ UpdateStates(anim.GetType(), anim.GetProcess(), anim.GetState());
+ // and render the animation effect
+ changed |= (anim.GetProcess() != ANIM_PROCESS_NONE);
+ anim.RenderAnimation(m_transform, center);
+
+ // debug stuff
+ //if (anim.GetProcess() != ANIM_PROCESS_NONE && IsVisible())
+ //{
+ // CLog::Log(LOGDEBUG, "Animating control {}", m_controlID);
+ //}
+ }
+
+ return changed;
+}
+
+bool CGUIControl::IsAnimating(ANIMATION_TYPE animType)
+{
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ if (anim.GetType() == animType)
+ {
+ if (anim.GetQueuedProcess() == ANIM_PROCESS_NORMAL)
+ return true;
+ if (anim.GetProcess() == ANIM_PROCESS_NORMAL)
+ return true;
+ }
+ else if (anim.GetType() == -animType)
+ {
+ if (anim.GetQueuedProcess() == ANIM_PROCESS_REVERSE)
+ return true;
+ if (anim.GetProcess() == ANIM_PROCESS_REVERSE)
+ return true;
+ }
+ }
+ return false;
+}
+
+CGUIAction CGUIControl::GetAction(int actionID) const
+{
+ ActionMap::const_iterator i = m_actions.find(actionID);
+ if (i != m_actions.end())
+ return i->second;
+ return CGUIAction();
+}
+
+bool CGUIControl::CanFocusFromPoint(const CPoint &point) const
+{
+ return CanFocus() && HitTest(point);
+}
+
+void CGUIControl::UnfocusFromPoint(const CPoint &point)
+{
+ if (HasFocus())
+ {
+ CPoint controlPoint(point);
+ m_transform.InverseTransformPosition(controlPoint.x, controlPoint.y);
+ if (!HitTest(controlPoint))
+ {
+ SetFocus(false);
+
+ // and tell our parent so it can unfocus
+ if (m_parentControl)
+ {
+ CGUIMessage msgLostFocus(GUI_MSG_LOSTFOCUS, GetID(), GetID());
+ m_parentControl->OnMessage(msgLostFocus);
+ }
+ }
+ }
+}
+
+void CGUIControl::SaveStates(std::vector<CControlState> &states)
+{
+ // empty for now - do nothing with the majority of controls
+}
+
+CGUIControl *CGUIControl::GetControl(int iControl, std::vector<CGUIControl*> *idCollector)
+{
+ return (iControl == m_controlID) ? this : nullptr;
+}
+
+
+void CGUIControl::UpdateControlStats()
+{
+ if (m_controlStats)
+ {
+ ++m_controlStats->nCountTotal;
+ if (IsVisible() && IsVisibleFromSkin())
+ ++m_controlStats->nCountVisible;
+ }
+}
+
+void CGUIControl::SetHitRect(const CRect& rect, const UTILS::COLOR::Color& color)
+{
+ m_hitRect = rect;
+ m_hitColor = color;
+}
+
+void CGUIControl::SetCamera(const CPoint &camera)
+{
+ m_camera = camera;
+ m_hasCamera = true;
+}
+
+CPoint CGUIControl::GetRenderPosition() const
+{
+ float z = 0;
+ CPoint point(GetPosition());
+ m_transform.TransformPosition(point.x, point.y, z);
+ if (m_parentControl)
+ point += m_parentControl->GetRenderPosition();
+ return point;
+}
+
+void CGUIControl::SetStereoFactor(const float &factor)
+{
+ m_stereo = factor;
+}