Využitím klávesnice ve hrách a dalších multimediálních aplikacích používajících knihovnu Pyglet jsme se zabývali v předchozím článku, takže se dnes logicky přesuneme na popis způsobů využití myši (popř. touchpadu). Knihovna Pyglet programátorům nabízí poměrně široké možnosti, jak lze myš využít, ať již se to týká reakce na rotaci myším kolečkem, tak i možností definice vlastních kurzorů reprezentovaných (barevnými) rastrovými obrázky.

Obsah

1. Multimediální knihovna Pyglet: práce s myší a dalšími polohovacími zařízeními

2. Reakce na stisk tlačítka myš: on_mouse_pressi

3. Stisk versus puštění tlačítka myši: on_mouse_release

4. Sprite ovládaný myší

5. Reakce na pohyb myši: on_mouse_motion

6. Tažení kurzorem myši: on_mouse_drag

7. Využití informací o relativní změně kurzoru myši

8. Otočení kolečkem myši: on_mouse_scroll

9. Změna velikosti spritu pomocí kolečka myši

10. Standardní množina kurzorů

11. Vlastní kurzor myši načtený z rastrového obrázku

12. Vycentrování vlastního kurzoru

13. Kombinace vlastního kurzoru a standardních kurzorů

14. Exkluzivní režim

15. Repositář s demonstračními příklady

16. Odkazy na Internetu

1. Multimediální knihovna Pyglet: práce s myší a dalšími polohovacími zařízeními

Připomeňme si, že v knihovně Pyglet existuje podpora pro několik typů vstupních zařízení. V první řadě se pochopitelně jedná o klávesnici a myš (popř. touchpad), což jsou dnes standardní zařízení dostupná na desktopech i noteboocích. Kromě toho jsou však podporována i další zařízení, zejména joysticky, různé typy pedálů, volanty, dotykové obrazovky atd. Pro přístup k těmto zařízením se používají buď specializované callback funkce nebo je nutné použít modul pyglet.input. Dnes navážeme na předchozí část tohoto seriálu, v níž jsme se zabývali popisem detailů práce s klávesnicí. Ukážeme si totiž, jakými způsoby je možné využít myš, reagovat na stisk tlačítek, pohyb myši, tažení myší, otočení kolečkem atd. Taktéž si ukážeme způsob změny kurzoru myši včetně možnosti použití vlastních kurzorů načtených z externích rastrových obrázků (podobně jako sprity).

Při práci s myší lze použít následující callback funkce:

Callback funkce Reaguje na událost Kapitola
on_mouse_press stisk tlačítka myši 2
on_mouse_release puštění tlačítka myši 3
on_mouse_motion pohyb kurzoru 5
on_mouse_drag tažení (pohyb kurzoru se stisknutým tlačítkem) 6
on_mouse_scroll otočení kolečkem (nebo kolečky!) myši 8

Callback funkce se vždy registrují k určitému oknu, což je ostatně podobný princip, s jakým jsme se již setkali při práci s klávesnicí.

2. Reakce na stisk tlačítka myši: on_mouse_press

Základní událostí při práci s myší je událost, která vznikne ve chvíli, kdy je stlačeno její libovolné tlačítko (nebo kolečko, které je většinou považováno za prostřední tlačítko). V případě, že zpracováváme události pro okno nazvané window, bude callback funkce reagující na stisk tlačítka vypadat následovně:

@window.event
def on_mouse_press(x, y, button, modifiers):
    pass

Při stisku tlačítka se současně zaznamená i pozice kurzoru myši a případné modifikátory (jako u klávesnice). Pokud současně stisknete více tlačítek, zavolá se callback funkce několikrát, tj. v parametru button bude vždy jen jediná konstanta, nikoli kombinace. Knihovna Pyglet rozlišuje minimálně tři tlačítka – levé, pravé a prostřední (což je většinou kolečko). Pokud vlastníte myš s více tlačítky (herní myši), musíte si otestovat, jak se budou tato tlačítka chovat v praxi. Pokud potřebujeme rozlišit stisk jednoho ze tří základních tlačítek, můžeme postupovat takto:

button_names = {pyglet.window.mouse.LEFT: "left",
                pyglet.window.mouse.RIGHT: "right",
                pyglet.window.mouse.MIDDLE: "middle"}


@window.event
def on_mouse_press(x, y, button, modifiers):
    button_name = button_names.get(button, "unknown")
    text = format("Mouse press: %s button at [%d, %d]" % (button_name, x, y))
    label.text = text
    print(text)
    on_draw()

Povšimněte si, že stisknuté tlačítko je reprezentováno jednou z konstant:

pyglet.window.mouse.LEFT
pyglet.window.mouse.RIGHT
pyglet.window.mouse.MIDDLE

Obrázek 1: Reakce na stisk levého tlačítka myši.

