|
| 1 | +# Grafika |
| 2 | + |
| 3 | +Teď si ukážeme, jak napsat grafickou aplikaci. |
| 4 | + |
| 5 | +Python obsahuje nástroje na kreslení obrázků, |
| 6 | +ale pro tvorbu her nejsou příliš vhodné. |
| 7 | +Použijeme proto *knihovnu* (nadstavbu) jménem Pyglet, která je přímo stavěná |
| 8 | +na interaktivní grafiku. |
| 9 | + |
| 10 | +Musíme si ji ale nejdřív zvlášť nainstalovat. |
| 11 | +Nejjistější je do příkazové řádky se zapnutým virtuálním prostředím |
| 12 | +zadat následující dva příkazy. |
| 13 | +(Existují i jednodušší způsoby, které ovšem vyžadují „správně“ |
| 14 | +nastavený systém.) |
| 15 | + |
| 16 | +* Aktualizace nástroje `pip`, který umí instalovat knihovny pro Python: |
| 17 | + ``` console |
| 18 | + (venv)$ python -m pip install --upgrade pip |
| 19 | + ``` |
| 20 | + (V překladu: **Python**e, spusť **m**odul **pip** a řekni mu, |
| 21 | + ať na**instal**uje a kdyžtak aktualizuje (*upgrade*) knihovnu **pip**.) |
| 22 | +* Samotné nainstalování Pygletu: |
| 23 | + ``` console |
| 24 | + (venv)$ python -m pip install pyglet |
| 25 | + ``` |
| 26 | + (V překladu: **Python**e, spusť **m**odul **pip** a řekni mu, |
| 27 | + ať na**instal**uje knihovnu **pyglet**.) |
| 28 | + |
| 29 | +U mě vypadá instalace nějak takto: |
| 30 | + |
| 31 | +```console |
| 32 | +(venv)$ python -m pip install --upgrade pip |
| 33 | +Requirement already satisfied: pip in ./venv/lib/python3.6/site-packages (18.0) |
| 34 | +(venv)$ python -m pip install pyglet |
| 35 | +Collecting pyglet |
| 36 | + Downloading pyglet-1.2.4-py3-none-any.whl (964kB) |
| 37 | +Installing collected packages: pyglet |
| 38 | +Successfully installed pyglet-1.2.4 |
| 39 | +``` |
| 40 | + |
| 41 | +Důležité je `Successfully installed`, resp. `Requirement already satisfied` |
| 42 | +na konci. |
| 43 | +To znamená že je knihovna připravená k použití! |
| 44 | + |
| 45 | + |
| 46 | +## Kostra programu |
| 47 | + |
| 48 | +Teď zkus v editoru vytvořit nový soubor, uložit ho jako `grafika.py` |
| 49 | +a napsat do něj následující program: |
| 50 | + |
| 51 | +```python |
| 52 | +import pyglet |
| 53 | +window = pyglet.window.Window() |
| 54 | +pyglet.app.run() |
| 55 | +print('Hotovo!') |
| 56 | +``` |
| 57 | + |
| 58 | +Spusť ho. Mělo by se objevit černé okýnko. |
| 59 | + |
| 60 | +> [note] Okýnko není černé? |
| 61 | +> Na některých počítačích (často s macOS a některými druhy Linuxu) se stává, |
| 62 | +> že okýnko není černé, ale je v něm nějaký „nepořádek“. |
| 63 | +> To nevadí. |
| 64 | +> Než do okýnka začneme kreslit, nepořádek uklidíme. |
| 65 | +
|
| 66 | +> [note] AttributeError? |
| 67 | +> Jestli dostaneš chybu |
| 68 | +> `AttributeError: module 'pyglet' has no attribute 'window'`, zkontroluj si, |
| 69 | +> zě jsi soubor pojmenoval{{a}} `grafika.py` a ne `pyglet.py`. |
| 70 | +> Soubor v editoru„ulož jako `grafika.py`, případný soubor `pyglet.py` smaž, |
| 71 | +> a zkus to znovu. |
| 72 | +
|
| 73 | +Hotovo? Pojďme si vysvětlit, co se v tomhle programu děje. |
| 74 | + |
| 75 | +Příkaz `import pyglet` ti zpřístupní grafickou knihovnu, tak jako třeba |
| 76 | +`import random` ti zpřístupní funkce okolo náhodných čísel. |
| 77 | + |
| 78 | +Zavolání `pyglet.window.Window()` vytvoří nové *okýnko* na obrazovce. |
| 79 | +Vrátí objekt, kterým pak tohle okýnko můžeš ovládat; ten si uložíme |
| 80 | +do proměnné `window`. |
| 81 | + |
| 82 | +Zavolání `pyglet.app.run()` pak spustí aplikaci. |
| 83 | +Co to znamená? |
| 84 | + |
| 85 | +Jednoduché programy, které jsi zatím psal{{a}}, jsou popisy procesu – podobně |
| 86 | +jako třeba recepty k vaření. |
| 87 | +Sled kroků, které Python postupně vykoná od prvního po poslední. |
| 88 | +Občas se něco opakuje a některé kroky se dají „zabalit“ do funkce, |
| 89 | +ale vždycky jsme zatím popisovali jeden postup od začátku po konec. |
| 90 | + |
| 91 | +Programy pro složitější aplikace spíš než jako recept vypadají jako příručka |
| 92 | +automechanika. |
| 93 | +Popisují, co se má stát v jaké situaci. |
| 94 | +Třeba program pro textový editor by mohl vypadat takhle: |
| 95 | + |
| 96 | +* Když uživatel zmáčkne písmenko na klávesnici, přidej ho do dokumentu. |
| 97 | +* <p>Když uživatel zmáčkne <kbd>⌫ Backspace</kbd>, poslední písmenko umaž.</p> |
| 98 | +* Když uživatel zmáčkne tlačítko Uložit, zapiš soubor na disk. |
| 99 | + |
| 100 | +I takový program se dá napsat i jako „recept“ – ale ten recept je pro všechny |
| 101 | +aplikace stejný: |
| 102 | + |
| 103 | +* Pořád dokola: |
| 104 | + * Počkej, než se něco zajímavého stane |
| 105 | + * Zareaguj na nastalou situaci |
| 106 | + |
| 107 | +A to je přesně to, co dělá `pyglet.app.run()`. |
| 108 | +Zpracovává *události*, situace na které je potřeba zareagovat. |
| 109 | +V tvém programu reaguje zavírací tlačítko okýnka a na klávesu <kbd>Esc</kbd> |
| 110 | +tím, že okno zavře a ukončí se. |
| 111 | + |
| 112 | +Tvůj úkol teď bude popsat, jaké další události jsou zajímavé |
| 113 | +a jak na ně reagovat. |
| 114 | + |
| 115 | + |
| 116 | +## Obsluha událostí |
| 117 | + |
| 118 | +Nejjednodušší událost, kterou můžeme obsloužit, je psaní textu na klávesnici. |
| 119 | + |
| 120 | +Zkus do programu těsně nad řádek `pyglet.app.run()` dát následující kód: |
| 121 | + |
| 122 | +``` python |
| 123 | +@window.event |
| 124 | +def on_text(text): |
| 125 | + print(text) |
| 126 | +``` |
| 127 | + |
| 128 | +Co to je? |
| 129 | +Je to definice funkce, ale na začátku má *dekorátor* – tu řádku začínající |
| 130 | +zavináčem. |
| 131 | + |
| 132 | +Dekorátor `window.event` je způsob, jak Pygletu říct, že má tuto funkci |
| 133 | +spustit, když se něco zajímavého stane. |
| 134 | + |
| 135 | +Co zajímavého? |
| 136 | +To Pyglet zjistí podle jména funkce: `on_text` reaguje na text. |
| 137 | +Vždycky, když uživatel zmáčkne klávesu, Pyglet zavolá tvoji funkci! |
| 138 | + |
| 139 | +A co udělá tvoje funkce? Zavolá `print`. To už znáš. |
| 140 | +Zadaný text se vypíše na konzoli, ze které program spouštíš. |
| 141 | +To, že je otevřené okýnko, neznamená že `print` začne automaticky psát do něj! |
| 142 | + |
| 143 | + |
| 144 | +## Kreslení |
| 145 | + |
| 146 | +Jak psát do okýnka? |
| 147 | +To je trochu složitější než do konzole. |
| 148 | +Text tu může mít různé barvy, velikosti, druhy písma, |
| 149 | +může být všelijak posunutý nebo natočený… |
| 150 | + |
| 151 | +Všechny tyhle *atributy* písma můžeme (i se samotným textem) uložit do objektu |
| 152 | +`Label` („popisek“). |
| 153 | +Zkus to – dej následující kód pod řádek s `window = `: |
| 154 | + |
| 155 | +```python |
| 156 | +label = pyglet.text.Label("Ahoj!", x=10, y=20) |
| 157 | +``` |
| 158 | + |
| 159 | +V proměnné `label` teď budeš mít máš popisek s textem `"Ahoj"`, který patří |
| 160 | +na pozici (10, 20) – 10 bodů od pravého okraje okna, 20 od spodního. |
| 161 | + |
| 162 | +To je ale jen informace. |
| 163 | +Podobně jako pro vypsání textu do konzole je potřeba zavolat `print`, |
| 164 | +pro nakreslení textu je potřeba reagovat na událost |
| 165 | +*vykreslení okna* – `on_draw`. |
| 166 | + |
| 167 | +Dej pod funkci `on_text` tento kód: |
| 168 | + |
| 169 | +```python |
| 170 | +@window.event |
| 171 | +def on_draw(): |
| 172 | + window.clear() |
| 173 | + label.draw() |
| 174 | +``` |
| 175 | + |
| 176 | +Tuhle funkci Pyglet zavolá vždycky, když je potřeba nakreslit obsah okýnka. |
| 177 | +U animací (filmů nebo her) to často bývá třeba 60× za sekundu |
| 178 | +(„[60 FPS](https://cs.wikipedia.org/wiki/Sn%C3%ADmkov%C3%A1_frekvence)“). |
| 179 | + |
| 180 | +Funkce dělá dvě věci: |
| 181 | +* Smaže celé okýnko (nabarví ho na černo) |
| 182 | +* Vykreslí text |
| 183 | + |
| 184 | +V okně teď bude vidět pozdrav! |
| 185 | + |
| 186 | + |
| 187 | +Zkus ještě změnit `on_text` tak, aby se zadaný text místo na konzoli |
| 188 | +ukázal v okýnku. |
| 189 | +To se dělá přiřazením do *atributu* `label.text`: |
| 190 | + |
| 191 | +```python |
| 192 | +@window.event |
| 193 | +def on_text(text): |
| 194 | + print('Starý text:', label.text) |
| 195 | + label.text = text |
| 196 | + print('Nový text:', label.text) |
| 197 | +``` |
| 198 | + |
| 199 | +Zvládneš v této funkci nový text přidat ke starému, |
| 200 | +aby program fungoval jako jednoduchý textový editor? |
| 201 | + |
| 202 | +{% filter solution %} |
| 203 | +```python |
| 204 | +@window.event |
| 205 | +def on_text(text): |
| 206 | + label.text = label.text + text |
| 207 | +``` |
| 208 | +{% endfilter %} |
| 209 | + |
| 210 | + |
| 211 | +## Další událostí |
| 212 | + |
| 213 | +Na jaké další události se dá reagovat? |
| 214 | +Všechny jsou popsané v [dokumentaci Pygletu](https://pyglet.readthedocs.io/en/latest/modules/window.html#pyglet.window.Window.on_activate). |
| 215 | +Tady uvádím pár zajímavých. |
| 216 | + |
| 217 | +### Stisk klávesy |
| 218 | + |
| 219 | +Klávesy, které nezadávají text (šipky, <kbd>Backspace</kbd> nebo |
| 220 | +<kbd>Enter</kbd>, atp.) se dají rozpoznat v události `on_key_press`. |
| 221 | + |
| 222 | +Funkce `on_key_press` má dva argumenty: první je kód klávesy, |
| 223 | +který můžeš porovnat s konstantou z [pyglet.window.key](https://pyglet.readthedocs.io/en/latest/modules/window_key.html#key-constants). |
| 224 | +Druhý určuje stisknuté modifikátory jako <kbd>Shift</kbd> nebo <kbd>Ctrl</kbd>. |
| 225 | + |
| 226 | +``` python |
| 227 | +@window.event |
| 228 | +def on_key_press(key_code, modifier): |
| 229 | + if key_code == pyglet.window.key.BACKSPACE: |
| 230 | + label.text = label.text[:-1] |
| 231 | + |
| 232 | + if key_code == pyglet.window.key.ENTER: |
| 233 | + print('Zadaná zpráva:', label.text) |
| 234 | + window.close() |
| 235 | +``` |
| 236 | + |
| 237 | +Na macOS budeš možná muset zaměňit `BACKSPACE` za `DELETE`. {# XXX: je to tak? #} |
| 238 | +(Nebo si doma nastuduj [způsob](https://pyglet.readthedocs.io/en/latest/programming_guide/keyboard.html#motion-events), jak to dělat automaticky a správně.) |
| 239 | + |
| 240 | + |
| 241 | +### Kliknutí myši |
| 242 | + |
| 243 | +Při obsluze události `on_mouse_press` dostaneš informace o pozici |
| 244 | +kliknutí (<var>x</var>-ovou a <var>x</var>-ovou souřadnici) |
| 245 | +a navíc informaci o stisknutém tlačítku myši a modifikátoru. |
| 246 | + |
| 247 | +Takhle se třeba popisek přesune na místo kliknutí: |
| 248 | + |
| 249 | +```python |
| 250 | +@window.event |
| 251 | +def on_mouse_press(x, y, button, modifier): |
| 252 | + label.x = x |
| 253 | + label.y = y |
| 254 | +``` |
| 255 | + |
| 256 | + |
| 257 | +## Celý program |
| 258 | + |
| 259 | +Pro případ, že by ses ztratil{{a}} nebo nevěděla, |
| 260 | +kam který kousek kódu patří, uvádím výsledný ukázkový program. |
| 261 | + |
| 262 | +```python |
| 263 | +import pyglet |
| 264 | +window = pyglet.window.Window() |
| 265 | +label = pyglet.text.Label("Ahoj!", x=10, y=20) |
| 266 | + |
| 267 | + |
| 268 | +@window.event |
| 269 | +def on_draw(): |
| 270 | + window.clear() |
| 271 | + label.draw() |
| 272 | + |
| 273 | + |
| 274 | +@window.event |
| 275 | +def on_text(text): |
| 276 | + label.text = label.text + text |
| 277 | + |
| 278 | + |
| 279 | +@window.event |
| 280 | +def on_key_press(key_code, modifier): |
| 281 | + if key_code == pyglet.window.key.BACKSPACE: |
| 282 | + label.text = label.text[:-1] |
| 283 | + |
| 284 | + if key_code == pyglet.window.key.ENTER: |
| 285 | + print('Zadaná zpráva:', label.text) |
| 286 | + window.close() |
| 287 | + |
| 288 | + |
| 289 | +@window.event |
| 290 | +def on_mouse_press(x, y, button, modifier): |
| 291 | + label.x = x |
| 292 | + label.y = y |
| 293 | + |
| 294 | + |
| 295 | +pyglet.app.run() |
| 296 | +``` |
| 297 | + |
0 commit comments