hostile-takeover/game/fingerhandler.cpp
2014-07-06 17:47:28 -07:00

746 lines
19 KiB
C++

#include "game/ht.h"
#include "base/tick.h"
namespace wi {
const int SMALL_DRAG = kwcTile / 4;
const int MEDIUM_DRAG = kwcTile / 3;
const int LARGE_DRAG = kwcTile / 2;
const long QUICK_UP_TICKS = 20;
SimUIForm::FingerHandler::FingerHandler(SimUIForm *psui) {
m_psui = psui;
m_gidHitLast = kgidNull;
m_gidHilight = kgidNull;
m_xDownLast = 0;
m_yDownLast = 0;
m_state = FHS_NONE;
m_fShowUnitMenu = false;
m_pfrmUnitTitle = NULL;
m_ff = 0;
// Tell the placement form to pass input through, which will
// allow the this handler to scroll the playfield.
if (gpfrmPlace != NULL) {
gpfrmPlace->SetPassOnInput(true);
}
m_pselspr = gpsprm->CreateSelectionSprite();
if (m_pselspr != NULL) {
m_pselspr->Show(false);
}
}
SimUIForm::FingerHandler::~FingerHandler() {
delete m_pfrmUnitTitle;
delete m_pselspr;
}
bool SimUIForm::FingerHandler::OnPenEvent(Event *pevt, bool fScrollOnly) {
#if 0
switch (pevt->eType) {
case penDownEvent:
IPhone::Log("penDownEvent x=%d y=%d", pevt->x, pevt->y);
break;
case penDownEvent2:
IPhone::Log("penDownEvent2 x=%d y=%d", pevt->x, pevt->y);
break;
case penUpEvent:
IPhone::Log("penUpEvent x=%d y=%d", pevt->x, pevt->y);
break;
case penUpEvent2:
IPhone::Log("penUpEvent2 x=%d y=%d", pevt->x, pevt->y);
break;
case penMoveEvent:
IPhone::Log("penMoveEvent x=%d y=%d", pevt->x, pevt->y);
break;
case penMoveEvent2:
IPhone::Log("penMoveEvent2 x=%d y=%d", pevt->x, pevt->y);
break;
case penHoldEvent:
IPhone::Log("penHoldEvent x=%d y=%d", pevt->x, pevt->y);
break;
}
#endif
switch (pevt->eType) {
case penDownEvent:
OnPenDown(pevt, fScrollOnly);
break;
case penDownEvent2:
OnPenDown2(pevt, fScrollOnly);
break;
case penUpEvent:
OnPenUp(pevt, fScrollOnly);
break;
case penUpEvent2:
OnPenUp2(pevt, fScrollOnly);
break;
case penMoveEvent:
OnPenMove(pevt);
break;
case penMoveEvent2:
OnPenMove2(pevt);
break;
case penHoldEvent:
OnPenHold(pevt);
break;
default:
return false;
}
return true;
}
void SimUIForm::FingerHandler::OnPenDown(Event *pevt, bool fScrollOnly) {
m_tDown = base::GetTickCount();
m_xDownLast = pevt->x;
m_yDownLast = pevt->y;
m_x1 = pevt->x;
m_y1 = pevt->y;
m_ff |= kfPhFinger1Down;
// The first finger can come down while in FHS_SELECT mode
if (m_state == FHS_SELECT) {
UpdateSelect(pevt);
return;
}
// Not in select mode, then there should be no modes operating
Gob *pgob = m_psui->HitTestGob(pevt->x, pevt->y, true, &m_wxTarget,
&m_wyTarget, &m_fHitSurrounding);
// Pretend no gob hittesting if it is in fog
if (pgob != NULL) {
TRect trc;
pgob->GetTileRect(&trc);
if (gsim.GetLevel()->GetFogMap()->IsCovered(&trc)) {
pgob = NULL;
}
}
m_gidHitLast = (pgob == NULL) ? kgidNull : pgob->GetId();
EnterNone();
if (fScrollOnly) {
EnterDrag(pevt);
return;
}
if (CheckSelect(pevt)) {
EnterSelect(pevt);
return;
}
if (pgob != NULL) {
EnterHilight(pgob);
return;
}
// Enter drag right away to make the UI responsive
EnterDrag(pevt);
}
void SimUIForm::FingerHandler::OnPenDown2(Event *pevt, bool fScrollOnly) {
m_x2 = pevt->x;
m_y2 = pevt->y;
m_ff |= kfPhFinger2Down;
// fScrollOnly is set if in observe mode, or in structure placement mode
if (fScrollOnly) {
return;
}
// May already be in FHS_SELECT when finger 2 comes down
if (m_state == FHS_SELECT) {
UpdateSelect(pevt);
return;
}
// Otherwise, enter it
if (CheckSelect(pevt)) {
EnterSelect(pevt);
return;
}
}
void SimUIForm::FingerHandler::OnPenMove(Event *pevt) {
m_x1 = pevt->x;
m_y1 = pevt->y;
if (m_state == FHS_DRAG) {
UpdateDrag(pevt);
return;
}
if (m_state == FHS_SELECT) {
UpdateSelect(pevt);
return;
}
if (m_state == FHS_HILIGHT) {
if (CheckDragged(pevt, LARGE_DRAG)) {
EnterDrag(pevt);
}
}
}
void SimUIForm::FingerHandler::OnPenMove2(Event *pevt) {
m_x2 = pevt->x;
m_y2 = pevt->y;
if (m_state == FHS_SELECT) {
UpdateSelect(pevt);
return;
}
}
void SimUIForm::FingerHandler::OnPenUp(Event *pevt, bool fScrollOnly) {
m_x1 = pevt->x;
m_y1 = pevt->y;
m_ff &= ~kfPhFinger1Down;
// The form manager sometimes posts these when modes need to be
// gracefully broken out of when a new form shows during capture.
if (pevt->dw == 1) {
m_ff &= ~(kfPhFinger1Down | kfPhFinger2Down);
EnterNone();
return;
}
bool fTarget = false;
switch (m_state) {
case FHS_DRAG:
// Try to detect a quick targetting jab and don't scroll the map
if (IsQuickUp(pevt)) {
fTarget = !CheckDragged(pevt, MEDIUM_DRAG);
} else {
fTarget = !CheckDragged(pevt, SMALL_DRAG);
}
if (fTarget) {
break;
}
// fall through
default:
// Update the position, since there may be a new position
OnPenMove(pevt);
}
if (m_state == FHS_NONE) {
return;
}
if (m_state == FHS_SELECT) {
if (m_ff & kfPhFinger2Down) {
UpdateSelect(pevt);
} else {
EnterNone();
}
return;
}
if (m_state == FHS_DRAG) {
if (gpfrmPlace != NULL && gpfrmPlace->IsBusy()) {
if (!CheckDragged(pevt, LARGE_DRAG)) {
gpfrmPlace->UpdatePosition(pevt);
}
EnterNone();
return;
}
if (fTarget && !fScrollOnly) {
m_psui->MoveOrAttackOrSelect(NULL, m_wxTarget, m_wyTarget,
kfMasMove);
}
EnterNone();
return;
}
// Select or attack hilighted gob.
if (m_state == FHS_HILIGHT) {
if (m_gidHilight == kgidNull) {
EnterNone();
return;
}
// EnterNone before showing the menu since that is modal
Gob *pgobT = ggobm.GetGob(m_gidHilight);
bool fShowUnitMenu = m_fShowUnitMenu;
EnterNone();
if (pgobT == NULL || (pgobT->GetFlags() & kfGobUnit) == 0) {
return;
}
// Structures always get their menu shown when selected this way.
if (pgobT->GetFlags() & kfGobStructure) {
fShowUnitMenu = true;
}
// If showing the unit menu, make sure the command is selection.
// When a miner targets a friendly processor, it isn't selection for
// example.
if (fShowUnitMenu && !m_psui->IsSelectionCommand(pgobT)) {
fShowUnitMenu = false;
}
// Select or attack
m_psui->MoveOrAttackOrSelect(pgobT, m_wxTarget, m_wyTarget,
kfMasSelect | kfMasAttack);
// Show the menu last since it is modal
if (fShowUnitMenu) {
m_psui->ShowUnitMenu(pgobT);
}
return;
}
// What is this state?
Assert();
EnterNone();
}
void SimUIForm::FingerHandler::OnPenUp2(Event *pevt, bool fScrollOnly) {
m_ff &= ~kfPhFinger2Down;
m_x2 = pevt->x;
m_y2 = pevt->y;
if (m_state == FHS_SELECT) {
if (m_ff & kfPhFinger1Down) {
UpdateSelect(pevt);
} else {
EnterNone();
}
return;
}
}
void SimUIForm::FingerHandler::OnPenHold(Event *pevt) {
if (m_state == FHS_HILIGHT) {
if (m_gidHilight == kgidNull) {
return;
}
// If actually hit on the surrounding gob hit test area,
// and not the gob itself, switch to drag. This allows for
// precise move targeting around gobs. This is only useful
// if there are friendly units selected.
if (m_fHitSurrounding && m_psui->HasSelectedUnits()) {
// Don't enter drag for the mobile HQ, so that transforming
// it is easier. This trades of nearby targetting with easy
// of transforming.
Gob *pgob = ggobm.GetGob(m_gidHilight);
if (pgob == NULL || pgob->GetType() != kgtMobileHeadquarters) {
EnterDrag(pevt);
return;
}
}
// Hit directly on gob. Don't change modes. This is simple
// to understand for the user. When then pen goes up,
// show the unit menu.
m_fShowUnitMenu = true;
// In the meantime, show the unit title. This gives confirmation
// to the user that they've held the pen long enough.
ShowUnitTitle(ggobm.GetGob(m_gidHilight));
return;
}
}
void SimUIForm::FingerHandler::UnhilightGob() {
if (m_gidHilight != kgidNull) {
Gob *pgobT = ggobm.GetGob(m_gidHilight);
if (pgobT != NULL && (pgobT->GetFlags() & kfGobUnit)) {
((UnitGob *)pgobT)->Hilight(false);
}
m_gidHilight = kgidNull;
}
}
void SimUIForm::FingerHandler::EnterNone() {
m_pselspr->Show(false);
UnhilightGob();
m_fShowUnitMenu = false;
delete m_pfrmUnitTitle;
m_pfrmUnitTitle = NULL;
m_state = FHS_NONE;
}
void SimUIForm::FingerHandler::EnterHilight(Gob *pgob) {
Assert((pgob->GetFlags() & kfGobUnit) != 0);
((UnitGob *)pgob)->Hilight(true);
m_gidHilight = pgob->GetId();
m_state = FHS_HILIGHT;
}
bool SimUIForm::FingerHandler::CheckSelect(Event *pevt) {
return (m_ff & (kfPhFinger1Down | kfPhFinger2Down)) ==
(kfPhFinger1Down | kfPhFinger2Down);
}
void SimUIForm::FingerHandler::EnterSelect(Event *pevt) {
// Leave current modes
EnterNone();
// Form a box around the two down points
Rect rc;
if (m_x1 < m_x2) {
rc.left = m_x1;
rc.right = m_x2;
} else {
rc.left = m_x2;
rc.right = m_x1;
}
if (m_y1 < m_y2) {
rc.top = m_y1;
rc.bottom = m_y2;
} else {
rc.top = m_y2;
rc.bottom = m_y1;
}
// Initialize the drag rect. It expects a bottom left coordinate
// system. If the rect is rotated 90 cw, then it fits SimUI's
// coordinate system if x/y are swapped.
DPoint pt0, pt1, pt2;
pt0.x = rc.top;
pt0.y = rc.left;
pt1.x = rc.bottom;
pt1.y = rc.left;
pt2.x = rc.bottom;
pt2.y = rc.right;
DragRect drc;
drc.Init(pt0, pt1, pt2);
// Figure out the tracking masks
#define kcpCloseEnough 32.0
DPoint ptA;
ptA.x = m_y1;
ptA.y = m_x1;
m_maskA = drc.HitTest(ptA, &m_vOffsetA);
if (m_vOffsetA.mag() > kcpCloseEnough) {
m_vOffsetA = Vec2d(0, 0);
}
DPoint ptB;
ptB.x = m_y2;
ptB.y = m_x2;
m_maskB = drc.HitTest(ptB, &m_vOffsetB);
if (m_vOffsetB.mag() > kcpCloseEnough) {
m_vOffsetB = Vec2d(0, 0);
}
// Show the drag rect
m_pselspr->SetDragRect(drc);
m_pselspr->Show(true);
// Redraw the screen so the selection updates
gevm.SetRedrawFlags(kfRedrawDirty | kfRedrawBeforeTimer);
// Set selection based on this rect
SetSelection();
// Enter FHS_SELECT mode
m_state = FHS_SELECT;
}
void SimUIForm::FingerHandler::UpdateSelect(Event *pevt) {
DPoint ptA, ptB;
ptA.x = m_y1;
ptA.y = m_x1;
ptB.x = m_y2;
ptB.y = m_x2;
// Update tracking masks
DragRect drc = m_pselspr->GetDragRect();
switch (pevt->eType) {
case penDownEvent:
m_maskA = drc.HitTest(ptA, &m_vOffsetA);
if (m_vOffsetA.mag() > kcpCloseEnough) {
m_vOffsetA = Vec2d(0, 0);
}
break;
case penDownEvent2:
m_maskB = drc.HitTest(ptB, &m_vOffsetB);
if (m_vOffsetB.mag() > kcpCloseEnough) {
m_vOffsetB = Vec2d(0, 0);
}
break;
case penUpEvent:
m_maskA = 0;
break;
case penUpEvent2:
m_maskB = 0;
break;
}
drc.TrackPoints(m_maskA, m_vOffsetA.add(ptA), m_maskB, m_vOffsetB.add(ptB));
m_pselspr->SetDragRect(drc);
SetSelection();
// Redraw the screen so the selection updates
gevm.SetRedrawFlags(kfRedrawDirty | kfRedrawBeforeTimer);
}
void SimUIForm::FingerHandler::CheckScroll() {
// Don't auto-scroll if not drag selecting
if (!m_pselspr->IsVisible()) {
return;
}
// Beta feedback: users don't like auto scrolling.
// Don't auto scroll if both fingers are down. Do auto scroll if
// one finger is extending a rect - this keeps the old behavior.
if (m_maskA != 0 && m_maskB != 0) {
return;
}
// First see if either finger is in the "scroll border" area
Size sizPlayfield;
ggame.GetPlayfieldSize(&sizPlayfield);
int cpBorder = PcFromUwc(kwcScrollBorderSize);
word wfAdjust = 0;
// The portrait mode status bar on the iPhone isn't visible when
// running the game, but it still eats input! Scroll before hitting
// that.
int cpLeftExtra = PcFromUwc(kwcScrollLeftExtra);
// For some reason, the iphone passes a finger up before the finger
// gets close enough to the right edge of the screen to scroll within
// cpBorder. So, subtract extra from the right side.
int cpRightExtra = cpLeftExtra;
if (m_maskA != 0) {
if (m_x1 < cpBorder + cpLeftExtra) {
wfAdjust |= 1;
} else if (m_x1 > sizPlayfield.cx - cpBorder - cpRightExtra) {
wfAdjust |= 4;
}
if (m_y1 < cpBorder) {
wfAdjust |= 2;
} else if (m_y1 > sizPlayfield.cy - cpBorder) {
wfAdjust |= 8;
}
}
if (m_maskB != 0) {
if (m_x2 < cpBorder + cpLeftExtra) {
wfAdjust |= 1;
} else if (m_x2 > sizPlayfield.cx - cpBorder - cpRightExtra) {
wfAdjust |= 4;
}
if (m_y2 < cpBorder) {
wfAdjust |= 2;
} else if (m_y2 > sizPlayfield.cy - cpBorder) {
wfAdjust |= 8;
}
}
if (wfAdjust == 0) {
return;
}
// Set the new view pos
WCoord wxView, wyView;
gsim.GetViewPos(&wxView, &wyView);
WCoord wxViewNew, wyViewNew;
wxViewNew = wxView;
wyViewNew = wyView;
if (wfAdjust & 1) {
wxViewNew -= kwcScrollStepSize;
}
if (wfAdjust & 4) {
wxViewNew += kwcScrollStepSize;
}
if (wfAdjust & 2) {
wyViewNew -= kwcScrollStepSize;
}
if (wfAdjust & 8) {
wyViewNew += kwcScrollStepSize;
}
gsim.SetViewPos(wxViewNew, wyViewNew);
// If tracking two fingers, the rect stays where it is. It
// only needs to update the selection.
if (m_maskA != 0 && m_maskB != 0) {
SetSelection();
gevm.SetRedrawFlags(kfRedrawDirty | kfRedrawBeforeTimer);
return;
}
// Otherwise tracking one finger; expand the size of the rect.
// Note the coordinates passed to scroll are rotated since that
// is how the drag rect is being maintained.
WCoord wxViewActual, wyViewActual;
gsim.GetViewPos(&wxViewActual, &wyViewActual);
int dx = PcFromWc(wxView - wxViewActual);
int dy = PcFromWc(wyView - wyViewActual);
DragRect drc = m_pselspr->GetDragRect();
drc.ScrollExpand(m_maskA, m_maskB, dy, dx);
m_pselspr->SetDragRect(drc);
SetSelection();
gevm.SetRedrawFlags(kfRedrawDirty | kfRedrawBeforeTimer);
}
bool SimUIForm::FingerHandler::IsQuickUp(Event *pevt) {
if (pevt->eType != penUpEvent) {
return false;
}
return (base::GetTickCount() - m_tDown) <= QUICK_UP_TICKS;
}
bool SimUIForm::FingerHandler::CheckDragged(Event *pevt, int wcDrag) {
int pcDrag = PcFromWc(wcDrag);
if (abs(pevt->x - m_xDownLast) > pcDrag ||
abs(pevt->y - m_yDownLast) > pcDrag) {
return true;
}
return false;
}
void SimUIForm::FingerHandler::EnterDrag(Event *pevt) {
EnterNone();
m_xDrag = pevt->x;
m_yDrag = pevt->y;
gsim.GetViewPos(&m_wxViewDrag, &m_wyViewDrag);
m_state = FHS_DRAG;
}
void SimUIForm::FingerHandler::UpdateDrag(Event *pevt) {
Vec2d v(m_xDrag - pevt->x, m_yDrag - pevt->y);
v = v.scale(gnScrollSpeed);
gsim.SetViewPos(m_wxViewDrag + WcFromPc(v.dx),
m_wyViewDrag + WcFromPc(v.dy));
}
void SimUIForm::FingerHandler::OnPaint(DibBitmap *pbm) {
}
void SimUIForm::FingerHandler::ShowUnitTitle(Gob *pgob) {
// Must be a unit gob
if (pgob == NULL || (pgob->GetFlags() & kfGobUnit) == 0) {
return;
}
// Don't show this twice
if (m_pfrmUnitTitle != NULL) {
return;
}
// Create the menu
m_pfrmUnitTitle = new UnitMenu();
if (m_pfrmUnitTitle == NULL) {
return;
}
if (!m_pfrmUnitTitle->Init(gpmfrmm, gpiniForms, kidfUnitMenu)) {
return;
}
// Initialize and show, non-modal
m_pfrmUnitTitle->SetOwner((UnitGob *)pgob, false);
m_pfrmUnitTitle->Show(true);
gsndm.PlaySfx(ksfxGuiFormShow);
}
void SimUIForm::FingerHandler::SetSelection() {
if (m_pselspr == NULL) {
return;
}
WCoord wxView, wyView;
gsim.GetViewPos(&wxView, &wyView);
const DragRect& drc = m_pselspr->GetDragRect();
Rect rcBounding;
drc.GetBoundingRect(&rcBounding);
// Rotate the rect since drc is rotated
int t = rcBounding.left;
rcBounding.left = rcBounding.top;
rcBounding.top = t;
t = rcBounding.right;
rcBounding.right = rcBounding.bottom;
rcBounding.bottom = t;
for (Gob *pgobT = ggobm.GetFirstGob(); pgobT != NULL;
pgobT = ggobm.GetNextGob(pgobT)) {
// Only unit gobs that are active
dword ff = pgobT->GetFlags();
if ((ff & (kfGobUnit | kfGobActive)) != (kfGobUnit | kfGobActive)) {
continue;
}
UnitGob *punt = (UnitGob *)pgobT;
// Player must own this unit, or the game must be in god mode
bool fSelect = true;
if (punt->GetOwner() != gpplrLocal && !gfGodMode) {
fSelect = false;
}
// No structures unless it's a tower
if ((ff & kfGobStructure) && (punt->GetConsts()->um & kumTowers) == 0) {
fSelect = false;
}
#if 0
// No miners, by popular request
if (punt->GetType() == kgtGalaxMiner) {
fSelect = false;
}
#endif
// If center not in bounding rect, discard
WPoint wptCenter;
punt->GetCenter(&wptCenter);
Point ptCenter;
ptCenter.x = PcFromWc(wptCenter.wx - wxView);
ptCenter.y = PcFromWc(wptCenter.wy - wyView);
if (!rcBounding.PtIn(ptCenter.x, ptCenter.y)) {
fSelect = false;
}
// Center must be in drag rect
if (fSelect) {
DPoint pt;
pt.y = ptCenter.x;
pt.x = ptCenter.y;
if (!drc.PtIn(pt)) {
fSelect = false;
}
}
punt->Select(fSelect);
}
}
} // namespace wi