If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle
See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// gui_panel.h
#include "library.h"
#include "hal.h"
#include "gui_panel.h"
/*
GPanel is the base class for all visible GUI elements
m_pFocus is the currently focussed child. Key events are always relayed
to the focussed child. Oblivious children cannot hold the focus. Modal
panels are always focussed.
*/
/*
==============================================================================
GPanel IMPLEMENTATION
==============================================================================
*/
GPanel *GPanel::s_pModal = 0;
GPanel *GPanel::s_pGrabMouse = 0;
std::list<GPanel*> GPanel::s_TopLevel;
/*
==============
GPanel::GPanel
Initialize member variables, link into parent's list
==============
*/
GPanel::GPanel(GPanel *pParent, int x, int y, int w, int h, bool bTopLevel)
: LObject(), m_rc(x, y, w, h)
{
m_pFocus = 0;
m_bFocussed = 0;
m_pMouseIn = 0;
m_bHasMouse = false;
m_bGrabbedMouse = false;
m_bModal = false;
m_bDormant = false;
m_bOblivious = false;
m_bCanFocus = false;
m_bVisible = true;
m_bTopLevel = false;
m_bActive = false;
m_color[0] = m_color[1] = m_color[2] = m_color[3] = 255;
m_pParent = pParent;
if (pParent) {
pParent->m_Children.push_front(this);
pParent->intAddChild(this);
} else
bTopLevel = true;
if (bTopLevel)
SetTopLevel(true);
}
/*
==============
GPanel::~GPanel
Unlink the panel from parent and children
==============
*/
GPanel::~GPanel()
{
lassert(m_bModal == false);
while(!m_Children.empty())
delete *m_Children.begin();
if (m_pParent) {
m_pParent->intRemoveChild(this);
m_pParent->m_Children.remove(this);
}
if (m_bTopLevel)
s_TopLevel.remove(this);
}
/*
==============
GPanel::intAddChild
Called by the child's constructor, as a mere notification.
Linking is done by the child itself.
==============
*/
void GPanel::intAddChild(GPanel *pChild)
{
}
/*
==============
GPanel::intRemoveChild
This is only a notification. Unlinking is done by the child
itself.
==============
*/
void GPanel::intRemoveChild(GPanel *pChild)
{
if (pChild == m_pFocus)
SetFocus(0);
}
/*
==============
GPanel::IsVisible
Parents' invisibility overrides children's visibility.
==============
*/
bool GPanel::IsVisible()
{
if (!m_bVisible)
return false;
if (m_bTopLevel || !m_pParent)
return true;
return m_pParent->IsVisible();
}
/*
===============
GPanel::SetTopLevel
(Un-)makes the panel a top-level panel
===============
*/
void GPanel::SetTopLevel(bool toplevel)
{
if (!m_pParent && !toplevel)
return;
if (toplevel == m_bTopLevel)
return;
m_bTopLevel = toplevel;
if (m_bTopLevel) {
s_TopLevel.push_front(this);
} else {
s_TopLevel.remove(this);
}
}
/*
==============
GPanel::SetOblivious
Oblivious panels can't receive the focus
==============
*/
void GPanel::SetOblivious(bool bOblivious)
{
if (bOblivious) {
lassert(!m_bModal);
if (m_pParent && m_pParent->m_pFocus == this)
m_pParent->SetFocus(0);
}
m_bOblivious = bOblivious;
}
/*
==============
GPanel::SetCanFocus
Sets the panel's can-focus state
==============
*/
void GPanel::SetCanFocus(bool bCanFocus)
{
// Drop the focus if necessary
if (!bCanFocus) {
lassert(!m_bModal);
if (m_pParent && m_pParent->m_pFocus == this)
m_pParent->SetFocus(0);
}
m_bCanFocus = bCanFocus;
}
/*
==============
GPanel::SetFocus
Set the focus to the given panel. It must be a direct child of us.
The panel must not be oblivious.
If self isn't focussed it tries to gain focus.
==============
*/
void GPanel::SetFocus(GPanel *pPanel)
{
lassert(!pPanel || pPanel->m_pParent == this);
lassert(!pPanel || pPanel->CanFocus());
// if it's currently focussed, nothing happens unless we have
// to grab the focus from our parent
if (m_pFocus == pPanel) {
if (pPanel && !m_bFocussed) {
lassert(m_pParent);
m_pParent->SetFocus(this);
}
return;
}
// notify the current focus that it looses
if (m_pFocus) {
if (m_bFocussed)
m_pFocus->intGainFocus(false);
m_pFocus = 0;
}
// notify the new focus that it wins
if (pPanel) {
m_pFocus = pPanel;
if (m_bFocussed)
m_pFocus->intGainFocus(true);
else {
lassert(m_pParent);
m_pParent->SetFocus(this); // calls child's gainfocus indirectly
}
}
}
/*
==============
GPanel::FocusNext
Select the next focussable child.
Returns true if a focussable child has been found.
==============
*/
bool GPanel::FocusNext()
{
GPanel_lit it, start;
if (m_Children.empty())
return false;
if (m_pFocus)
start = std::find(m_Children.begin(), m_Children.end(), m_pFocus);
else
start = m_Children.end();
it = start;
do {
if (it == m_Children.begin())
it = m_Children.end();
else
it--;
if (it != m_Children.end() && (*it)->CanFocus()) {
SetFocus(*it);
return true;
}
} while(it != start);
return false;
}
/*
==============
GPanel::FocusLast
Select the previous (actually m_logical.next) focussable child
Returns true if a focussable child has been found.
==============
*/
bool GPanel::FocusLast()
{
GPanel_lit it, start;
if (m_Children.empty())
return false;
if (m_pFocus)
start = std::find(m_Children.begin(), m_Children.end(), m_pFocus);
else
start = m_Children.begin();
it = start;
do {
if (it == m_Children.end())
it = m_Children.begin();
else
it++;
if (it != m_Children.end() && (*it)->CanFocus()) {
SetFocus(*it);
return true;
}
} while(it != start);
return false;
}
/*
==============
GPanel::PanelToScreen
GPanel::ScreenToPanel
Convert coordinates relative to the panel to coordinates relative
to the screen and vice versa
==============
*/
void GPanel::PanelToScreen(int *x, int *y)
{
m_rc.RectToWorld(x, y);
if (!m_bTopLevel)
m_pParent->PanelToScreen(x, y);
}
void GPanel::ScreenToPanel(int *x, int *y)
{
if (!m_bTopLevel)
m_pParent->ScreenToPanel(x, y);
m_rc.WorldToRect(x, y);
}
/*
==============
GPanel::GrabMouse
Toggles mouse grabbing on or off.
If a panel grabs the mouse, it'll be the only one to receive mouse
events until the mouse in un-grabbed.
Only one panel can grab the mouse at a time. This means that the mouse
should only be grabbed as a reaction to a mouse event.
==============
*/
void GPanel::GrabMouse(bool bGrab)
{
if (!bGrab) {
s_pGrabMouse = 0;
m_bGrabbedMouse = false;
} else {
s_pGrabMouse = this;
m_bGrabbedMouse = true;
}
}
/*
==============
GPanel::Run
Executes the panel modally
==============
*/
int GPanel::Run()
{
GPanel *pOldModal;
bool bOldFocussed;
bool bOldVisible;
lassert(!m_bModal);
lassert(CanFocus());
pOldModal = s_pModal;
s_pModal = this;
m_bModal = true;
m_iEndModal = -1;
// Modal panels are always focussed and visible
bOldFocussed = m_bFocussed;
if (!bOldFocussed)
intGainFocus(true);
bOldVisible = IsVisible();
if (!bOldVisible)
SetVisible(true);
try
{
static const hal_inputcb_t panel_inputcb = {
GPanel::cbKey,
GPanel::cbMouseButton,
GPanel::cbMouseMove,
GPanel::cbQuitRequest
};
Begin();
// run the gameloop
while(m_bModal) {
hal->StartFrame();
hal->Ortho(0, 0, 640, 480);
hal->Input(&panel_inputcb);
if (!m_bDormant)
intLogic();
for(GPanel_rlit tlp = s_TopLevel.rbegin(); tlp != s_TopLevel.rend(); tlp++)
{
// for(int i = 0; i < 10; i++)
if ((*tlp)->IsVisible())
(*tlp)->intDraw();
}
intDrawMouse();
hal->EndFrame();
}
}
catch(...)
{
m_bModal = false;
s_pModal = pOldModal;
if (!bOldFocussed)
intGainFocus(false);
if (!bOldVisible)
SetVisible(false);
End();
throw;
}
// cleanup: reset the modal pointer and mouse state
m_bModal = false;
s_pModal = pOldModal;
if (!bOldFocussed)
intGainFocus(false);
if (!bOldVisible)
SetVisible(false);
End();
return m_iEndModal;
}
/*
==============
GPanel::intDrawMouse
Draw the mouse if necessary. Called from Run()
==============
*/
void GPanel::intDrawMouse()
{
GPanel *panel;
const char *szMouse;
sprite_t *spr;
int x, y;
// Get the mouse cursor name
panel = this;
while(panel->m_pMouseIn)
panel = panel->m_pMouseIn;
szMouse = panel->MouseCursor();
if (!szMouse)
return;
hal->SetDrawRect(0, 0, 640, 480, 0, 0);
spr = SPR_Get(szMouse);
hal->GetMouseState(&x, &y);
SPR_Draw(spr, x, y, -1, hal->framestart);
SPR_Free(spr);
}
/*
==============
GPanel::EndModal
Ends the mainloop
==============
*/
void GPanel::EndModal(int iCode)
{
lassert(m_bModal);
m_bModal = false;
m_iEndModal = iCode;
}
/*
==============
GPanel::intLogic
Call our own logic, then children's logic function if desired
==============
*/
void GPanel::intLogic()
{
lassert(!m_bDormant);
Logic();
for(GPanel_lit child = m_Children.begin(); child != m_Children.end(); child++)
if (!(*child)->m_bDormant)
(*child)->intLogic();
}
/*
==============
GPanel::MouseCursor
The mouse cursor to be used when this panel becomes modal
==============
*/
const char *GPanel::MouseCursor()
{
return "mouse";
}
/*
==============
GPanel::Begin
GPanel::End
Called just before and just after the modal loop in Run
==============
*/
void GPanel::Begin()
{
}
void GPanel::End()
{
}
/*
==============
GPanel::Logic
Called every frame to update game logic etc..
Only called if m_bThink is set and m_bDormant is unset.
==============
*/
void GPanel::Logic()
{
}
/*
==============
GPanel::intDraw
Draw self, then all children in Z-order
==============
*/
void GPanel::intDraw()
{
GPanel *pPanel;
LRect rc(m_rc.pos[0], m_rc.pos[1], m_rc.size[0], m_rc.size[1]);
int x, y;
lassert(m_bVisible);
x = y = 0;
pPanel = this;
while(!pPanel->m_bTopLevel) {
pPanel = pPanel->m_pParent;
pPanel->m_rc.ClipLocalOffset(&rc, &x, &y);
pPanel->m_rc.RectToWorld(&rc.pos[0], &rc.pos[1]);
}
if (rc.size[0] <= 0 || rc.size[1] <= 0)
return;
hal->SetDrawRect(rc.pos[0], rc.pos[1], rc.size[0], rc.size[1], x, y);
Draw();
for(GPanel_rlit pnl = m_Children.rbegin(); pnl != m_Children.rend(); pnl++) {
if ((*pnl)->m_bTopLevel)
continue;
if ((*pnl)->m_bVisible)
(*pnl)->intDraw();
}
}
/*
==============
GPanel::Draw
Redraw the panel. Only called if m_bVisible is true.
==============
*/
void GPanel::Draw()
{
}
/*
==============
GPanel::OkayToQuit
Called when a quit event is received from the OS.
Return true if you want to quit.
==============
*/
bool GPanel::OkayToQuit()
{
return true;
}
/*
==============
GPanel::Key
GPanel::MouseEnter
GPanel::MouseButton
GPanel::MouseMove
Event handling functions.
Return true if the event was used, or false if ignored.
Mouse coordinates are relative to the panel.
The default key handler supports arrow keys and [shift-]tab
for focus cycling.
==============
*/
void GPanel::GainFocus(bool bGain)
{
}
bool GPanel::Key(int code, char c, bool down)
{
if (!down)
return false;
switch(code) {
case KEY_LEFT:
case KEY_UP: return FocusLast();
case KEY_RIGHT:
case KEY_DOWN: return FocusNext();
case KEY_TAB:
{
byte *keystate = hal->GetKeyState();
if (keystate[KEY_LSHIFT] || keystate[KEY_RSHIFT])
return FocusLast();
else
return FocusNext();
}
}
return false;
}
void GPanel::MouseEnter(bool bEnter)
{
}
bool GPanel::MouseButton(int btn, int x, int y, bool down)
{
if (btn == 0) {
if (!IsFocussed() && CanFocus()) {
lassert(m_pParent);
if (down)
m_pParent->SetFocus(this);
return true;
}
}
return false;
}
void GPanel::MouseMove(int x, int y)
{
}
/*
==============
GPanel::intOkayToQuit
Query our own and all children's okay-to-quit status. Return true
if everyone's okay with it.
==============
*/
bool GPanel::intOkayToQuit()
{
for(GPanel_lit child = m_Children.begin(); child != m_Children.end(); child++) {
if ((*child)->m_bOblivious)
continue;
if (!(*child)->intOkayToQuit())
return false;
}
if (!OkayToQuit())
return false;
return true;
}
/*
==============
GPanel::intGainFocus
Set the focussed state and propagate the event to the focussed child
==============
*/
void GPanel::intGainFocus(bool bGain)
{
lassert(m_bFocussed != bGain);
if (m_pFocus)
m_pFocus->intGainFocus(bGain);
m_bFocussed = bGain;
GainFocus(bGain);
}
/*
==============
GPanel::intKey
Simply dispatch the key to the currently focussed child.
If that doesn't handle the key, call our own handler.
==============
*/
bool GPanel::intKey(int code, char c, bool down)
{
if (m_pFocus) {
if (m_pFocus->intKey(code, c, down))
return true;
}
return Key(code, c, down);
}
/*
==============
GPanel::intTrackMouse
Tracks the mouse coordinates down to child panels and sends
MouseEnter/MouseLeave events accordingly.
Returns the child panel which is currently under the mouse (if any).
==============
*/
GPanel *GPanel::intTrackMouse(int x, int y)
{
GPanel *pChild = 0;
//L_Printf("%p, trackmouse: %i, %i\n", this, x, y);
for(GPanel_lit child = m_Children.begin(); child != m_Children.end(); child++) {
if ((*child)->m_bTopLevel)
continue;
if ((*child)->m_bOblivious || !(*child)->m_bVisible)
continue;
if (!(*child)->m_rc.Contains(x, y))
continue;
pChild = *child;
break;
}
if (m_pMouseIn != pChild) {
if (m_pMouseIn)
m_pMouseIn->intMouseEnter(false);
if (pChild)
pChild->intMouseEnter(true);
m_pMouseIn = pChild;
}
return pChild;
}
/*
==============
GPanel::intMouseEnter
Relays mouse entering info down to m_pMouseIn, if any
==============
*/
void GPanel::intMouseEnter(bool bEnter)
{
if (!bEnter && m_pMouseIn) {
m_pMouseIn->intMouseEnter(false);
m_pMouseIn = 0;
}
m_bHasMouse = bEnter;
MouseEnter(bEnter);
}
/*
==============
GPanel::intMouseButton
GPanel::intMouseMove
Create MouseEnter events as the mouse moves.
Mouse clicks are relayed to the topmost child at the x/y location.
If it ignores the event, we call our own respective handler function.
Mouse moves are propagated to all children under the respective x/y
coordinates.
==============
*/
bool GPanel::intMouseButton(int btn, int x, int y, bool down)
{
GPanel *pChild;
int rx, ry;
pChild = intTrackMouse(x, y);
if (pChild) {
rx = x;
ry = y;
pChild->m_rc.WorldToRect(&rx, &ry);
if (pChild->intMouseButton(btn, rx, ry, down))
return true;
}
return MouseButton(btn, x, y, down);
}
void GPanel::intMouseMove(int x, int y)
{
int rx, ry;
intTrackMouse(x, y);
for(GPanel_lit child = m_Children.begin(); child != m_Children.end(); child++) {
if ((*child)->m_bTopLevel)
continue;
if ((*child)->m_bOblivious || !(*child)->m_bVisible)
continue;
if (!(*child)->m_rc.Contains(x, y))
continue;
rx = x;
ry = y;
(*child)->m_rc.WorldToRect(&rx, &ry);
(*child)->intMouseMove(rx, ry);
}
MouseMove(x, y);
}
/*
==============
GPanel::cbKey
GPanel::cbMouseButton
GPanel::cbMouseMove
These callback functions are simple glue functions which call the
currently modal object's dispatch function
==============
*/
void GPanel::cbKey(int code, char c, bool down)
{
lassert(s_pModal);
s_pModal->intKey(code, c, down);
}
GPanel *GPanel::cbTrackMouse(int scrx, int scry)
{
GPanel *pPanel;
bool bHasMouse;
if (!s_pGrabMouse)
pPanel = s_pModal;
else
pPanel = s_pGrabMouse;
pPanel->ScreenToPanel(&scrx, &scry);
bHasMouse = (scrx >= 0 && scrx < pPanel->m_rc.size[0] &&
scry >= 0 && scry < pPanel->m_rc.size[1]);
if (bHasMouse != pPanel->m_bHasMouse)
pPanel->intMouseEnter(bHasMouse);
if (!s_pGrabMouse && !bHasMouse)
return 0;
return pPanel;
}
void GPanel::cbMouseButton(int btn, int scrx, int scry, bool down)
{
GPanel *pPanel;
pPanel = cbTrackMouse(scrx, scry);
if (pPanel) {
pPanel->ScreenToPanel(&scrx, &scry);
pPanel->intMouseButton(btn, scrx, scry, down);
}
}
void GPanel::cbMouseMove(int x, int y)
{
GPanel *pPanel;
pPanel = cbTrackMouse(x, y);
if (pPanel) {
pPanel->ScreenToPanel(&x, &y);
pPanel->intMouseMove(x, y);
}
}
void GPanel::cbQuitRequest()
{
lassert(s_pModal);
if (s_pModal->intOkayToQuit())
s_pModal->EndModal(-1);
}