V následujícím demonstračním příkladu se při stisku tlačítka myši zobrazí jeho jméno na obrazovce aplikace a současně se toto jméno vypíše na konzoli. Navíc se vytiskne i aktuální souřadnice kurzoru myši:

#!/usr/bin/env python

import pyglet


GRAY = (128, 128, 128, 255)
RED = (255, 128, 128, 255)


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def create_gray_label(text, x, y, anchor_x, anchor_y):
    return pyglet.text.Label(text,
                             font_size=18,
                             x=x,
                             y=y,
                             anchor_x=anchor_x,
                             anchor_y=anchor_y,
                             color=GRAY)


window = create_window(640, 480)
label = create_gray_label('Mouse press:', 10, 10, 'left', 'bottom')


@window.event
def on_draw():
    window.clear()
    label.draw()


button_names = {pyglet.window.mouse.LEFT: "left",
                pyglet.window.mouse.RIGHT: "right",
                pyglet.window.mouse.MIDDLE: "middle"}


@window.event
def on_mouse_press(x, y, button, modifiers):
    button_name = button_names.get(button, "unknown")
    text = format("Mouse press: %s button at [%d, %d]" % (button_name, x, y))
    label.text = text
    print(text)
    on_draw()


pyglet.app.run()

Obrázek 2: Reakce na stisk pravého tlačítka myši.

3. Stisk versus puštění tlačítka myši: on_mouse_release

Opakem callback funkce zavolané při stisku tlačítka myši je pochopitelně callback funkce reagující na puštění tohoto tlačítka. Obě funkce mají naprosto stejné parametry. Jen pro porovnání:

@window.event
def on_mouse_press(x, y, button, modifiers):
    pass


@window.event
def on_mouse_release(x, y, button, modifiers):
    pass

Obrázek 3: Reakce na stisk prostředního tlačítka myši.

Předchozí demonstrační příklad si tedy můžeme nepatrně upravit takovým způsobem, aby vypisoval informace jak o stisku tlačítka myši, tak i o jeho puštění. Vše zajistí jedna univerzální funkce volaná z obou předchozích callback funkcí:

def on_mouse_action(x, y, button, action):
    button_name = button_names.get(button, "unknown")
    text = format("Mouse %s %s button at [%d, %d]" %
                  (action, button_name, x, y))
    label.text = text
    print(text)
    on_draw()


@window.event
def on_mouse_press(x, y, button, modifiers):
    on_mouse_action(x, y, button, "pressed")


@window.event
def on_mouse_release(x, y, button, modifiers):
    on_mouse_action(x, y, button, "released")

Obrázek 4: Reakce na puštění prostředního tlačítka myši.

Úplný zdrojový text tohoto příkladu:

#!/usr/bin/env python

import pyglet


GRAY = (128, 128, 128, 255)
RED = (255, 128, 128, 255)


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def create_gray_label(text, x, y, anchor_x, anchor_y):
    return pyglet.text.Label(text,
                             font_size=18,
                             x=x,
                             y=y,
                             anchor_x=anchor_x,
                             anchor_y=anchor_y,
                             color=GRAY)


window = create_window(640, 480)
label = create_gray_label('Mouse:', 10, 10, 'left', 'bottom')


@window.event
def on_draw():
    window.clear()
    label.draw()


button_names = {pyglet.window.mouse.LEFT: "left",
                pyglet.window.mouse.RIGHT: "right",
                pyglet.window.mouse.MIDDLE: "middle"}


def on_mouse_action(x, y, button, action):
    button_name = button_names.get(button, "unknown")
    text = format("Mouse %s %s button at [%d, %d]" %
                  (action, button_name, x, y))
    label.text = text
    print(text)
    on_draw()


@window.event
def on_mouse_press(x, y, button, modifiers):
    on_mouse_action(x, y, button, "pressed")


@window.event
def on_mouse_release(x, y, button, modifiers):
    on_mouse_action(x, y, button, "released")


pyglet.app.run()

4. Sprite ovládaný myší

V dalším demonstračním příkladu budeme s využitím myši po obrazovce přesunovat rastrový obrázek neboli sprite. Prozatím využijeme reakci na stisk tlačítka myši (tj. pokud nebude tlačítko zmáčknuto, bude sprite nehybný). V příslušné callback funkci se změní pozice spritu ve scéně:

@window.event
def on_mouse_press(x, y, button, modifiers):
    sprite.x = x
    sprite.y = y
    on_mouse_action(x, y, button, "pressed")

Navíc musíme zajistit vycentrování spritu na aktivní bod kurzoru. To se ve skutečnosti provede jednoduše přesunem „kotvy“ svázané se spritem:

# stred spritu bude odpovidat stredu obrazku - sprite se nam bude
# mnohem lepe pozicovat
image.anchor_x = image.width / 2
image.anchor_y = image.height / 2

