You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
555 lines
14 KiB
555 lines
14 KiB
#include <vgui_controls/JoypadFocus.h>
|
|
#include <vgui_controls/ImagePanelColored.h>
|
|
#include <vgui/MouseCode.h>
|
|
#include <vgui/ISystem.h>
|
|
#include <vgui/ISurface.h>
|
|
#include "keydefs.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
CJoypadFocus::CJoypadFocus()
|
|
{
|
|
m_bJoypadMode = false;
|
|
m_bDebugOutput = false;
|
|
m_iModal = 0;
|
|
|
|
m_FocusAreas.Purge();
|
|
m_CurrentFocus.bClickOnFocus = false;
|
|
m_CurrentFocus.hPanel = NULL;
|
|
|
|
m_KeyNum[JF_KEY_UP] = K_AXISY_POS;
|
|
m_KeyNum[JF_KEY_DOWN] = K_AXISY_NEG;
|
|
m_KeyNum[JF_KEY_LEFT] = K_AXISX_NEG;
|
|
m_KeyNum[JF_KEY_RIGHT] = K_AXISX_POS;
|
|
m_KeyNum[JF_KEY_CONFIRM] = K_JOY1;
|
|
m_KeyNum[JF_KEY_CANCEL] = K_JOY2;
|
|
|
|
for (int i=0;i<NUM_JF_KEYS;i++)
|
|
{
|
|
m_bKeyDown[i] = false;
|
|
m_fNextKeyRepeatTime[i] = 0;
|
|
}
|
|
}
|
|
|
|
void CJoypadFocus::SetJoypadCodes(int iUpCode, int iDownCode, int iLeftCode, int iRightCode, int iConfirmCode, int iCancelCode)
|
|
{
|
|
m_KeyNum[JF_KEY_UP] = iUpCode;
|
|
m_KeyNum[JF_KEY_DOWN] = iDownCode;
|
|
m_KeyNum[JF_KEY_LEFT] = iLeftCode;
|
|
m_KeyNum[JF_KEY_RIGHT] = iRightCode;
|
|
m_KeyNum[JF_KEY_CONFIRM] = iConfirmCode;
|
|
m_KeyNum[JF_KEY_CANCEL] = iCancelCode;
|
|
}
|
|
|
|
void CJoypadFocus::AddToFocusList(vgui::Panel* pPanel, bool bClickOnFocus, bool bModal)
|
|
{
|
|
if (!pPanel)
|
|
return;
|
|
|
|
FocusArea Focus;
|
|
Focus.hPanel = pPanel;
|
|
Focus.bClickOnFocus = bClickOnFocus;
|
|
Focus.bModal = bModal;
|
|
if (bModal)
|
|
m_iModal++;
|
|
vgui::PHandle hOtherPanel;
|
|
for (int i=0;i<m_FocusAreas.Count();i++)
|
|
{
|
|
hOtherPanel = m_FocusAreas[i].hPanel;
|
|
if (hOtherPanel == Focus.hPanel) // check if it's already here
|
|
return;
|
|
}
|
|
if (m_bDebugOutput)
|
|
Msg("adding panel to joypad focus list: %s:%s\n", pPanel->GetName(), pPanel->GetClassName());
|
|
//if (GetFocusPanel() == NULL)
|
|
//{
|
|
//SetFocusPanel(pPanel, bClickOnFocus);
|
|
//}
|
|
m_FocusAreas.AddToTail(Focus);
|
|
}
|
|
|
|
// sets focus to one of the panels in the list of focus areas
|
|
void CJoypadFocus::SetFocusPanel(int index)
|
|
{
|
|
if (index < 0 || index >= m_FocusAreas.Count())
|
|
return;
|
|
|
|
SetFocusPanel(m_FocusAreas[index].hPanel, m_FocusAreas[index].bClickOnFocus);
|
|
}
|
|
|
|
void CJoypadFocus::SetFocusPanel(vgui::Panel* pPanel, bool bClickOnFocus)
|
|
{
|
|
m_CurrentFocus.hPanel = pPanel;
|
|
m_CurrentFocus.bClickOnFocus = bClickOnFocus;
|
|
if (pPanel)
|
|
{
|
|
if (m_bDebugOutput)
|
|
Msg("Setcur jfocus: %s:%s ", pPanel->GetName(), pPanel->GetClassName());
|
|
if (!m_hOutline.Get())
|
|
{
|
|
CJoypadOutline *pOutline = new CJoypadOutline(NULL, "JoypadOutline");
|
|
m_hOutline = pOutline;
|
|
if (m_bDebugOutput)
|
|
{
|
|
if (pOutline)
|
|
Msg("spawned outline\n");
|
|
else
|
|
Msg("Outline is zero!!\n");
|
|
}
|
|
}
|
|
if (m_hOutline.Get())
|
|
{
|
|
if (pPanel->GetParent())
|
|
m_hOutline->SetParent(pPanel->GetParent());
|
|
else
|
|
m_hOutline->SetParent(pPanel);
|
|
int x, y, w, t;
|
|
pPanel->GetJoypadCursorBounds(x, y, w, t);
|
|
|
|
m_hOutline->MoveToFront();
|
|
m_hOutline->SizeTo(x, y, w, t);
|
|
m_hOutline->SetMouseInputEnabled(false);
|
|
}
|
|
if (bClickOnFocus)
|
|
{
|
|
ClickFocusPanel(true, false);
|
|
ClickFocusPanel(false, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_bDebugOutput)
|
|
Msg("Cleared currently focused joypad panel\n");
|
|
}
|
|
}
|
|
|
|
vgui::Panel* CJoypadFocus::GetFocusPanel()
|
|
{
|
|
return m_CurrentFocus.hPanel;
|
|
}
|
|
|
|
void CJoypadFocus::RemoveFromFocusList(vgui::Panel* pPanel)
|
|
{
|
|
if (!pPanel)
|
|
return;
|
|
if (m_bDebugOutput)
|
|
Msg("removing panel from joypad focus list: %s:%s\n", pPanel->GetName(), pPanel->GetClassName());
|
|
vgui::PHandle hPanel;
|
|
hPanel = pPanel;
|
|
for (int i=m_FocusAreas.Count()-1;i>=0;i--)
|
|
{
|
|
if (m_FocusAreas[i].hPanel == hPanel)
|
|
{
|
|
if (m_FocusAreas[i].bModal)
|
|
m_iModal--;
|
|
m_FocusAreas.Remove(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CJoypadFocus::OnJoypadButtonPressed(int keynum)
|
|
{
|
|
if (!IsJoypadMode())
|
|
return false;
|
|
|
|
// don't allow multiple direction presses at once
|
|
int iDirectionPress = -1;
|
|
|
|
for (int i=0;i<4;i++)
|
|
{
|
|
if (m_KeyNum[i] == keynum)
|
|
iDirectionPress = i;
|
|
}
|
|
bool bDirectionAlreadyDown = false;
|
|
if (iDirectionPress != -1)
|
|
{
|
|
for (int i=0;i<4;i++)
|
|
{
|
|
if (m_bKeyDown[i] && i != iDirectionPress) // if it's a direction we're already pushing, we allow it?
|
|
bDirectionAlreadyDown = true;
|
|
}
|
|
// abort if we're pushing a direction and another direction is already down
|
|
if (bDirectionAlreadyDown)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (int i=0;i<NUM_JF_KEYS;i++)
|
|
{
|
|
if (m_KeyNum[i] == keynum)
|
|
{
|
|
m_bKeyDown[i] = true;
|
|
m_fNextKeyRepeatTime[i] = vgui::system()->GetTimeMillis() + JF_KEY_REPEAT_DELAY;
|
|
}
|
|
}
|
|
|
|
if (keynum == m_KeyNum[JF_KEY_UP])
|
|
{
|
|
int index = FindNextPanel(GetFocusPanel(), 270);
|
|
if (index != -1)
|
|
SetFocusPanel(index);
|
|
return (GetFocusPanel() != NULL);
|
|
}
|
|
else if (keynum == m_KeyNum[JF_KEY_DOWN])
|
|
{
|
|
int index = FindNextPanel(GetFocusPanel(), 90);
|
|
if (index != -1)
|
|
SetFocusPanel(index);
|
|
return (GetFocusPanel() != NULL);
|
|
}
|
|
else if (keynum == m_KeyNum[JF_KEY_LEFT])
|
|
{
|
|
int index = FindNextPanel(GetFocusPanel(), 180);
|
|
if (index != -1)
|
|
SetFocusPanel(index);
|
|
return (GetFocusPanel() != NULL);
|
|
}
|
|
else if (keynum == m_KeyNum[JF_KEY_RIGHT])
|
|
{
|
|
int index = FindNextPanel(GetFocusPanel(), 0);
|
|
if (index != -1)
|
|
SetFocusPanel(index);
|
|
return (GetFocusPanel() != NULL);
|
|
}
|
|
else if (keynum == m_KeyNum[JF_KEY_CONFIRM])
|
|
{
|
|
if (m_CurrentFocus.bClickOnFocus)
|
|
DoubleClickFocusPanel(false);
|
|
else
|
|
ClickFocusPanel(true, false);
|
|
|
|
return (GetFocusPanel() != NULL);
|
|
}
|
|
else if (keynum == m_KeyNum[JF_KEY_CANCEL])
|
|
{
|
|
ClickFocusPanel(true, true);
|
|
return (GetFocusPanel() != NULL);
|
|
}
|
|
// don't swallow non-direction or confirm/cancel keys
|
|
return false;
|
|
}
|
|
|
|
bool CJoypadFocus::OnJoypadButtonReleased(int keynum)
|
|
{
|
|
if (!IsJoypadMode())
|
|
return false;
|
|
|
|
for (int i=0;i<NUM_JF_KEYS;i++)
|
|
{
|
|
if (m_KeyNum[i] == keynum)
|
|
{
|
|
m_bKeyDown[i] = false;
|
|
m_fNextKeyRepeatTime[i] = 0;
|
|
}
|
|
}
|
|
|
|
if (keynum == m_KeyNum[JF_KEY_CONFIRM])
|
|
{
|
|
ClickFocusPanel(false, false);
|
|
return (GetFocusPanel() != NULL);
|
|
}
|
|
else if (keynum == m_KeyNum[JF_KEY_CANCEL])
|
|
{
|
|
ClickFocusPanel(false, true);
|
|
return (GetFocusPanel() != NULL);
|
|
}
|
|
// don't swallow non confirm/cancel buttons
|
|
return false;
|
|
}
|
|
|
|
void CJoypadFocus::CheckKeyRepeats()
|
|
{
|
|
float curtime = vgui::system()->GetTimeMillis();
|
|
for (int i=0;i<NUM_JF_KEYS;i++)
|
|
{
|
|
if (m_fNextKeyRepeatTime[i]!=0 && curtime > m_fNextKeyRepeatTime[i])
|
|
{
|
|
// player is holding down the specified key, send another press
|
|
if (m_bDebugOutput)
|
|
Msg("Sending key repeat\n");
|
|
OnJoypadButtonPressed(m_KeyNum[i]);
|
|
m_fNextKeyRepeatTime[i] = curtime + JF_KEY_REPEAT_INTERVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CJoypadFocus::ClickFocusPanel(bool bDown, bool bRightMouse)
|
|
{
|
|
vgui::Panel *pOther = GetFocusPanel();
|
|
if (pOther)
|
|
{
|
|
if (bDown)
|
|
pOther->OnMousePressed(bRightMouse ? vgui::MOUSE_RIGHT : vgui::MOUSE_LEFT);
|
|
else
|
|
pOther->OnMouseReleased(bRightMouse ? vgui::MOUSE_RIGHT : vgui::MOUSE_LEFT);
|
|
}
|
|
}
|
|
|
|
void CJoypadFocus::DoubleClickFocusPanel(bool bRightMouse)
|
|
{
|
|
vgui::Panel *pOther = GetFocusPanel();
|
|
if (pOther)
|
|
{
|
|
pOther->OnMouseDoublePressed(bRightMouse ? vgui::MOUSE_RIGHT : vgui::MOUSE_LEFT);
|
|
}
|
|
}
|
|
|
|
// find a panel in the specified direction
|
|
int CJoypadFocus::FindNextPanel(vgui::Panel *pSource, float angle)
|
|
{
|
|
if (!pSource)
|
|
{
|
|
// no panel selected, should pick the top left most one
|
|
int iBestIndex = -1;
|
|
float fBestRating = -1;
|
|
for (int i=0;i<m_FocusAreas.Count();i++)
|
|
{
|
|
if (m_iModal>0 && !m_FocusAreas[i].bModal)
|
|
continue;
|
|
vgui::Panel *pPanel = m_FocusAreas[i].hPanel;
|
|
if (!pPanel)
|
|
continue;
|
|
|
|
int x, y;
|
|
pPanel->GetPos(x, y);
|
|
float fRating = x * 1.3 + y;
|
|
if (fBestRating == -1 || fRating < fBestRating)
|
|
{
|
|
fBestRating = fRating;
|
|
iBestIndex = i;
|
|
}
|
|
}
|
|
return iBestIndex;
|
|
}
|
|
|
|
if (m_bDebugOutput)
|
|
Msg("angle = %f ", angle);
|
|
float radangle = angle * (3.14159265f / 180.0f);
|
|
float xdir = cos(radangle);
|
|
float ydir = sin(radangle);
|
|
if (m_bDebugOutput)
|
|
Msg("xdir = %f ydir = %f\n", xdir, ydir);
|
|
|
|
//Vector2D dir(xdir, ydir);
|
|
//dir.NormalizeInPlace(); // normalization unnecessary?
|
|
|
|
// find the centre of our panel
|
|
int x, y, w, t;
|
|
pSource->GetBounds(x, y, w, t);
|
|
int posx = w * 0.5f;
|
|
int posy = t * 0.5f;
|
|
pSource->LocalToScreen(posx, posy);
|
|
|
|
// position the source dot at the middle of the edge in the direction we're searching in
|
|
posx += (w * 0.5f) * xdir - xdir;
|
|
posy += (t * 0.5f) * ydir - ydir;
|
|
|
|
//Vector2D vecSource(posx, posy);
|
|
|
|
// go through all panels, see if they're in the right direction and rate them
|
|
vgui::Panel *pBest = NULL;
|
|
int iBestIndex = -1;
|
|
float fBestRating = -1;
|
|
for (int i=0;i<m_FocusAreas.Count();i++)
|
|
{
|
|
vgui::Panel* pOther = m_FocusAreas[i].hPanel;
|
|
if (!pOther || pOther == pSource || !IsPanelReallyVisible(pOther))
|
|
continue;
|
|
if (m_iModal>0 && !m_FocusAreas[i].bModal)
|
|
continue;
|
|
// check if it's within our arc
|
|
int w2, t2;
|
|
pOther->GetSize(w2, t2);
|
|
int posx2 = w2 * 0.5f;
|
|
int posy2 = t2 * 0.5f;
|
|
pOther->LocalToScreen(posx2, posy2);
|
|
// pick the point in our bounds closest to the source point
|
|
if (posx < posx2)
|
|
posx2 = max((posx2 - w2 * 0.5f), posx);
|
|
else if (posx > posx2)
|
|
posx2 = min((posx2 + w2 * 0.5f), posx);
|
|
|
|
if (posy < posy2)
|
|
posy2 = max((posy2 - t2 * 0.5f), posy);
|
|
else if (posy > posy2)
|
|
posy2 = min((posy2 + t2 * 0.5f), posy);
|
|
//Vector2D vecOther(posx2, posy2);
|
|
|
|
float diffx = posx2 - posx;
|
|
float diffy = posy2 - posy;
|
|
float diff_len = sqrt(diffx * diffx + diffy * diffy);
|
|
if (diff_len <= 0)
|
|
diff_len = 0.1f;
|
|
float diffx_norm = diffx / diff_len;
|
|
float diffy_norm = diffy / diff_len;
|
|
|
|
float the_dot = 0;
|
|
the_dot += diffx_norm * xdir;
|
|
the_dot += diffy_norm * ydir;
|
|
//Vector2D difference;
|
|
//difference = vecOther - vecSource;
|
|
//Vector2D diffnorm = difference;
|
|
//diffnorm.NormalizeInPlace();
|
|
if (m_bDebugOutput)
|
|
Msg("Checking panel %i (%s). diff=%f,%f dir=%f, %f dot=%f\n", i, pOther->GetClassName(), diffx, diffy, xdir, ydir, the_dot);
|
|
|
|
if (the_dot > 0.3f)
|
|
{
|
|
// this panel is in the right direction, now rate it
|
|
float fRating = -1;
|
|
if (angle == 0 || angle == 90 || angle == 180 || angle == 270) // we're searching in a perpendicular direction, so we can do a more accurate rating
|
|
{
|
|
fRating = 0;
|
|
// double perpendicular distance cost
|
|
if (angle == 90 || angle == 270)
|
|
{
|
|
if (m_bDebugOutput)
|
|
Msg(" vertical rating: %f * 3 + %f\n", fabs(diffx), fabs(diffy));
|
|
fRating = fabs(diffy) + (fabs(diffx) * 3.0f);
|
|
}
|
|
else
|
|
{
|
|
if (m_bDebugOutput)
|
|
Msg(" horiz rating: %f + %f * 3\n", fabs(diffx), fabs(diffy));
|
|
fRating = fabs(diffx) + (fabs(diffy) * 3.0f);
|
|
}
|
|
}
|
|
else // strange angle, just rate based on distance
|
|
{
|
|
if (m_bDebugOutput)
|
|
Msg(" distancebased rating\n");
|
|
fRating = diff_len;
|
|
}
|
|
if (m_bDebugOutput)
|
|
Msg(" Panel is in right dir, rating = %f\n", fRating);
|
|
// if this panel is better, remember it
|
|
if (pBest == NULL || (fRating != -1 && fRating < fBestRating))
|
|
{
|
|
if (m_bDebugOutput)
|
|
Msg(" this is the new best!\n");
|
|
pBest = pOther;
|
|
iBestIndex = i;
|
|
fBestRating = fRating;
|
|
}
|
|
}
|
|
}
|
|
return iBestIndex;
|
|
}
|
|
|
|
bool CJoypadFocus::IsPanelReallyVisible(vgui::Panel *pPanel)
|
|
{
|
|
while (pPanel->IsVisible() && pPanel->GetAlpha() >= 255) // todo: make required alpha an arg
|
|
{
|
|
pPanel = pPanel->GetParent();
|
|
if (!pPanel)
|
|
return true; // got to the top without hitting something that wasn't visible
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ================================================================================
|
|
|
|
CJoypadFocus* g_pJoypadFocus = NULL;
|
|
|
|
CJoypadFocus* GetJoypadFocus()
|
|
{
|
|
if (g_pJoypadFocus == NULL)
|
|
{
|
|
g_pJoypadFocus = new CJoypadFocus();
|
|
}
|
|
return g_pJoypadFocus;
|
|
}
|
|
|
|
// ================================================================================
|
|
|
|
CJoypadOutline::CJoypadOutline(vgui::Panel *parent, const char *name) : vgui::Panel(parent, name)
|
|
{
|
|
SetMouseInputEnabled(false);
|
|
SetAlpha(0);
|
|
m_hLastFocusPanel = NULL;
|
|
//m_pImagePanel = new vgui::ImagePanelColored(this, "ImagePanel");
|
|
//m_pImagePanel->SetImage("swarm/JoypadCursor");
|
|
//m_pImagePanel->SetShouldScaleImage(true);
|
|
}
|
|
|
|
void CJoypadOutline::ApplySchemeSettings(vgui::IScheme* pScheme)
|
|
{
|
|
BaseClass::ApplySchemeSettings(pScheme);
|
|
|
|
SetPaintBackgroundEnabled(false);
|
|
//SetPaintBackgroundType(0);
|
|
//SetBgColor(Color(255,0,0,64));
|
|
}
|
|
|
|
void CJoypadOutline::Paint()
|
|
{
|
|
long curtime = vgui::system()->GetTimeMillis();
|
|
int pulse_time = 1000;
|
|
int half_pulse_time = pulse_time / 2;
|
|
long remainder = curtime % pulse_time;
|
|
if (remainder > half_pulse_time)
|
|
remainder = half_pulse_time - (remainder - half_pulse_time);
|
|
float fWhite = float(remainder) / float(half_pulse_time);
|
|
//int red = 66.0f + (255.0f - 66.0f) * fWhite;
|
|
//int green = 142.0f + (255.0f - 142.0f) * fWhite;
|
|
//int blue = 192.0f + (255.0f - 192.0f) * fWhite;
|
|
int red = 255.0f + (255.0f - 255) * fWhite;
|
|
int green = 0 + (255.0f - 0) * fWhite;
|
|
int blue = 0;
|
|
Color col(red, green, blue, 255);
|
|
DrawBox( 0, 0, GetWide(), GetTall(), col, 0.8f, true );
|
|
}
|
|
|
|
void CJoypadOutline::GetCornerTextureSize( int& w, int& h )
|
|
{
|
|
BaseClass::GetCornerTextureSize(w, h);
|
|
|
|
int sw, sh;
|
|
vgui::surface()->GetScreenSize( sw, sh );
|
|
float fScale = sh / 768.0f;
|
|
w *= fScale * 0.25f;
|
|
h *= fScale * 0.25f;
|
|
}
|
|
|
|
// hide/show us and position us over the focused panel
|
|
void CJoypadOutline::OnThink()
|
|
{
|
|
if (!GetJoypadFocus())
|
|
return;
|
|
|
|
vgui::Panel* pFocus = GetJoypadFocus()->GetFocusPanel();
|
|
//Msg("outline thinking, focus = %s\n", pFocus ? pFocus->GetClassName() : "none");
|
|
if (pFocus && GetJoypadFocus()->IsJoypadMode())
|
|
{
|
|
if (pFocus != m_hLastFocusPanel.Get())
|
|
{
|
|
m_hLastFocusPanel = pFocus;
|
|
SetAlpha(255);
|
|
}
|
|
if (pFocus->GetParent())
|
|
SetParent(pFocus->GetParent());
|
|
else
|
|
SetParent(pFocus);
|
|
// make sure we're positioned over the focused panel
|
|
int x, y, w, t;
|
|
pFocus->GetJoypadCursorBounds(x, y, w, t);
|
|
SizeTo(x, y, w, t);
|
|
|
|
GetJoypadFocus()->CheckKeyRepeats();
|
|
|
|
SetMouseInputEnabled(false);
|
|
}
|
|
else
|
|
{
|
|
SetAlpha(0);
|
|
SetMouseInputEnabled(false);
|
|
}
|
|
}
|
|
|
|
void CJoypadOutline::SizeTo(int x, int y, int w, int t)
|
|
{
|
|
SetBounds(x, y, w, t);
|
|
//m_pImagePanel->SetBounds(0, 0, w, t);
|
|
}
|