Základem zpracování událostí jsou v SDL union SDL_Event a funkce, které načítají tyto objekty z fronty zpráv. Nejdříve bude v rychlosti probrána zmíněná datová struktura a pak se budeme celý zbytek článku věnovat práci s frontou událostí.
Union SDL_Event
Pro ty, kteří už zapomněli… Datový typ union je podobný klasické struktuře, rozdíl mezi nimi spočívá v tom, že v určitém okamžiku může v jeho vnitřku existovat vždy jen jedna z deklarovaných položek. Při vytváření se alokuje paměť o velikosti největší z nich.
Union SDL_Event je pravděpodobně, hned po SDL_Surface, druhým nejdůležitějším a nejpoužívanějším ze všech SDL datových typů. Jak už bylo několikrát zmíněno, poskytuje programátorovi rozhraní pro práci s událostmi.
typedef union
{
Uint8 type; // Typ události
SDL_ActiveEvent active; // (De)aktivace okna
SDL_KeyboardEvent key; // Klávesnice
SDL_MouseMotionEvent motion; // Myš
SDL_MouseButtonEvent button;
SDL_JoyAxisEvent jaxis; // Joystick
SDL_JoyBallEvent jball;
SDL_JoyHatEvent jhat;
SDL_JoyButtonEvent jbutton;
SDL_ResizeEvent resize; // Změna velikosti okna
SDL_ExposeEvent expose; // Požadavek na překreslení
SDL_QuitEvent quit; // Požadavek na ukončení
SDL_UserEvent user; // Uživatelská událost
SDL_SywWMEvent syswm; // Systémově závislá
} SDL_Event;
Jak to všechno funguje? Když uživatel například změní velikost okna, SDL vygeneruje objekt SDL_Event, atribut type nastaví na hodnotu SDL_VIDEORESIZE a do jeho parametrů (podobjekt resize) uloží nové rozměry okna. Celý objekt je pak vložen do fronty událostí.
Detekuje-li aplikace příchod zprávy, podle parametru type zjistí, že se jedná o změnu velikosti okna, a v resize.w, resize.h najde nové rozměry. V závislosti na nich pak provede odpovídající akci – například překreslí scénu nebo aktualizuje OpenGL perspektivu.
Proměnná type může nabývat hodnot uvedených v následující tabulce v levém sloupci. Vpravo se pak nachází odpovídající struktura, ve které se hledají podrobnosti o události.
Typ události | Odpovídající struktura |
---|---|
SDL_ACTIVEEVENT | SDL_ActiveEvent |
SDL_KEYDOWN/UP | SDL_KeyboardEvent |
SDL_MOUSEMOTION | SDL_MouseMotionEvent |
SDL_MOUSEBUTTONDOWN/UP | SDL_MouseButtonEvent |
SDL_JOYAXISMOTION | SDL_JoyAxisEvent |
SDL_JOYBALLMOTION | SDL_JoyBallEvent |
SDL_JOYHATMOTION | SDL_JoyHatEvent |
SDL_JOYBUTTONDOWN/UP | SDL_JoyButtonEvent |
SDL_QUIT | SDL_QuitEvent |
SDL_VIDEORESIZE | SDL_ResizeEvent |
SDL_VIDEOEXPOSE | SDL_ExposeEvent |
SDL_USEREVENT | SDL_UserEvent |
SDL_SYSWMEVENT | SDL_SysWMEvent |
Protože popis těchto struktur a všeho, co s nimi souvisí, zabere několik následujících článků, budeme se nejprve věnovat funkcím, které vyzvedávají události z fronty, a pak až konkrétnímu popisu jednotlivých zpráv.
Načítání událostí z fronty
Existují dva základní způsoby, jak načíst událost z fronty, v SDL je reprezentují funkce SDL_PollEvent() a SDL_WaitEvent(). Obě vezmou událost, která je zrovna na řadě, zkopírují její data do předaného parametru a odstraní ji z fronty. Co se stane s událostí dál, záleží na programátorovi, který vytváří aplikaci.
int SDL_PollEvent(SDL_Event *event);
int SDL_WaitEvent(SDL_Event *event);
Rozdíl mezi těmito funkcemi se projeví až tehdy, když je fronta prázdná. Jak už z názvu SDL_WaitEvent() vyplývá, tato funkce čeká libovolně dlouho, dokud nějaká zpráva nedorazí. SDL_PollEvent() se na rodíl od toho v případě prázdné fronty ihned ukončí a nulovým návratovým kódem oznámí, že nebylo načteno nic. V ostatních případech vrátí jedničku, která vyjadřuje, že byla nějaká zpráva načtena a má se zpracovat. SDL_WaitEvent() naproti tomu vrátí nulu, pouze pokud nastane nějaká chyba.
Události se typicky zpracovávají v cyklu, který se ukončí, když je fronta prázdná.
SDL_Event event;
while(SDL_PollEvent(&event))
{
// Zpracování události
}
// Všechny události zpracovány
Může vyvstat otázka, kterou z funkcí je lepší používat. Dá se říci, že v 99 procentech případů sáhne programátor po SDL_PollEvent() a SDL_WaitEvent() použije pouze ve výjimečných případech. Důvodem je, že program potřebuje neustále provádět určitou činnost, jako jsou animace a herní logika. Systémové časovače však nemusí být pro tento typ úloh zrovna nejlepší volbou, protože jsou většinou výrazně pomalejší než cyklus, který se provádí neustále dokola. U jednoduchých her je v podstatě jedno, co se použije, nicméně u trochu složitějších bývají s rychlostí velké problémy.
Události se načítají ze vstupních zařízení funkcí SDL_PumpEvents(), bez ní by nikdy aplikaci nepřišla žádná zpráva. V SDL_PollEvent() a SDL_WaitEvent() je volána automaticky, při jiném způsobu načítání zpráv musí být použita explicitně.
void SDL_PumpEvents(void);
SDL dokumentace uvádí, že SDL_PumpEvents() nesmí být použito v jiném vláknu, než ve kterém bylo voláno SDL_SetVideoMode().
Vkládání událostí do fronty
Událostní systém v SDL není pouze jednosměrný, ale může být použit i k dialogu mezi různými částmi aplikace. Funkce SDL_PushEvent() přebírá ukazatel na objekt události, který umístí do fronty (resp. jeho kopii), v případě úspěchu vrátí 0, jinak –1.
int SDL_PushEvent(SDL_Event *event);
Většinou se posílají uživatelské události (SDL_USEREVENT), ale jelikož ještě nebyly vysvětleny, ukážeme si poslání na SDL_QUIT. Tato událost zprostředkovává programu požadavek na ukončení a nepřebírá žádné parametry.
void PushQuitEvent()
{
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
}
Kdykoli by byla v programu zavolána tato funkce, aplikace by byla ukončena (předpokládá se standardní ukončení aplikace po příchodu SDL_QUIT).
Obecná práce s frontou
Všechny činnosti s frontou zpráv, které byly právě probrány, a také některé další, mohou být provedeny pomocí SDL_PeepEvents(). Tato obecná funkce přebírá v prvních dvou parametrech ukazatel na pole událostí a samozřejmě jeho velikost.
int SDL_PeepEvents(SDL_Event *events, int numevents,
SDL_eventaction action, Uint32 mask);
Parametr action specifikuje, co se má vykonat. Je-li nastaven na SDL_ADDEVENT, jsou události z pole vloženy do fronty, v případě SDL_PEEKEVENT budou vráceny, ale ne vymazány. K vrácení a následnému vymazání z fronty slouží SDL_GETEVENT.
Poslední parametr definuje masku událostí, se kterými se má pracovat. Jedná se o ANDované flagy makra SDL_EVENTMASK(typ_události). Také se mohou použít přímé názvy masek, lze je najít v hlavičkovém souboru SDL_events.h. Maska pro libovolné události nese označení SDL_ALLEVENTS.
Návratová hodnota představuje počet vložených/načtených událostí, v případě chyby je vráceno –1.
Zákaz generování některých událostí
Pomocí funkce SDL_EventState() lze specifikovat, jestli se mají události daného typu vkládat do fronty, nebo ne. První parametr specifikuje typ události a druhý určuje činnost funkce. Je-li předán flag SDL_IGNORE, události se do fronty vkládat nebudou, v případě SDL_ENABLE se zpracovávají normálně a SDL_QUERY slouží k dotazům. Vrácen je vždy stav po modifikaci.
Uint8 SDL_EventState(Uint8 type, int state);
Tato funkce se uplatní především při používání technik, které zjišťují stav vstupních zařízení přímo, a bylo by tedy zbytečné generovat události. Například funkce SDL_GetKeyState() umožňuje programátorovi dotázat se na stisk klávesy. K tomu však až příště.
Pomocí následujících dvou řádků lze zakázat generování všech událostí klávesnice.
SDL_EventState(SDL_KEYUP, SDL_IGNORE);
SDL_EventState(SDL_KEYDOWN, SDL_IGNORE);
Filtr událostí
O něco flexibilnější než předchozí funkce je SDL_SetEventFilter(), pomocí níž lze do SDL předat ukazatel na rutinu, která může filtrovat události nejen podle typu, ale i podle ostatních parametrů.
void SDL_SetEventFilter(SDL_EventFilter filter);
SDL_EventFilter SDL_GetEventFilter(void);
Typ SDL_EventFilter je definován jako ukazatel na funkci, které je v parametru předán ukazatel na událost. Vrátí-li tato funkce číslo jedna, bude událost vložena do fronty, v případě nuly nebude.
typedef int (*SDL_EventFilter)(const SDL_Event *event);
Jediná připomínka vzniká u události SDL_QUIT. Filtr je pro ni volán pouze tehdy, pokud správce oken vyžaduje zavřít okno aplikace. V takovém případě vrácená jednička říká, aby bylo okno zavřeno, cokoli jiného ponechá okno otevřené (je-li to možné). Pokud je vyvoláno SDL_QUIT kvůli signálu o přerušení, filtr volán nebude a zpráva je vždy doručena přímo aplikaci.
A ještě dvě krátké poznámky: Filtr není volán na události, které vkládá do fronty sama aplikace (SDL_PushEvent(), SDL_PeepEvents()), a jelikož může být vykonáván v jiném vláknu, měli byste si dávat pozor, co v něm děláte.
V následujícím příkladu má SDL zakázáno generovat události klávesnice, pokud se nejedná o klávesu abecedního znaku nebo čísla. Je to vlastně analogie příkladu u funkce SDL_EventState(), nicméně zde byl výběr navíc omezen podle parametrů. Všimněte si, že filtr má podobnou strukturu jako samotné zpracovávání událostí.
#include <ctype.h>// Kvůli isalnum()
int EventFilter(const SDL_Event *event)
{
switch(event->type)
{
case SDL_KEYDOWN:
// Abecední znak nebo číslo?
if(isalnum(event->key.keysym.sym))
return 1;// Vložit
else
return 0;// Nevkládat
break;
default:
break;
}
return 1;// Vložit
}
// Zapnutí filtru např. v Init()
SDL_SetEventFilter(EventFilter);
Ukázkové programy
Fronta událostí
Program demonstruje nejčastější techniky používané při práci s frontou událostí. Jsou jimi načítání a vkládání zpráv z/do fronty a filtrování událostí. Pomocí mezerníku lze zapínat a vypínat filtrování zpráv o uvolnění kláves (při příchodu se vypíše oznámení do konzole), zprávy týkající se myši se vždy ignorují. Stejně jako u příkladu z minula, ani zde se nic nevykresluje. (Zdrojový kód se zvýrazněním syntaxe.)
Download
Pokračování
Dnešním dílem jsme postavili spíše teoretické základy pro zpracování událostí. Příští díl už bude konkrétnější, je věnován zpracování vstupů z klávesnice.