sprite = pyglet.sprite.Sprite(image)
# vycentrovani spritu
sprite.x = window.width / 2 - image.width / 2
sprite.y = window.height / 2 - image.height / 2

Obrázek 5: Přesun spritu pomocí myši.

Úplný zdrojový text tohoto příkladu vypadá následovně:

sprite = make_sprite("gnome-globe.png", window)


@window.event
def on_draw():
    window.clear()
    label.draw()
    sprite.draw()
#!/usr/bin/env python

import pyglet


GRAY = (128, 128, 128, 255)
RED = (255, 128, 128, 255)


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def make_sprite(filename, window):
    image_stream = open("gnome-globe.png", "rb")
    image = pyglet.image.load('gnome-globe.png', file=image_stream)

    # stred spritu bude odpovidat stredu obrazku - sprite se nam bude
    # mnohem lepe pozicovat
    image.anchor_x = image.width / 2
    image.anchor_y = image.height / 2

    sprite = pyglet.sprite.Sprite(image)
    # vycentrovani spritu
    sprite.x = window.width / 2 - image.width / 2
    sprite.y = window.height / 2 - image.height / 2
    sprite.step = 5
    return sprite


def create_gray_label(text, x, y, anchor_x, anchor_y):
    return pyglet.text.Label(text,
                             font_size=18,
                             x=x,
                             y=y,
                             anchor_x=anchor_x,
                             anchor_y=anchor_y,
                             color=GRAY)


window = create_window(640, 480)
label = create_gray_label('Mouse:', 10, 10, 'left', 'bottom')
sprite = make_sprite("gnome-globe.png", window)


@window.event
def on_draw():
    window.clear()
    label.draw()
    sprite.draw()


button_names = {pyglet.window.mouse.LEFT: "left",
                pyglet.window.mouse.RIGHT: "right",
                pyglet.window.mouse.MIDDLE: "middle"}


def on_mouse_action(x, y, button, action):
    button_name = button_names.get(button, "unknown")
    text = format("Mouse %s %s button at [%d, %d]" %
                  (action, button_name, x, y))
    label.text = text
    print(text)
    on_draw()


@window.event
def on_mouse_press(x, y, button, modifiers):
    sprite.x = x
    sprite.y = y
    on_mouse_action(x, y, button, "pressed")


@window.event
def on_mouse_release(x, y, button, modifiers):
    on_mouse_action(x, y, button, "released")


pyglet.app.run()

5. Reakce na pohyb myši: on_mouse_motion

Další callback funkcí, kterou můžeme v aplikacích využít, je funkce on_mouse_motion. Jak již název této funkce naznačuje, bude volána ve chvíli, kdy se pohybuje kurzor myši. Funkci se přitom předává jak absolutní pozice kurzoru v okně, tak i pozice relativní, tj. jak daleko se kurzor posunul od posledního volání této funkce. Relativní souřadnice jsou přitom v mnoha aplikacích lépe použitelné. Pokud tedy budeme chtít, aby sprite sledoval kurzor myši (aniž by bylo stlačeno nějaké tlačítko myši), může pomocný kód vypadat velmi jednoduše:

@window.event
def on_mouse_motion(x, y, dx, dy):
    sprite.x = x
    sprite.y = y

Opět si ukažme úplný zdrojový text příkladu, v němž je callback funkce on_mouse_motion použita:

#!/usr/bin/env python

import pyglet


GRAY = (128, 128, 128, 255)
RED = (255, 128, 128, 255)


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def make_sprite(filename, window):
    image_stream = open("gnome-globe.png", "rb")
    image = pyglet.image.load('gnome-globe.png', file=image_stream)

    # stred spritu bude odpovidat stredu obrazku - sprite se nam bude
    # mnohem lepe pozicovat
    image.anchor_x = image.width / 2
    image.anchor_y = image.height / 2

    sprite = pyglet.sprite.Sprite(image)
    # vycentrovani spritu
    sprite.x = window.width / 2 - image.width / 2
    sprite.y = window.height / 2 - image.height / 2
    sprite.step = 5
    return sprite


window = create_window(640, 480)
sprite = make_sprite("gnome-globe.png", window)


@window.event
def on_draw():
    window.clear()
    sprite.draw()


@window.event
def on_mouse_motion(x, y, dx, dy):
    sprite.x = x
    sprite.y = y


pyglet.app.run()

6. Tažení kurzorem myši: on_mouse_drag

Předchozí callback funkci pojmenované on_mouse_motion se podobá další užitečná funkce nazvaná on_mouse_drag. Tato funkce je zavolána ve chvíli, kdy uživatel pohybuje kurzorem myši a současně je stisknuto některé její tlačítko (popř. i další modifikátory). Tomuto chování odpovídají i parametry callback funkce, protože obsahují absolutní i relativní souřadnice kurzoru myši (viz on_mouse_motion) a taktéž informace o stisknutých tlačítkách (viz on_mouse_press a taktéž on_mouse_release):

