/* GWEN Copyright (c) 2010 Facepunch Studios See license in Gwen.h */ #include "Gwen/Controls/Base.h" #include "Gwen/Controls/Label.h" #include "Gwen/Gwen.h" #include "Gwen/BaseRender.h" #include "Gwen/Skin.h" #include "Gwen/Platform.h" #include "Gwen/DragAndDrop.h" #include "Gwen/ToolTip.h" #include "Gwen/Utility.h" #include #ifndef GWEN_NO_ANIMATION #include "Gwen/Anim.h" #endif using namespace Gwen; using namespace Controls; Base::Base(Base* pParent) { m_Parent = NULL; m_ActualParent = NULL; m_InnerPanel = NULL; m_Skin = NULL; SetParent(pParent); m_bHidden = false; m_Bounds = Gwen::Rect(0, 0, 10, 10); m_Padding = Padding(0, 0, 0, 0); m_Margin = Margin(0, 0, 0, 0); m_iDock = 0; m_DragAndDrop_Package = NULL; m_pUserData = NULL; RestrictToParent(false); SetMouseInputEnabled(true); SetKeyboardInputEnabled(false); Invalidate(); SetCursor(Gwen::CursorType::Normal); SetToolTip(NULL); SetTabable(false); SetShouldDrawBackground(true); m_bDisabled = false; m_bCacheTextureDirty = true; m_bCacheToTexture = false; } Base::~Base() { { Canvas* canvas = GetCanvas(); if (canvas) canvas->PreDelete(this); } Base::List::iterator iter = Children.begin(); while (iter != Children.end()) { Base* pChild = *iter; iter = Children.erase(iter); delete pChild; } for (AccelMap::iterator accelIt = m_Accelerators.begin(); accelIt != m_Accelerators.end(); ++accelIt) { delete accelIt->second; } m_Accelerators.clear(); SetParent(NULL); if (Gwen::HoveredControl == this) Gwen::HoveredControl = NULL; if (Gwen::KeyboardFocus == this) Gwen::KeyboardFocus = NULL; if (Gwen::MouseFocus == this) Gwen::MouseFocus = NULL; DragAndDrop::ControlDeleted(this); ToolTip::ControlDeleted(this); #ifndef GWEN_NO_ANIMATION Anim::Cancel(this); #endif if (m_DragAndDrop_Package) { delete m_DragAndDrop_Package; m_DragAndDrop_Package = NULL; } } extern int avoidUpdate; void Base::Invalidate() { m_bNeedsLayout = true; m_bCacheTextureDirty = true; avoidUpdate = -3; } void Base::DelayedDelete() { Canvas* canvas = GetCanvas(); canvas->AddDelayedDelete(this); } Canvas* Base::GetCanvas() { Base* pCanvas = m_Parent; if (!pCanvas) return NULL; return pCanvas->GetCanvas(); } void Base::SetParent(Base* pParent) { if (m_Parent == pParent) return; if (m_Parent) { m_Parent->RemoveChild(this); } m_Parent = pParent; m_ActualParent = NULL; if (m_Parent) { m_Parent->AddChild(this); } } void Base::Dock(int iDock) { if (m_iDock == iDock) return; m_iDock = iDock; Invalidate(); InvalidateParent(); } int Base::GetDock() { return m_iDock; } bool Base::Hidden() const { return m_bHidden; } bool Base::Visible() const { if (Hidden()) return false; if (GetParent()) { return GetParent()->Visible(); } return true; } void Base::InvalidateChildren(bool bRecursive) { for (Base::List::iterator it = Children.begin(); it != Children.end(); ++it) { (*it)->Invalidate(); if (bRecursive) (*it)->InvalidateChildren(bRecursive); } if (m_InnerPanel) { for (Base::List::iterator it = m_InnerPanel->Children.begin(); it != m_InnerPanel->Children.end(); ++it) { (*it)->Invalidate(); if (bRecursive) (*it)->InvalidateChildren(bRecursive); } } } void Base::Position(int pos, int xpadding, int ypadding) { int w = GetParent()->Width(); int h = GetParent()->Height(); const Padding& padding = GetParent()->GetPadding(); int x = X(); int y = Y(); if (pos & Pos::Left) x = padding.left + xpadding; if (pos & Pos::Right) x = w - Width() - padding.right - xpadding; if (pos & Pos::CenterH) x = padding.left + xpadding + (w - Width() - padding.left - padding.right) * 0.5; if (pos & Pos::Top) y = padding.top + ypadding; if (pos & Pos::Bottom) y = h - Height() - padding.bottom - ypadding; if (pos & Pos::CenterV) y = padding.top + ypadding + (h - Height() - padding.bottom - padding.top) * 0.5; SetPos(x, y); } void Base::SendToBack() { if (!m_Parent) return; if (m_Parent->Children.front() == this) return; m_Parent->Children.remove(this); m_Parent->Children.push_front(this); InvalidateParent(); } void Base::BringToFront() { if (!m_Parent) return; if (m_Parent->Children.back() == this) return; m_Parent->Children.remove(this); m_Parent->Children.push_back(this); InvalidateParent(); } Controls::Base* Base::FindChildByName(const Gwen::String& name, bool bRecursive) { Base::List::iterator iter; for (iter = Children.begin(); iter != Children.end(); ++iter) { Base* pChild = *iter; if (pChild->GetName() == name) return pChild; if (bRecursive) { Controls::Base* pSubChild = pChild->FindChildByName(name, true); if (pSubChild) return pSubChild; } } return NULL; } void Base::BringNextToControl(Controls::Base* pChild, bool bBehind) { if (!m_Parent) return; m_Parent->Children.remove(this); Base::List::iterator it = std::find(m_Parent->Children.begin(), m_Parent->Children.end(), pChild); if (it == m_Parent->Children.end()) return BringToFront(); if (bBehind) { ++it; if (it == m_Parent->Children.end()) return BringToFront(); } m_Parent->Children.insert(it, this); InvalidateParent(); } void Base::AddChild(Base* pChild) { if (m_InnerPanel) { m_InnerPanel->AddChild(pChild); return; } Children.push_back(pChild); OnChildAdded(pChild); pChild->m_ActualParent = this; } void Base::RemoveChild(Base* pChild) { // If we removed our innerpanel // remove our pointer to it if (m_InnerPanel == pChild) { m_InnerPanel = NULL; } if (m_InnerPanel) { m_InnerPanel->RemoveChild(pChild); } Children.remove(pChild); OnChildRemoved(pChild); } void Base::RemoveAllChildren() { while (Children.size() > 0) { RemoveChild(*Children.begin()); } } int Base::NumChildren() { // Include m_InnerPanel's children here? return Children.size(); } void Base::OnChildAdded(Base* /*pChild*/) { Invalidate(); } void Base::OnChildRemoved(Base* /*pChild*/) { Invalidate(); } Skin::Base* Base::GetSkin(void) { if (m_Skin) return m_Skin; if (m_Parent) return m_Parent->GetSkin(); Debug::AssertCheck(0, "Base::GetSkin Returning NULL!\n"); return NULL; } void Base::MoveBy(int x, int y) { SetBounds(X() + x, Y() + y, Width(), Height()); } void Base::MoveTo(int x, int y) { if (m_bRestrictToParent && GetParent()) { Base* pParent = GetParent(); if (x - GetPadding().left < pParent->GetMargin().left) x = pParent->GetMargin().left + GetPadding().left; if (y - GetPadding().top < pParent->GetMargin().top) y = pParent->GetMargin().top + GetPadding().top; if (x + Width() + GetPadding().right > pParent->Width() - pParent->GetMargin().right) x = pParent->Width() - pParent->GetMargin().right - Width() - GetPadding().right; if (y + Height() + GetPadding().bottom > pParent->Height() - pParent->GetMargin().bottom) y = pParent->Height() - pParent->GetMargin().bottom - Height() - GetPadding().bottom; } SetBounds(x, y, Width(), Height()); } void Base::SetPos(int x, int y) { SetBounds(x, y, Width(), Height()); } bool Base::SetSize(int w, int h) { return SetBounds(X(), Y(), w, h); } bool Base::SetBounds(const Gwen::Rect& bounds) { return SetBounds(bounds.x, bounds.y, bounds.w, bounds.h); } bool Base::SetBounds(int x, int y, int w, int h) { if (m_Bounds.x == x && m_Bounds.y == y && m_Bounds.w == w && m_Bounds.h == h) return false; Gwen::Rect oldBounds = GetBounds(); m_Bounds.x = x; m_Bounds.y = y; m_Bounds.w = w; m_Bounds.h = h; OnBoundsChanged(oldBounds); return true; } void Base::OnBoundsChanged(Gwen::Rect oldBounds) { //Anything that needs to update on size changes //Iterate my children and tell them I've changed // if (m_Bounds.w != oldBounds.w || m_Bounds.h != oldBounds.h) { if (GetParent()) GetParent()->OnChildBoundsChanged(oldBounds, this); Invalidate(); } Redraw(); UpdateRenderBounds(); } void Base::OnScaleChanged() { for (Base::List::iterator iter = Children.begin(); iter != Children.end(); ++iter) { (*iter)->OnScaleChanged(); } } void Base::OnChildBoundsChanged(Gwen::Rect /*oldChildBounds*/, Base* /*pChild*/) { } void Base::Render(Gwen::Skin::Base* /*skin*/) { } void Base::DoCacheRender(Gwen::Skin::Base* skin, Gwen::Controls::Base* pMaster) { Gwen::Renderer::Base* render = skin->GetRender(); Gwen::Renderer::ICacheToTexture* cache = render->GetCTT(); if (!cache) return; Gwen::Point pOldRenderOffset = render->GetRenderOffset(); Gwen::Rect rOldRegion = render->ClipRegion(); if (this != pMaster) { render->AddRenderOffset(GetBounds()); render->AddClipRegion(GetBounds()); } else { render->SetRenderOffset(Gwen::Point(0, 0)); render->SetClipRegion(GetBounds()); } if (m_bCacheTextureDirty && render->ClipRegionVisible()) { render->StartClip(); if (ShouldCacheToTexture()) cache->SetupCacheTexture(this); //Render myself first Render(skin); if (!Children.empty()) { //Now render my kids for (Base::List::iterator iter = Children.begin(); iter != Children.end(); ++iter) { Base* pChild = *iter; if (pChild->Hidden()) continue; pChild->DoCacheRender(skin, pMaster); } } if (ShouldCacheToTexture()) { cache->FinishCacheTexture(this); m_bCacheTextureDirty = false; } } render->SetClipRegion(rOldRegion); render->StartClip(); render->SetRenderOffset(pOldRenderOffset); cache->DrawCachedControlTexture(this); } void Base::DoRender(Gwen::Skin::Base* skin) { // If this control has a different skin, // then so does its children. if (m_Skin) skin = m_Skin; // Do think Think(); Gwen::Renderer::Base* render = skin->GetRender(); if (render->GetCTT() && ShouldCacheToTexture()) { DoCacheRender(skin, this); return; } Gwen::Point pOldRenderOffset = render->GetRenderOffset(); render->AddRenderOffset(GetBounds()); RenderUnder(skin); Gwen::Rect rOldRegion = render->ClipRegion(); render->AddClipRegion(GetBounds()); if (render->ClipRegionVisible()) { render->StartClip(); //Render myself first Render(skin); if (!Children.empty()) { //Now render my kids for (Base::List::iterator iter = Children.begin(); iter != Children.end(); ++iter) { Base* pChild = *iter; if (pChild->Hidden()) continue; pChild->DoRender(skin); } } render->SetClipRegion(rOldRegion); render->StartClip(); RenderOver(skin); } else { render->SetClipRegion(rOldRegion); } RenderFocus(skin); render->SetRenderOffset(pOldRenderOffset); } void Base::SetSkin(Skin::Base* skin, bool doChildren) { if (m_Skin == skin) return; m_Skin = skin; Invalidate(); Redraw(); OnSkinChanged(skin); if (doChildren) { for (Base::List::iterator it = Children.begin(); it != Children.end(); ++it) { (*it)->SetSkin(skin, true); } } } void Base::OnSkinChanged(Skin::Base* /*skin*/) { //Do something } bool Base::OnMouseWheeled(int iDelta) { if (m_ActualParent) return m_ActualParent->OnMouseWheeled(iDelta); return false; } void Base::OnMouseMoved(int /*x*/, int /*y*/, int /*deltaX*/, int /*deltaY*/) { } void Base::OnMouseEnter() { onHoverEnter.Call(this); if (GetToolTip()) ToolTip::Enable(this); else if (GetParent() && GetParent()->GetToolTip()) ToolTip::Enable(GetParent()); } void Base::OnMouseLeave() { onHoverLeave.Call(this); if (GetToolTip()) ToolTip::Disable(this); } bool Base::IsHovered() { return Gwen::HoveredControl == this; } bool Base::ShouldDrawHover() { return Gwen::MouseFocus == this || Gwen::MouseFocus == NULL; } bool Base::HasFocus() { return Gwen::KeyboardFocus == this; } void Base::Focus() { if (Gwen::KeyboardFocus == this) return; if (Gwen::KeyboardFocus) Gwen::KeyboardFocus->OnLostKeyboardFocus(); Gwen::KeyboardFocus = this; OnKeyboardFocus(); Redraw(); } void Base::Blur() { if (Gwen::KeyboardFocus != this) return; Gwen::KeyboardFocus = NULL; OnLostKeyboardFocus(); Redraw(); } bool Base::IsOnTop() { Base::List::iterator iter = GetParent()->Children.begin(); Base* pChild = *iter; if (pChild == this) return true; return false; } void Base::Touch() { if (GetParent()) GetParent()->OnChildTouched(this); } void Base::OnChildTouched(Controls::Base* /*pChild*/) { Touch(); } Base* Base::GetControlAt(int x, int y) { if (Hidden()) return NULL; if (x < 0 || y < 0 || x >= Width() || y >= Height()) return NULL; Base::List::reverse_iterator iter; for (iter = Children.rbegin(); iter != Children.rend(); ++iter) { Base* pChild = *iter; Base* pFound = NULL; pFound = pChild->GetControlAt(x - pChild->X(), y - pChild->Y()); if (pFound) return pFound; } if (!GetMouseInputEnabled()) return NULL; return this; } void Base::Layout(Skin::Base* skin) { if (skin->GetRender()->GetCTT() && ShouldCacheToTexture()) skin->GetRender()->GetCTT()->CreateControlCacheTexture(this); } int avoidUpdate = -15; void Base::RecurseLayout(Skin::Base* skin) { if (m_Skin) skin = m_Skin; if (Hidden()) return; if (NeedsLayout()) { m_bNeedsLayout = false; Layout(skin); } if (avoidUpdate > 0) return; Gwen::Rect rBounds = GetRenderBounds(); // Adjust bounds for padding rBounds.x += m_Padding.left; rBounds.w -= m_Padding.left + m_Padding.right; rBounds.y += m_Padding.top; rBounds.h -= m_Padding.top + m_Padding.bottom; int sz = Children.size(); if (sz > 100) { // printf("!\n"); } int curChild = 0; for (Base::List::iterator iter = Children.begin(); iter != Children.end(); ++iter) { Base* pChild = *iter; curChild++; if (pChild->Hidden()) continue; int iDock = pChild->GetDock(); if (iDock & Pos::Fill) continue; if (iDock & Pos::Top) { const Margin& margin = pChild->GetMargin(); pChild->SetBounds(rBounds.x + margin.left, rBounds.y + margin.top, rBounds.w - margin.left - margin.right, pChild->Height()); int iHeight = margin.top + margin.bottom + pChild->Height(); rBounds.y += iHeight; rBounds.h -= iHeight; } if (iDock & Pos::Left) { const Margin& margin = pChild->GetMargin(); pChild->SetBounds(rBounds.x + margin.left, rBounds.y + margin.top, pChild->Width(), rBounds.h - margin.top - margin.bottom); int iWidth = margin.left + margin.right + pChild->Width(); rBounds.x += iWidth; rBounds.w -= iWidth; } if (iDock & Pos::Right) { // TODO: THIS MARGIN CODE MIGHT NOT BE FULLY FUNCTIONAL const Margin& margin = pChild->GetMargin(); pChild->SetBounds((rBounds.x + rBounds.w) - pChild->Width() - margin.right, rBounds.y + margin.top, pChild->Width(), rBounds.h - margin.top - margin.bottom); int iWidth = margin.left + margin.right + pChild->Width(); rBounds.w -= iWidth; } if (iDock & Pos::Bottom) { // TODO: THIS MARGIN CODE MIGHT NOT BE FULLY FUNCTIONAL const Margin& margin = pChild->GetMargin(); pChild->SetBounds(rBounds.x + margin.left, (rBounds.y + rBounds.h) - pChild->Height() - margin.bottom, rBounds.w - margin.left - margin.right, pChild->Height()); rBounds.h -= pChild->Height() + margin.bottom + margin.top; } pChild->RecurseLayout(skin); } m_InnerBounds = rBounds; curChild = 0; // // Fill uses the left over space, so do that now. // for (Base::List::iterator iter = Children.begin(); iter != Children.end(); ++iter) { Base* pChild = *iter; int iDock = pChild->GetDock(); curChild++; if (!(iDock & Pos::Fill)) continue; const Margin& margin = pChild->GetMargin(); pChild->SetBounds(rBounds.x + margin.left, rBounds.y + margin.top, rBounds.w - margin.left - margin.right, rBounds.h - margin.top - margin.bottom); pChild->RecurseLayout(skin); } PostLayout(skin); if (IsTabable()) { if (!GetCanvas()->FirstTab) GetCanvas()->FirstTab = this; if (!GetCanvas()->NextTab) GetCanvas()->NextTab = this; } if (Gwen::KeyboardFocus == this) { GetCanvas()->NextTab = NULL; } } bool Base::IsChild(Controls::Base* pChild) { for (Base::List::iterator iter = Children.begin(); iter != Children.end(); ++iter) { if (pChild == (*iter)) return true; } return false; } Gwen::Point Base::LocalPosToCanvas(const Gwen::Point& pnt) { if (m_Parent) { int x = pnt.x + X(); int y = pnt.y + Y(); // If our parent has an innerpanel and we're a child of it // add its offset onto us. // if (m_Parent->m_InnerPanel && m_Parent->m_InnerPanel->IsChild(this)) { x += m_Parent->m_InnerPanel->X(); y += m_Parent->m_InnerPanel->Y(); } return m_Parent->LocalPosToCanvas(Gwen::Point(x, y)); } return pnt; } Gwen::Point Base::CanvasPosToLocal(const Gwen::Point& pnt) { if (m_Parent) { int x = pnt.x - X(); int y = pnt.y - Y(); // If our parent has an innerpanel and we're a child of it // add its offset onto us. // if (m_Parent->m_InnerPanel && m_Parent->m_InnerPanel->IsChild(this)) { x -= m_Parent->m_InnerPanel->X(); y -= m_Parent->m_InnerPanel->Y(); } return m_Parent->CanvasPosToLocal(Gwen::Point(x, y)); } return pnt; } bool Base::IsMenuComponent() { if (!m_Parent) return false; return m_Parent->IsMenuComponent(); } void Base::CloseMenus() { for (Base::List::iterator it = Children.begin(); it != Children.end(); ++it) { (*it)->CloseMenus(); } } void Base::UpdateRenderBounds() { m_RenderBounds.x = 0; m_RenderBounds.x = 0; m_RenderBounds.w = m_Bounds.w; m_RenderBounds.h = m_Bounds.h; } void Base::UpdateCursor() { Platform::SetCursor(m_Cursor); } DragAndDrop::Package* Base::DragAndDrop_GetPackage(int /*x*/, int /*y*/) { return m_DragAndDrop_Package; } bool Base::DragAndDrop_HandleDrop(Gwen::DragAndDrop::Package* /*pPackage*/, int /*x*/, int /*y*/) { DragAndDrop::SourceControl->SetParent(this); return true; } bool Base::DragAndDrop_Draggable() { if (!m_DragAndDrop_Package) return false; return m_DragAndDrop_Package->draggable; } void Base::DragAndDrop_SetPackage(bool bDraggable, const String& strName, void* pUserData) { if (!m_DragAndDrop_Package) { m_DragAndDrop_Package = new Gwen::DragAndDrop::Package(); } m_DragAndDrop_Package->draggable = bDraggable; m_DragAndDrop_Package->name = strName; m_DragAndDrop_Package->userdata = pUserData; } void Base::DragAndDrop_StartDragging(Gwen::DragAndDrop::Package* pPackage, int x, int y) { pPackage->holdoffset = CanvasPosToLocal(Gwen::Point(x, y)); pPackage->drawcontrol = this; } bool Base::SizeToChildren(bool w, bool h) { Gwen::Point size = ChildrenSize(); return SetSize(w ? size.x : Width(), h ? size.y : Height()); } Gwen::Point Base::ChildrenSize() { Gwen::Point size; for (Base::List::iterator iter = Children.begin(); iter != Children.end(); ++iter) { Base* pChild = *iter; if (pChild->Hidden()) continue; size.x = GwenUtil_Max(size.x, pChild->Right()); size.y = GwenUtil_Max(size.y, pChild->Bottom()); } return size; } void Base::SetPadding(const Padding& padding) { if (m_Padding.left == padding.left && m_Padding.top == padding.top && m_Padding.right == padding.right && m_Padding.bottom == padding.bottom) return; m_Padding = padding; Invalidate(); InvalidateParent(); } void Base::SetMargin(const Margin& margin) { if (m_Margin.top == margin.top && m_Margin.left == margin.left && m_Margin.bottom == margin.bottom && m_Margin.right == margin.right) return; m_Margin = margin; Invalidate(); InvalidateParent(); } bool Base::HandleAccelerator(Gwen::UnicodeString& accelerator) { if (Gwen::KeyboardFocus == this || !AccelOnlyFocus()) { AccelMap::iterator iter = m_Accelerators.find(accelerator); if (iter != m_Accelerators.end()) { iter->second->Call(this); return true; } } for (Base::List::iterator it = Children.begin(); it != Children.end(); ++it) { if ((*it)->HandleAccelerator(accelerator)) return true; } return false; } bool Base::OnKeyPress(int iKey, bool bPress) { bool bHandled = false; switch (iKey) { case Key::Tab: bHandled = OnKeyTab(bPress); break; case Key::Space: bHandled = OnKeySpace(bPress); break; case Key::Home: bHandled = OnKeyHome(bPress); break; case Key::End: bHandled = OnKeyEnd(bPress); break; case Key::Return: bHandled = OnKeyReturn(bPress); break; case Key::Backspace: bHandled = OnKeyBackspace(bPress); break; case Key::Delete: bHandled = OnKeyDelete(bPress); break; case Key::Right: bHandled = OnKeyRight(bPress); break; case Key::Left: bHandled = OnKeyLeft(bPress); break; case Key::Up: bHandled = OnKeyUp(bPress); break; case Key::Down: bHandled = OnKeyDown(bPress); break; case Key::Escape: bHandled = OnKeyEscape(bPress); break; default: break; } if (!bHandled && GetParent()) GetParent()->OnKeyPress(iKey, bPress); return bHandled; } bool Base::OnKeyRelease(int iKey) { return OnKeyPress(iKey, false); } bool Base::OnKeyTab(bool bDown) { if (!bDown) return true; if (GetCanvas()->NextTab) { GetCanvas()->NextTab->Focus(); Redraw(); } return true; } void Base::RenderFocus(Gwen::Skin::Base* skin) { if (Gwen::KeyboardFocus != this) return; if (!IsTabable()) return; skin->DrawKeyboardHighlight(this, GetRenderBounds(), 3); } void Base::SetToolTip(const String& strText) { SetToolTip(Gwen::Utility::StringToUnicode(strText)); } void Base::SetToolTip(const UnicodeString& strText) { Label* tooltip = new Label(this); tooltip->SetText(strText); tooltip->SizeToContents(); SetToolTip(tooltip); } #ifndef GWEN_NO_ANIMATION void Base::Anim_WidthIn(float fLength, float fDelay, float fEase) { Gwen::Anim::Add(this, new Gwen::Anim::Size::Width(0, Width(), fLength, false, fDelay, fEase)); SetWidth(0); } void Base::Anim_HeightIn(float fLength, float fDelay, float fEase) { Gwen::Anim::Add(this, new Gwen::Anim::Size::Height(0, Height(), fLength, false, fDelay, fEase)); SetHeight(0); } void Base::Anim_WidthOut(float fLength, bool bHide, float fDelay, float fEase) { Gwen::Anim::Add(this, new Gwen::Anim::Size::Width(Width(), 0, fLength, bHide, fDelay, fEase)); } void Base::Anim_HeightOut(float fLength, bool bHide, float fDelay, float fEase) { Gwen::Anim::Add(this, new Gwen::Anim::Size::Height(Height(), 0, fLength, bHide, fDelay, fEase)); } #endif