/* * 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 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 &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 &states) { // empty for now - do nothing with the majority of controls } CGUIControl *CGUIControl::GetControl(int iControl, std::vector *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; }