def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
    pass

Pokud budeme chtít realizovat pohyb spritu metodou drag and drop, bude to díky výše popsané callback funkci ve skutečnosti velmi snadné až triviální:

@window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
    if buttons & pyglet.window.mouse.LEFT:
        sprite.x = x
        sprite.y = y

Takto implementovanou změnu pozice spritu použijeme v dalším demonstračním příkladu:

#!/usr/bin/env python

import pyglet


GRAY = (128, 128, 128, 255)
RED = (255, 128, 128, 255)


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def make_sprite(filename, window):
    image_stream = open("gnome-globe.png", "rb")
    image = pyglet.image.load('gnome-globe.png', file=image_stream)

    # stred spritu bude odpovidat stredu obrazku - sprite se nam bude
    # mnohem lepe pozicovat
    image.anchor_x = image.width / 2
    image.anchor_y = image.height / 2

    sprite = pyglet.sprite.Sprite(image)
    # vycentrovani spritu
    sprite.x = window.width / 2 - image.width / 2
    sprite.y = window.height / 2 - image.height / 2
    sprite.step = 5
    return sprite


window = create_window(640, 480)
sprite = make_sprite("gnome-globe.png", window)


@window.event
def on_draw():
    window.clear()
    sprite.draw()


@window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
    if buttons & pyglet.window.mouse.LEFT:
        sprite.x = x
        sprite.y = y


pyglet.app.run()

7. Využití informací o relativní změně kurzoru myši

Pokud jste si spustili předchozí příklad, asi jste zjistili, že ve chvíli, kdy je stisknuto tlačítko myši, „poskočí“ sprite přesně na to místo, kde se kurzor nachází. Je tomu tak z toho důvodu, že při stisku tlačítka myši se poprvé zavolá funkce on_mouse_drag, která nastaví pozici spritu nezávisle na jeho předchozí pozici v okně aplikace:

@window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
    if buttons & pyglet.window.mouse.LEFT:
        sprite.x = x
        sprite.y = y

Toto chování je možné relativně snadno zkorigovat a to konkrétně použitím parametrů dx a dy, které obsahují relativní posun kurzoru od posledního volání této funkce. Relativní souřadnice samozřejmě musíme přičíst k aktuálním souřadnicím spritu (pro jednoduchost zde používám operátor +=):

@window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
    if buttons & pyglet.window.mouse.LEFT:
        sprite.x += dx
        sprite.y += dy

Takto vylepšenou změnu pozice spritu použijeme v dalším demonstračním příkladu:

#!/usr/bin/env python

import pyglet


GRAY = (128, 128, 128, 255)
RED = (255, 128, 128, 255)


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def make_sprite(filename, window):
    image_stream = open("gnome-globe.png", "rb")
    image = pyglet.image.load('gnome-globe.png', file=image_stream)

    # stred spritu bude odpovidat stredu obrazku - sprite se nam bude
    # mnohem lepe pozicovat
    image.anchor_x = image.width / 2
    image.anchor_y = image.height / 2

    sprite = pyglet.sprite.Sprite(image)
    # vycentrovani spritu
    sprite.x = window.width / 2 - image.width / 2
    sprite.y = window.height / 2 - image.height / 2
    sprite.step = 5
    return sprite


window = create_window(640, 480)
sprite = make_sprite("gnome-globe.png", window)


@window.event
def on_draw():
    window.clear()
    sprite.draw()


@window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
    if buttons & pyglet.window.mouse.LEFT:
        sprite.x += dx
        sprite.y += dy


pyglet.app.run()

8. Otočení kolečkem myši: on_mouse_scroll

V mnoha aplikacích můžeme využít i kolečko myši. Ve chvíli, kdy uživatel kolečkem otočí, zavolá se callback funkce pojmenovaná on_mouse_scroll, které se předá jak aktuální souřadnice kurzoru myši, tak i relativní hodnota otočení. Ve skutečnosti tato callback funkce podporuje dvě relativní hodnoty – x-ovou a y-ovou. Záleží na konkrétním provedení myši, jaké informace (zda vůbec nějaké) se přenesou v parametru scroll_x; typicky je tato hodnota využitelná u myší, které namísto standardního kolečka obsahují buď malý touchpad nebo tzv. scroll ball (malý trackball). Naproti tomu náklon kolečka se většinou považuje za stisk čtvrtého resp. pátého tlačítka myši:

@window.event
def on_mouse_scroll(x, y, scroll_x, scroll_y):
    pass

9. Změna velikosti spritu pomocí kolečka myši

Kolečkem myši můžeme měnit například velikost spritu. Callback funkce on_mouse_scroll může vypadat následovně (konstanta 4.0 byla zvolena na základě praktických zkušeností):

@window.event
def on_mouse_scroll(x, y, scroll_x, scroll_y):
    sprite.scale += float(scroll_y/4.0)

Obrázek 6: Změna velikosti spritu s využitím kolečka myši.

Ve chvíli, kdy uživatel otočí kolečkem, zavolá se callback funkce a v parametru scroll_y bude uloženo kladné či záporné číslo, podle toho, na kterou stranu bylo kolečkem otočeno. Tato hodnota se po mírné úpravě přičte či odečte od aktuálního měřítka (velikosti spritu). Povšimněte si, že pokud měřítka dosáhne záporné hodnoty, bude sprite středově zrcadlen:

Obrázek 7: Zrcadlení spritu s využitím kolečka myši.

Následuje výpis zdrojového kódu tohoto příkladu:

#!/usr/bin/env python

import pyglet


GRAY = (128, 128, 128, 255)
RED = (255, 128, 128, 255)


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def make_sprite(filename, window):
    image_stream = open("gnome-globe.png", "rb")
    image = pyglet.image.load('gnome-globe.png', file=image_stream)

    # stred spritu bude odpovidat stredu obrazku - sprite se nam bude
    # mnohem lepe pozicovat
    image.anchor_x = image.width / 2
    image.anchor_y = image.height / 2

    sprite = pyglet.sprite.Sprite(image)
    # vycentrovani spritu
    sprite.x = window.width / 2 - image.width / 2
    sprite.y = window.height / 2 - image.height / 2
    sprite.step = 5
    return sprite


window = create_window(640, 480)
sprite = make_sprite("gnome-globe.png", window)


@window.event
def on_draw():
    window.clear()
    sprite.draw()


@window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
    if buttons & pyglet.window.mouse.LEFT:
        sprite.x += dx
        sprite.y += dy


@window.event
def on_mouse_scroll(x, y, scroll_x, scroll_y):
    sprite.scale += float(scroll_y/4.0)


pyglet.app.run()

Obrázek 8: Zvětšení a současně i posun spritu.

10. Standardní množina kurzorů

V knihovně Pyglet je možné namísto standardního kurzoru ve tvaru šipky směřující doleva nahoru použít i kurzory dalších tvarů. Libovolný standardní (systémový) kurzor se vybere příkazem:

cursor = window.get_system_mouse_cursor(cursor_type)

Následně se tento kurzor nastaví pomocí:

window.set_mouse_cursor(cursor)

Konstanty pro standardní (systémové) kurzory jsou specifikovány ve třídě pyglet.window.Window.. Jak je z následujícího výpisu patrné, je možné namísto konstant použít přímo řetězce obsahující jména kurzorů:

CURSOR_CROSSHAIR = 'crosshair'
CURSOR_DEFAULT = None
CURSOR_HAND = 'hand'
CURSOR_HELP = 'help'
CURSOR_NO = 'no'
CURSOR_SIZE = 'size'
CURSOR_SIZE_DOWN = 'size_down'
CURSOR_SIZE_DOWN_LEFT = 'size_down_left'
CURSOR_SIZE_DOWN_RIGHT = 'size_down_right'
CURSOR_SIZE_LEFT = 'size_left'
CURSOR_SIZE_LEFT_RIGHT = 'size_left_right'
CURSOR_SIZE_RIGHT = 'size_right'
CURSOR_SIZE_UP = 'size_up'
CURSOR_SIZE_UP_DOWN = 'size_up_down'
CURSOR_SIZE_UP_LEFT = 'size_up_left'
CURSOR_SIZE_UP_RIGHT = 'size_up_right'
CURSOR_TEXT = 'text'
CURSOR_WAIT = 'wait'
CURSOR_WAIT_ARROW = 'wait_arrow'

V dalším příkladu si připravíme slovník s mapováním kláves F1F10 na konstanty se jmény standardních (systémových) kurzorů:

cursors = {
    pyglet.window.key.F1: pyglet.window.Window.CURSOR_DEFAULT,
    pyglet.window.key.F2: pyglet.window.Window.CURSOR_HAND,
    pyglet.window.key.F3: pyglet.window.Window.CURSOR_HELP,
    pyglet.window.key.F4: pyglet.window.Window.CURSOR_SIZE,
    pyglet.window.key.F5: pyglet.window.Window.CURSOR_SIZE_UP,
    pyglet.window.key.F6: pyglet.window.Window.CURSOR_SIZE_DOWN,
    pyglet.window.key.F7: pyglet.window.Window.CURSOR_SIZE_LEFT,
    pyglet.window.key.F8: pyglet.window.Window.CURSOR_SIZE_RIGHT,
    pyglet.window.key.F9: pyglet.window.Window.CURSOR_WAIT,
    pyglet.window.key.F10: pyglet.window.Window.CURSOR_NO
}

Následně po stisku klávesy F1F10 provedeme přepnutí kurzoru, což je ve skutečnosti velmi snadné:

@window.event
def on_key_press(symbol, modifiers):
    cursor_type = cursors.get(symbol)
    cursor = window.get_system_mouse_cursor(cursor_type)
    window.set_mouse_cursor(cursor)

Úplný zdrojový kód příkladu pro přepínání kurzorů myši vypadá takto:

#!/usr/bin/env python

import pyglet


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


window = create_window(640, 480)


@window.event
def on_draw():
    window.clear()


cursors = {
    pyglet.window.key.F1: pyglet.window.Window.CURSOR_DEFAULT,
    pyglet.window.key.F2: pyglet.window.Window.CURSOR_HAND,
    pyglet.window.key.F3: pyglet.window.Window.CURSOR_HELP,
    pyglet.window.key.F4: pyglet.window.Window.CURSOR_SIZE,
    pyglet.window.key.F5: pyglet.window.Window.CURSOR_SIZE_UP,
    pyglet.window.key.F6: pyglet.window.Window.CURSOR_SIZE_DOWN,
    pyglet.window.key.F7: pyglet.window.Window.CURSOR_SIZE_LEFT,
    pyglet.window.key.F8: pyglet.window.Window.CURSOR_SIZE_RIGHT,
    pyglet.window.key.F9: pyglet.window.Window.CURSOR_WAIT,
    pyglet.window.key.F10: pyglet.window.Window.CURSOR_NO
}


@window.event
def on_key_press(symbol, modifiers):
    cursor_type = cursors.get(symbol)
    cursor = window.get_system_mouse_cursor(cursor_type)
    window.set_mouse_cursor(cursor)


pyglet.app.run()

11. Vlastní kurzor myši načtený z rastrového obrázku

Zavoláním metody:

window.set_mouse_cursor(cursor)

Je možné namísto předpřipravených kurzorů použít libovolný rastrový obrázek načtený z externího souboru. Pro načtení obrázku a jeho konverzi do kurzoru si můžeme připravit pomocnou funkci:

def load_cursor(filename):
    image = pyglet.image.load(filename)
    return pyglet.window.ImageMouseCursor(image)

Pokud tedy budeme chtít namísto standardního kurzoru použít náš testovací glóbus, stačí nám na to pouhé dva řádky:

cursor = load_cursor("gnome-globe.png")
window.set_mouse_cursor(cursor)

V dalším příkladu tedy nemusíme načítat sprity ani reagovat na pohyb kurzoru; vše se totiž provede automaticky:

#!/usr/bin/env python

import pyglet


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def load_cursor(filename):
    image = pyglet.image.load(filename)
    return pyglet.window.ImageMouseCursor(image)


window = create_window(640, 480)
cursor = load_cursor("gnome-globe.png")
window.set_mouse_cursor(cursor)


@window.event
def on_draw():
    window.clear()


pyglet.app.run()

12. Vycentrování vlastního kurzoru

V předchozím demonstračním příkladu jsme pro načtení kurzoru použili uživatelskou funkci, která načetla rastrový obrázek a vytvořila z něho kurzor zavoláním pyglet.window.ImageMouseCursor:

def load_cursor(filename):
    image = pyglet.image.load(filename)
    return pyglet.window.ImageMouseCursor(image)

Tímto způsobem načtený kurzor má jednu nepříjemnou vlastnost – aktivní bod kurzoru je umístěn v levém horním rohu obrázku a nikoli v jeho středu. Tato vlastnost způsobí problémy (i když nijak velké) ve chvíli, kdy je kurzor umístěn na okraji okna aplikace. Úprava chování je ovšem ve skutečnosti velmi jednoduchá, protože funkce pyglet.window.ImageMouseCursor akceptuje další dva nepovinné parametry, přes něž se předává pozice aktivního bodu kurzoru. Pokud do těchto parametrů zadáme souřadnice [image.width/2, image.height/2], bude bod přesunut přesně doprostřed obrázku:

def load_cursor(filename):
    image = pyglet.image.load(filename)
    return pyglet.window.ImageMouseCursor(image, image.width/2, image.height/2)

Úplný zdrojový kód upraveného demonstračního příkladu je zobrazen pod tímto odstavcem:

#!/usr/bin/env python

import pyglet


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def load_cursor(filename):
    image = pyglet.image.load(filename)
    return pyglet.window.ImageMouseCursor(image, image.width/2, image.height/2)


window = create_window(640, 480)
cursor = load_cursor("gnome-globe.png")
window.set_mouse_cursor(cursor)


@window.event
def on_draw():
    window.clear()


pyglet.app.run()

13. Kombinace vlastního kurzoru a standardních kurzorů

V aplikaci se samozřejmě můžeme přepínat mezi standardními kurzory a kurzorem vlastním (nebo větším množstvím vlastních kurzorů). To je ukázáno v dnešním předposledním demonstračním příkladu, v němž je možné klávesami F1 až F10 vybírat standardní kurzory a mezerníkem pak kurzor vlastní (globus):

Klávesa Kurzor
F1 výchozí šipka směřující vlevo nahoru
F2 kurzor s ikonou ruky
F3 kurzor s ikonou nápovědy
F4 kurzor používaný při změně velikosti objektů
F5 šipka nahoru
F6 šipka dolů
F7 šipka doleva
F8 šipka doprava
F9 čekání na dokončení operace (závisí na systému)
F10 většinou zobrazeno jako ×
mezerník vlastní kurzor (globus)

Následuje výpis zdrojového kódu tohoto příkladu:

#!/usr/bin/env python

import pyglet


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def load_cursor(filename):
    image = pyglet.image.load(filename)
    return pyglet.window.ImageMouseCursor(image, image.width/2, image.height/2)


window = create_window(640, 480)
custom_cursor = load_cursor("gnome-globe.png")


@window.event
def on_draw():
    window.clear()


cursors = {
    pyglet.window.key.F1: pyglet.window.Window.CURSOR_DEFAULT,
    pyglet.window.key.F2: pyglet.window.Window.CURSOR_HAND,
    pyglet.window.key.F3: pyglet.window.Window.CURSOR_HELP,
    pyglet.window.key.F4: pyglet.window.Window.CURSOR_SIZE,
    pyglet.window.key.F5: pyglet.window.Window.CURSOR_SIZE_UP,
    pyglet.window.key.F6: pyglet.window.Window.CURSOR_SIZE_DOWN,
    pyglet.window.key.F7: pyglet.window.Window.CURSOR_SIZE_LEFT,
    pyglet.window.key.F8: pyglet.window.Window.CURSOR_SIZE_RIGHT,
    pyglet.window.key.F9: pyglet.window.Window.CURSOR_WAIT,
    pyglet.window.key.F10: pyglet.window.Window.CURSOR_NO
}


@window.event
def on_key_press(symbol, modifiers):
    if symbol == pyglet.window.key.SPACE:
        cursor = custom_cursor
    else:
        cursor_type = cursors.get(symbol)
        cursor = window.get_system_mouse_cursor(cursor_type)
    window.set_mouse_cursor(cursor)


pyglet.app.run()

14. Exkluzivní režim

Poslední důležitou vlastností knihovny Pyglet při práci s myší je schopnost získat takzvaný exkluzivní přístup ke kurzoru myši. To znamená, že kurzor nebude vyjíždět z okna aktivní aplikace (resp. přesněji řečeno vyjíždět bude, ale nebude viditelný, ostatně implicitně nebude viditelný ani v aktivní aplikaci). Toto chování využijeme zejména ve chvíli, kdy se používají callback funkce on_mouse_motion a on_mouse_drag. Exkluzivní režim se povoluje jednoduše, ideálně ihned po vytvoření okna:

window = create_window(640, 480)
window.set_exclusive_mouse(True)

Exkluzivní přístup použijeme v dnešním posledním demonstračním příkladu, v němž se pomocí myši posouvá spritem (malým rastrovým obrázkem) po ploše okna aplikace:

#!/usr/bin/env python

import pyglet


def create_window(width, height):
    return pyglet.window.Window(width=width,
                                height=height,
                                caption="Pyglet library")


def make_sprite(filename, window):
    image_stream = open("gnome-globe.png", "rb")
    image = pyglet.image.load('gnome-globe.png', file=image_stream)

    # stred spritu bude odpovidat stredu obrazku - sprite se nam bude
    # mnohem lepe pozicovat
    image.anchor_x = image.width / 2
    image.anchor_y = image.height / 2

    sprite = pyglet.sprite.Sprite(image)
    # vycentrovani spritu
    sprite.x = window.width / 2 - image.width / 2
    sprite.y = window.height / 2 - image.height / 2
    sprite.step = 5
    return sprite


window = create_window(640, 480)
window.set_exclusive_mouse(True)
sprite = make_sprite("gnome-globe.png", window)


@window.event
def on_draw():
    window.clear()
    sprite.draw()


@window.event
def on_mouse_motion(x, y, dx, dy):
    sprite.x += dx
    sprite.y += dy


pyglet.app.run()

15. Repositář s demonstračními příklady

Všech dvanáct dnes popsaných demonstračních příkladů bylo uloženo do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý repositář. Pro jejich spuštění je samozřejmě nutné mít nainstalovanou jak knihovnu Pyglet, tak i podpůrné grafické knihovny OpenGL a GLU (což se většinou provede automaticky v rámci instalace balíčku s Pygletem, viz též úvodní díl tohoto seriálu):

Příklad Odkaz
76_on_mouse_press.py https://github.com/tisnik/presentations/blob/master/pyglet/76_on_mouse_press.py
77_on_mouse_press_release.py https://github.com/tisnik/presentations/blob/master/pyglet/77_on_mouse_press_release.py
78_on_mouse_press_sprite.py https://github.com/tisnik/presentations/blob/master/pyglet/78_on_mouse_press_sprite.py
79_on_mouse_motion.py https://github.com/tisnik/presentations/blob/master/pyglet/79_on_mouse_motion.py
80_on_mouse_drag.py https://github.com/tisnik/presentations/blob/master/pyglet/80_on_mouse_drag.py
81_better_on_mouse_drag.py https://github.com/tisnik/presentations/blob/master/pyglet/81_better_on_mouse_drag.py
82_on_mouse_scroll.py https://github.com/tisnik/presentations/blob/master/pyglet/82_on_mouse_scroll.py
83_mouse_cursor.py https://github.com/tisnik/presentations/blob/master/pyglet/83_mouse_cursor.py
84_custom_mouse_cursor.py https://github.com/tisnik/presentations/blob/master/pyglet/84_custom_mouse_cursor.py
85_custom_centered_cursor.py https://github.com/tisnik/presentations/blob/master/pyglet/85_custom_centered_cursor.py
86_custom_and_standard_cursors.py https://github.com/tisnik/presentations/blob/master/pyglet/86_custom_and_standard_cursors.py
87_exclusive_mouse.py https://github.com/tisnik/presentations/blob/master/pyglet/87_exclusive_mouse.py

16. Odkazy na Internetu

  1. Working with the mouse
    http://pyglet.readthedocs.io/en/latest/programming_guide/mouse.html
  2. Working with the keyboard
    http://pyglet.readthedocs.io/en/latest/programming_guide/keyboard.html
  3. pyglet.input
    https://pyglet.readthedocs.io/en/latest/modules/input.html
  4. Class pyglet.graphics.vertexdomain.VertexList
    https://pythonhosted.org/pyglet/api/pyglet.graphics.vertexdomain.VertexList-class.html
  5. Class pyglet.graphics.vertexdomain.VertexDomain
    https://pythonhosted.org/pyglet/api/pyglet.graphics.vertexdomain.VertexDomain-class.html
  6. Pyglet: Module Hierarchy
    https://pythonhosted.org/pyglet/api/module-tree.html
  7. Learning Modern OpenGL
    https://www.codeproject.com/articles/771225/learning-modern-opengl
  8. OpenGL Utility Library
    https://en.wikipedia.org/wiki/OpenGL_Utility_Library
  9. GLU Specification
    https://www.opengl.org/registry/doc/glu1.3.pdf
  10. The Perlin noise math FAQ
    https://mzucker.github.io/html/perlin-noise-math-faq.html
  11. Perlin noise
    https://en.wikipedia.org/wiki/Perlin_noise
  12. Perlin Noise Generator (Python recipe)
    http://code.activestate.com/recipes/578470-perlin-noise-generator/
  13. Simplex noise demystified
    http://www.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
  14. glTexEnv – příkaz OpenGL
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTexEnv.xml
  15. glGetTexEnv – příkaz OpenGL
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glGetTexEnv.xml
  16. Pyglet Home Page
    https://bitbucket.org/pyglet/pyglet/wiki/Home
  17. Pyglet: dokumentace k verzi 1.2
    https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/
  18. Dokumentace k verzi 1.2 ve formátu PDF
    https://readthedocs.org/projects/pyglet/downloads/pdf/pyglet-1.2-maintenance/
  19. PyOpenGL
    http://pyopengl.sourceforge.net/
  20. The #! magic, details about the shebang/hash-bang mechanism on various Unix flavours
    https://www.in-ulm.de/~mascheck/various/shebang/
  21. Shebang (Unix)
    https://en.wikipedia.org/wiki/Shebang_%28Unix%29
  22. Domovská stránka systému LÖVE
    http://love2d.org/
  23. Simple DirectMedia Layer (home page)
    http://www.libsdl.org/
  24. Simple DirectMedia Layer (Wikipedia)
    https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
  25. Seriál Grafická knihovna OpenGL
    https://www.root.cz/serialy/graficka-knihovna-opengl/
  26. Pyglet event loop
    http://pyglet.readthedocs.io/en/latest/programming_guide/eventloop.html
  27. Decorators I: Introduction to Python Decorators
    http://www.artima.com/weblogs/viewpost.jsp?thread=240808
  28. 3D Programming in Python – Part 1
    https://greendalecs.wordpress.com/2012/04/21/3d-programming-in-python-part-1/
  29. A very basic Pyglet tutorial
    http://www.natan.termitnjak.net/tutorials/pyglet_basic.html
  30. Alpha blending
    https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending