Add browser gamepad support with pad button pictograms
Co-Authored-By: deepseek-v4.1-flash (opencode-go) via CodeCraft
This commit is contained in:
1 parent
073999d592
commit
8d51d6ec6a
26 files changed
+1066
-22
No files matched your search
@@ -440,6 +440,25 @@ help.t_long = Dlouhý stisk: informace o místě; ruka: položí tam jednotky
|
||||
help.t_double = Dvojité klepnutí rukou: pohlavek vlastním, pěst hrdinům na tvém území
|
||||
help.t_tools = Lišta nástrojů: kategorie otevřou řadu nástrojů; tlačítko menu otevře menu pauzy
|
||||
help.start_touch = Klepni pro start
|
||||
# gamepad (Gamepad API v prohlížeči)
|
||||
set.gamepad = Tlačítka gamepadu
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Použít
|
||||
pad.back = Zpět
|
||||
pad.tool = Nástroj
|
||||
pad.menu = Menu
|
||||
pad.camera = Kamera
|
||||
pad.zoom = Zvětšení
|
||||
help.p_move = Levá páčka – posun mapy
|
||||
help.p_aim = Pravá páčka – pohyb kurzoru
|
||||
help.p_rot = D-pad – otáčení a náklon
|
||||
help.p_zoom = LT / RT – přiblížení a oddálení
|
||||
help.p_use = A / křížek – použít nástroj, potvrdit (držet: natáhnout obdélník)
|
||||
help.p_back = B / kolečko – zpět, pauza Start Menu
|
||||
help.p_tool = LB / RB – volba nástroje Select – tato nápověda
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Most
|
||||
|
||||
@@ -439,6 +439,25 @@ help.t_long = Langt tryk: oplysninger om stedet; hånd: sæt enhederne ned der
|
||||
help.t_double = Dobbelttryk med hånden: bagkast til egne enheder, slå helte på din jord
|
||||
help.t_tools = Værktøjslinje: kategorier åbner en række værktøjer; menuknappen åbner pausemenuen
|
||||
help.start_touch = Tryk for at starte
|
||||
# gamepad (browserens Gamepad API)
|
||||
set.gamepad = Gamepad-knapper
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Brug
|
||||
pad.back = Tilbage
|
||||
pad.tool = Værktøj
|
||||
pad.menu = Menu
|
||||
pad.camera = Kamera
|
||||
pad.zoom = Zoom
|
||||
help.p_move = Venstre stick – flyt kortet
|
||||
help.p_aim = Højre stick – flyt markøren
|
||||
help.p_rot = D-pad – roter og vip
|
||||
help.p_zoom = LT / RT – zoom ind og ud
|
||||
help.p_use = A / kryds – brug værktøj, bekræft (hold: træk et rektangel)
|
||||
help.p_back = B / cirkel – tilbage, pausemenu Start Menu
|
||||
help.p_tool = LB / RB – vælg værktøj Select – denne hjælp
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Bro
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_double = Doppelt tippen mit der Hand: Rückhand für eigene, Faust für H
|
||||
help.t_tools = Werkzeugleiste: Kategorien öffnen eine Reihe Werkzeuge; die Menütaste öffnet das Pausemenü
|
||||
help.start_touch = Tippen zum Starten
|
||||
|
||||
# gamepad (Browser-Gamepad-API)
|
||||
set.gamepad = Gamepad-Tasten
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Benutzen
|
||||
pad.back = Zurück
|
||||
pad.tool = Werkzeug
|
||||
pad.menu = Menü
|
||||
pad.camera = Kamera
|
||||
pad.zoom = Zoom
|
||||
help.p_move = Linker Stick Karte bewegen
|
||||
help.p_aim = Rechter Stick Zeiger bewegen
|
||||
help.p_rot = Steuerkreuz Drehen und neigen
|
||||
help.p_zoom = LT / RT Hinein- und herauszoomen
|
||||
help.p_use = A / Kreuz Werkzeug benutzen, bestätigen (halten: Rechteck ziehen)
|
||||
help.p_back = B / Kreis Zurück, Pausenmenü Start Menü
|
||||
help.p_tool = LB / RB Werkzeug wählen Select Diese Hilfe
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Brücke
|
||||
tool.bridge.name = Brücke bauen
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Παρατεταμένο πάτημα: πληροφορίες γι
|
||||
help.t_double = Διπλό πάτημα με το χέρι: χαστούκι στις δικές σου μονάδες, γροθιά σε ήρωες στο έδαφός σου
|
||||
help.t_tools = Γραμμή εργαλείων: οι κατηγορίες ανοίγουν μια σειρά εργαλείων, το κουμπί μενού ανοίγει το μενού παύσης
|
||||
help.start_touch = Πάτησε για αρχή
|
||||
# gamepad (Το Gamepad API του φυλλομετρητή)
|
||||
set.gamepad = Κουμπιά χειριστηρίου
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Χρήση
|
||||
pad.back = Πίσω
|
||||
pad.tool = Εργαλείο
|
||||
pad.menu = Μενού
|
||||
pad.camera = Κάμερα
|
||||
pad.zoom = Εστίαση
|
||||
help.p_move = Αριστερό stick – μετακίνηση χάρτη
|
||||
help.p_aim = Δεξί stick – μετακίνηση δείκτη
|
||||
help.p_rot = D-pad – περιστροφή και κλίση
|
||||
help.p_zoom = LT / RT – μεγέθυνση και σμίκρυνση
|
||||
help.p_use = A / Σταυρός – χρήση εργαλείου, επιβεβαίωση (κράτημα: ορθογώνιο)
|
||||
help.p_back = B / Κύκλος – πίσω, μενού παύσης Start Μενού
|
||||
help.p_tool = LB / RB – επιλογή εργαλείου Select – αυτή η βοήθεια
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Γέφυρα
|
||||
|
||||
@@ -441,6 +441,25 @@ help.t_double = Double tap with the hand: backhand own units, punch heroes on yo
|
||||
help.t_tools = Toolbar: categories open a row of tools; the menu button opens the pause menu
|
||||
help.start_touch = Tap to start
|
||||
|
||||
# gamepad (browser Gamepad API)
|
||||
set.gamepad = Gamepad buttons
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Use
|
||||
pad.back = Back
|
||||
pad.tool = Tool
|
||||
pad.menu = Menu
|
||||
pad.camera = Camera
|
||||
pad.zoom = Zoom
|
||||
help.p_move = Left stick Move the map
|
||||
help.p_aim = Right stick Move the pointer
|
||||
help.p_rot = D-pad Rotate and tilt
|
||||
help.p_zoom = LT / RT Zoom in and out
|
||||
help.p_use = A / cross Use the tool, confirm (hold: drag a rectangle)
|
||||
help.p_back = B / circle Back, pause menu Start Menu
|
||||
help.p_tool = LB / RB Select the tool Select This help
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Bridge
|
||||
tool.bridge.name = Build Bridge
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Pulsación larga: información del punto; mano: soltar ahí las un
|
||||
help.t_double = Doble toque con la mano: abofetear unidades propias, golpear héroes en tu suelo
|
||||
help.t_tools = Barra de herramientas: las categorías abren una fila de herramientas; el botón de menú abre el menú de pausa
|
||||
help.start_touch = Toca para empezar
|
||||
# gamepad (Gamepad API del navegador)
|
||||
set.gamepad = Botones del mando
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Usar
|
||||
pad.back = Atrás
|
||||
pad.tool = Herramienta
|
||||
pad.menu = Menú
|
||||
pad.camera = Cámara
|
||||
pad.zoom = Zoom
|
||||
help.p_move = Stick izquierdo: mover el mapa
|
||||
help.p_aim = Stick derecho: mover el puntero
|
||||
help.p_rot = D-pad: rotar e inclinar
|
||||
help.p_zoom = LT / RT: acercar y alejar
|
||||
help.p_use = A / equis: usar herramienta, confirmar (mantener: arrastrar un rectángulo)
|
||||
help.p_back = B / círculo: atrás, menú de pausa Start Menú
|
||||
help.p_tool = LB / RB: elegir herramienta Select esta ayuda
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Puente
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Pitkä painallus: tietoa kohdasta; käsi: pudota yksiköt siihen
|
||||
help.t_double = Kaksoisnapautus kädellä: läimäytä omia, mukiloi sankareita omalla maalla
|
||||
help.t_tools = Työkalupalkki: kategoriat avaavat työkalurivin; valikkonappi avaa taukovalikon
|
||||
help.start_touch = Napauta aloittaaksesi
|
||||
# gamepad (selaimen Gamepad API)
|
||||
set.gamepad = Ohjaimen painikkeet
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Käytä
|
||||
pad.back = Takaisin
|
||||
pad.tool = Työkalu
|
||||
pad.menu = Valikko
|
||||
pad.camera = Kamera
|
||||
pad.zoom = Zoom
|
||||
help.p_move = Vasen sauva – siirrä karttaa
|
||||
help.p_aim = Oikea sauva – siirrä osoitinta
|
||||
help.p_rot = D-pad – pyöritä ja kallista
|
||||
help.p_zoom = LT / RT – lähennä ja loitonna
|
||||
help.p_use = A / risti – käytä työkalua, vahvista (pidä: vedä suorakulmio)
|
||||
help.p_back = B / ympyrä – takaisin, taukovalikko Start Valikko
|
||||
help.p_tool = LB / RB – valitse työkalu Select – tämä ohje
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Silta
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Dugi pritisak: podaci o mjestu; ruka: spusti jedinice ondje
|
||||
help.t_double = Dvostruki dodir rukom: pljusni svoje jedinice, udari junake na svom tlu
|
||||
help.t_tools = Alatna traka: kategorije otvaraju red alata; tipka izbornika otvara izbornik stanke
|
||||
help.start_touch = Dodirni za početak
|
||||
# gamepad (Gamepad API preglednika)
|
||||
set.gamepad = Gumbi gamepada
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Koristi
|
||||
pad.back = Natrag
|
||||
pad.tool = Alat
|
||||
pad.menu = Izbornik
|
||||
pad.camera = Kamera
|
||||
pad.zoom = Zum
|
||||
help.p_move = Lijevi stick – pomicanje karte
|
||||
help.p_aim = Desni stick – pomicanje kursora
|
||||
help.p_rot = D-pad – rotacija i nagib
|
||||
help.p_zoom = LT / RT – zumiranje
|
||||
help.p_use = A / križ – koristi alat, potvrdi (drži: povuci pravokutnik)
|
||||
help.p_back = B / krug – natrag, izbornik pauze Start Izbornik
|
||||
help.p_tool = LB / RB – odabir alata Select – ova pomoć
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Most
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Hosszú nyomás: információ a helyről; kéz: az egységek lerak
|
||||
help.t_double = Dupla koppintás a kézzel: saját egységek felpofozása, hősök ütése a saját földeden
|
||||
help.t_tools = Eszköztár: a kategóriák egy eszközsort nyitnak meg; a menügomb a szünetmenüt nyitja meg
|
||||
help.start_touch = Koppints az indításhoz
|
||||
# gamepad (a böngésző Gamepad API-ja)
|
||||
set.gamepad = Gamepad gombok
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Használat
|
||||
pad.back = Vissza
|
||||
pad.tool = Eszköz
|
||||
pad.menu = Menü
|
||||
pad.camera = Kamera
|
||||
pad.zoom = Nagyítás
|
||||
help.p_move = Bal bot – térkép mozgatása
|
||||
help.p_aim = Jobb bot – kurzor mozgatása
|
||||
help.p_rot = D-pad – forgatás és döntés
|
||||
help.p_zoom = LT / RT – közelebb és távolabb
|
||||
help.p_use = A / kereszt – eszköz használata, megerősítés (tartsd: téglalap húzása)
|
||||
help.p_back = B / kör – vissza, szünetmenü Start Menü
|
||||
help.p_tool = LB / RB – eszköz választása Select ez a súgó
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Híd
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Pressione lunga: informazioni sul punto; mano: posa lì le unità
|
||||
help.t_double = Doppio tocco con la mano: schiaffo alle tue unità, pugno agli eroi sul tuo suolo
|
||||
help.t_tools = Barra: le categorie aprono una fila di strumenti; il pulsante menu apre il menu di pausa
|
||||
help.start_touch = Tocca per iniziare
|
||||
# gamepad (Gamepad API del browser)
|
||||
set.gamepad = Pulsanti del gamepad
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Usa
|
||||
pad.back = Indietro
|
||||
pad.tool = Strumento
|
||||
pad.menu = Menu
|
||||
pad.camera = Camera
|
||||
pad.zoom = Zoom
|
||||
help.p_move = Stick sinistro: sposta la mappa
|
||||
help.p_aim = Stick destro: sposta il puntatore
|
||||
help.p_rot = D-pad: ruota e inclina
|
||||
help.p_zoom = LT / RT: zoom avanti e indietro
|
||||
help.p_use = A / croce: usa lo strumento, conferma (tieni premi: disegna un rettangolo)
|
||||
help.p_back = B / cerchio: indietro, menu di pausa Start Menu
|
||||
help.p_tool = LB / RB: scegli strumento Select questa guida
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Ponte
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Lang drukken: informatie over de plek; hand: units daar neerzetten
|
||||
help.t_double = Dubbeltikken met de hand: eigen units een mep geven, helden op je grond slaan
|
||||
help.t_tools = Werkbalk: categorieën openen een rij gereedschap; de menuknop opent het pauzemenu
|
||||
help.start_touch = Tik om te beginnen
|
||||
# gamepad (Gamepad API van de browser)
|
||||
set.gamepad = Gamepadknoppen
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Gebruiken
|
||||
pad.back = Terug
|
||||
pad.tool = Gereedschap
|
||||
pad.menu = Menu
|
||||
pad.camera = Camera
|
||||
pad.zoom = Zoom
|
||||
help.p_move = linker stick: kaart verplaatsen
|
||||
help.p_aim = rechter stick: aanwijzer verplaatsen
|
||||
help.p_rot = d-pad: draaien en kantelen
|
||||
help.p_zoom = LT / RT: in- en uitzoomen
|
||||
help.p_use = A / kruis: gereedschap gebruiken, bevestigen (indrukken: rechthoek slepen)
|
||||
help.p_back = B / cirkel: terug, pauzemenu Start Menu
|
||||
help.p_tool = LB / RB: gereedschap kiezen Select deze help
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Brug
|
||||
|
||||
@@ -439,6 +439,25 @@ help.t_long = Langt trykk: informasjon om stedet; hånd: sett ned enhetene der
|
||||
help.t_double = Dobbelttrykk med hånden: baksveiv egne enheter, slå helter på din grunn
|
||||
help.t_tools = Verktøylinje: kategorier åpner en rad med verktøy; menyknappen åpner pausemenyen
|
||||
help.start_touch = Trykk for å starte
|
||||
# gamepad (nettleserens Gamepad API)
|
||||
set.gamepad = Gamepad-knapper
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Bruk
|
||||
pad.back = Tilbake
|
||||
pad.tool = Verktøy
|
||||
pad.menu = Meny
|
||||
pad.camera = Kamera
|
||||
pad.zoom = Zoom
|
||||
help.p_move = Venstre stick – flytt kartet
|
||||
help.p_aim = Høyre stick – flytt pekeren
|
||||
help.p_rot = D-pad – roter og vip
|
||||
help.p_zoom = LT / RT – zoom inn og ut
|
||||
help.p_use = A / kryss – bruk verktøyet, bekreft (hold: dra et rektangel)
|
||||
help.p_back = B / sirkel – tilbake, pausemeny Start Meny
|
||||
help.p_tool = LB / RB – verktøyvalg Select – denne hjelpen
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Bro
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Toque longo: informação sobre o ponto; mão: largar ali as unida
|
||||
help.t_double = Toque duplo com a mão: esbofetear as tuas unidades, socar heróis no teu chão
|
||||
help.t_tools = Barra de ferramentas: as categorias abrem uma fila de ferramentas; o botão de menu abre o menu de pausa
|
||||
help.start_touch = Toca para começar
|
||||
# gamepad (Gamepad API do navegador)
|
||||
set.gamepad = Botões do controle
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Usar
|
||||
pad.back = Voltar
|
||||
pad.tool = Ferramenta
|
||||
pad.menu = Menu
|
||||
pad.camera = Câmera
|
||||
pad.zoom = Zoom
|
||||
help.p_move = Analógico esquerdo: mover o mapa
|
||||
help.p_aim = Analógico direito: mover o cursor
|
||||
help.p_rot = D-pad: girar e inclinar
|
||||
help.p_zoom = LT / RT: aproximar e afastar
|
||||
help.p_use = A / cruz: usar a ferramenta, confirmar (segurar: arrastar um retângulo)
|
||||
help.p_back = B / círculo: voltar, menu de pausa Start Menu
|
||||
help.p_tool = LB / RB: escolher ferramenta Select esta ajuda
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Ponte
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Apăsare lungă: informații despre loc; mână: lasă jos unită
|
||||
help.t_double = Atingere dublă cu mâna: palmă unităților tale, pumn eroilor de pe terenul tău
|
||||
help.t_tools = Bara de unelte: categoriile deschid un rând de unelte; butonul de meniu deschide meniul de pauză
|
||||
help.start_touch = Atinge pentru a începe
|
||||
# gamepad (Gamepad API-ul browserului)
|
||||
set.gamepad = Butoane gamepad
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Folosește
|
||||
pad.back = Înapoi
|
||||
pad.tool = Unealtă
|
||||
pad.menu = Meniu
|
||||
pad.camera = Cameră
|
||||
pad.zoom = Zoom
|
||||
help.p_move = Stickul stâng – mută harta
|
||||
help.p_aim = Stickul drept – mută pointerul
|
||||
help.p_rot = D-pad – rotește și înclină
|
||||
help.p_zoom = LT / RT – apropiere și depărtare
|
||||
help.p_use = A / cruce – folosește unealta, confirmă (ține apăsat: trage un dreptunghi)
|
||||
help.p_back = B / cerc – înapoi, meniul de pauză Start Meniu
|
||||
help.p_tool = LB / RB – alege unealta Select – acest ajutor
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Pod
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Долгое нажатие: сведения о месте; ру
|
||||
help.t_double = Двойное касание рукой: шлепок своим, удар героям на своей земле
|
||||
help.t_tools = Панель: категории открывают ряд инструментов; кнопка меню открывает паузу
|
||||
help.start_touch = Коснись, чтобы начать
|
||||
# gamepad (Gamepad API браузера)
|
||||
set.gamepad = Кнопки геймпада
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Использовать
|
||||
pad.back = Назад
|
||||
pad.tool = Инструмент
|
||||
pad.menu = Меню
|
||||
pad.camera = Камера
|
||||
pad.zoom = Масштаб
|
||||
help.p_move = Левый стик — движение карты
|
||||
help.p_aim = Правый стик — движение курсора
|
||||
help.p_rot = Крестовина — поворот и наклон
|
||||
help.p_zoom = LT / RT — приближение и отдаление
|
||||
help.p_use = A / крестик — использовать инструмент, подтвердить (удерживать: рамка)
|
||||
help.p_back = B / кружок — назад, меню паузы Start меню
|
||||
help.p_tool = LB / RB — выбор инструмента Select эта справка
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Мост
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Dlhé podržanie: informácie o mieste; ruka: položí tam jednotk
|
||||
help.t_double = Dvojité ťuknutie rukou: zaucho vlastným, päsť hrdinom na tvojom území
|
||||
help.t_tools = Lišta nástrojov: kategórie otvoria rad nástrojov; tlačidlo menu otvorí menu pauzy
|
||||
help.start_touch = Ťukni pre štart
|
||||
# gamepad (Gamepad API prehliadača)
|
||||
set.gamepad = Tlačidlá gamepadu
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Použiť
|
||||
pad.back = Späť
|
||||
pad.tool = Nástroj
|
||||
pad.menu = Menu
|
||||
pad.camera = Kamera
|
||||
pad.zoom = Priblíženie
|
||||
help.p_move = Ľavá páčka – posun mapy
|
||||
help.p_aim = Pravá páčka – posun kurzora
|
||||
help.p_rot = D-pad – otáčanie a náklon
|
||||
help.p_zoom = LT / RT – priblíženie a oddialenie
|
||||
help.p_use = A / krížik – použiť nástroj, potvrdiť (držať: natiahnuť obdĺžnik)
|
||||
help.p_back = B / krúžok – späť, pauza Start Menu
|
||||
help.p_tool = LB / RB – výber nástroja Select – táto nápoveda
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Most
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Дуги притисак: подаци о месту; рука:
|
||||
help.t_double = Двоструки додир руком: ошамари своје јединице, удари хероје на свом тлу
|
||||
help.t_tools = Трака алатки: категорије отварају ред алатки; дугме менија отвара мени паузе
|
||||
help.start_touch = Додирни за почетак
|
||||
# gamepad (Gamepad API прегледника)
|
||||
set.gamepad = Дугмад контролера
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Користи
|
||||
pad.back = Назад
|
||||
pad.tool = Алат
|
||||
pad.menu = Мени
|
||||
pad.camera = Камера
|
||||
pad.zoom = Зум
|
||||
help.p_move = Леви стик – померање карте
|
||||
help.p_aim = Десни стик – померање показивача
|
||||
help.p_rot = D-pad – ротација и нагиб
|
||||
help.p_zoom = LT / RT – зумирање
|
||||
help.p_use = A / крстић – користи алат, потврди (држати: повући правоугаоник)
|
||||
help.p_back = B / круг – назад, пауза Start Мени
|
||||
help.p_tool = LB / RB – избор алата Select – ова помоћ
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Мост
|
||||
|
||||
@@ -440,6 +440,25 @@ help.t_long = Långt tryck: information om platsen; hand: släpp enheterna där
|
||||
help.t_double = Dubbeltryck med handen: örfila egna enheter, slå hjältar på din mark
|
||||
help.t_tools = Verktygsfält: kategorier öppnar en rad verktyg; menyknappen öppnar pausmenyn
|
||||
help.start_touch = Tryck för att börja
|
||||
# gamepad (webbläsarens Gamepad API)
|
||||
set.gamepad = Gamepad-knappar
|
||||
val.xbox = Xbox
|
||||
val.playstation = PlayStation
|
||||
val.nintendo = Nintendo
|
||||
pad.use = Använd
|
||||
pad.back = Tillbaka
|
||||
pad.tool = Verktyg
|
||||
pad.menu = Meny
|
||||
pad.camera = Kamera
|
||||
pad.zoom = Zoom
|
||||
help.p_move = Vänster spak – flytta kartan
|
||||
help.p_aim = Höger spak – flytta markören
|
||||
help.p_rot = Pilknappar – rotera och luta
|
||||
help.p_zoom = LT / RT – zooma in och ut
|
||||
help.p_use = A / kors – använd verktyget, bekräfta (håll: dra en rektangel)
|
||||
help.p_back = B / cirkel – tillbaka, pausmeny Start Meny
|
||||
help.p_tool = LB / RB – välj verktyg Select – denna hjälp
|
||||
|
||||
|
||||
# rivers and bridges
|
||||
tool.bridge.label = Bro
|
||||
|
||||
+212
-1
@@ -6,7 +6,7 @@ use macroquad::prelude::*;
|
||||
|
||||
pub const CELL: f32 = 16.0;
|
||||
pub const ATLAS_COLS: u32 = 16;
|
||||
pub const ATLAS_ROWS: u32 = 18; // rows 14-17: cells only the high-res atlases fill (see HD_SETS)
|
||||
pub const ATLAS_ROWS: u32 = 20; // rows 14-17: cells only the high-res atlases fill (see HD_SETS); rows 18-19: gamepad pictograms
|
||||
|
||||
// atlas indices (4 variants each)
|
||||
pub const T_ROCK: u32 = 0;
|
||||
@@ -98,6 +98,30 @@ pub const T_GIFT: u32 = 220; // a wrapped present (Christmas)
|
||||
pub const T_FIR: u32 = 221; // fir needles with baubles (Christmas)
|
||||
pub const T_EGG: u32 = 222; // 2 variants: a painted egg (Easter)
|
||||
|
||||
// gamepad pictograms (rows 18-19 of the atlas): face buttons of the three pad types, d-pad,
|
||||
// sticks, shoulders and the select/start buttons
|
||||
pub const I_PAD_XBOX_A: u32 = 288; // A, B, X, Y on coloured discs
|
||||
pub const I_PAD_XBOX_B: u32 = 289;
|
||||
pub const I_PAD_XBOX_X: u32 = 290;
|
||||
pub const I_PAD_XBOX_Y: u32 = 291;
|
||||
pub const I_PAD_PS_CROSS: u32 = 292; // cross, circle, square, triangle on dark discs
|
||||
pub const I_PAD_PS_CIRCLE: u32 = 293;
|
||||
pub const I_PAD_PS_SQUARE: u32 = 294;
|
||||
pub const I_PAD_PS_TRIANGLE: u32 = 295;
|
||||
pub const I_PAD_NIN_A: u32 = 296; // A, B, X, Y on light discs
|
||||
pub const I_PAD_NIN_B: u32 = 297;
|
||||
pub const I_PAD_NIN_X: u32 = 298;
|
||||
pub const I_PAD_NIN_Y: u32 = 299;
|
||||
pub const I_PAD_DPAD: u32 = 300;
|
||||
pub const I_PAD_STICK_L: u32 = 301;
|
||||
pub const I_PAD_STICK_R: u32 = 302;
|
||||
pub const I_PAD_LB: u32 = 303;
|
||||
pub const I_PAD_RB: u32 = 304;
|
||||
pub const I_PAD_LT: u32 = 305;
|
||||
pub const I_PAD_RT: u32 = 306;
|
||||
pub const I_PAD_START: u32 = 307;
|
||||
pub const I_PAD_SELECT: u32 = 308;
|
||||
|
||||
pub struct Gfx {
|
||||
pub atlas: Texture2D,
|
||||
pub sprites: Texture2D,
|
||||
@@ -335,6 +359,42 @@ fn earth_px(x: i32, y: i32, v: u32) -> Color {
|
||||
shade(base, (big + fine) as i32)
|
||||
}
|
||||
|
||||
// ---- gamepad pictograms (16x16 cells; see I_PAD_*)
|
||||
|
||||
// tiny 3x5 letters for the button pictograms
|
||||
fn pad_glyph(ch: char, x: i32, y: i32) -> bool {
|
||||
if !(0..3).contains(&x) || !(0..5).contains(&y) {
|
||||
return false;
|
||||
}
|
||||
let rows: [u8; 5] = match ch {
|
||||
'A' => [0b010, 0b101, 0b111, 0b101, 0b101],
|
||||
'B' => [0b110, 0b101, 0b110, 0b101, 0b110],
|
||||
'X' => [0b101, 0b101, 0b010, 0b101, 0b101],
|
||||
'Y' => [0b101, 0b101, 0b010, 0b010, 0b010],
|
||||
'L' => [0b100, 0b100, 0b100, 0b100, 0b111],
|
||||
'R' => [0b110, 0b101, 0b110, 0b101, 0b101],
|
||||
'T' => [0b111, 0b010, 0b010, 0b010, 0b010],
|
||||
_ => [0; 5],
|
||||
};
|
||||
rows[y as usize] >> (2 - x) & 1 == 1
|
||||
}
|
||||
|
||||
// a round button: rim, fill, a highlight top left
|
||||
fn pad_disc(x: i32, y: i32, fill: (i32, i32, i32)) -> Option<Color> {
|
||||
let (dx, dy) = (x - 8, y - 8);
|
||||
let d2 = dx * dx + dy * dy;
|
||||
if d2 > 30 {
|
||||
return None;
|
||||
}
|
||||
Some(if d2 >= 22 {
|
||||
shade((fill.0 / 2, fill.1 / 2, fill.2 / 2), (rnd(x, y, 5) * 4.0) as i32)
|
||||
} else if dx + dy <= -7 {
|
||||
shade(fill, 26)
|
||||
} else {
|
||||
c(fill.0, fill.1, fill.2)
|
||||
})
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
// the interface draws atlas cells from the high-res atlas (the high-res graphics style)
|
||||
static HIGH_RES_UI: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
|
||||
@@ -1787,6 +1847,129 @@ fn gen_atlas() -> Image {
|
||||
});
|
||||
cv.cell(D_RUBY, |x, y| gems(x, y, 200, c(255, 150, 170), c(215, 30, 60), c(120, 12, 36)));
|
||||
cv.cell(D_DIAMOND, |x, y| gems(x, y, 210, c(255, 255, 255), c(170, 232, 255), c(80, 150, 200)));
|
||||
// ---- gamepad pictograms
|
||||
// a face button with a letter (Xbox, Nintendo)
|
||||
let face = |ch: char, fill: (i32, i32, i32), dark: bool| {
|
||||
move |x: i32, y: i32| {
|
||||
let disc = pad_disc(x, y, fill)?;
|
||||
if pad_glyph(ch, x - 7, y - 6) {
|
||||
return Some(if dark { c(42, 44, 50) } else { c(250, 250, 250) });
|
||||
}
|
||||
Some(disc)
|
||||
}
|
||||
};
|
||||
cv.cell(I_PAD_XBOX_A, face('A', (74, 166, 86), false));
|
||||
cv.cell(I_PAD_XBOX_B, face('B', (196, 66, 58), false));
|
||||
cv.cell(I_PAD_XBOX_X, face('X', (64, 108, 204), false));
|
||||
cv.cell(I_PAD_XBOX_Y, face('Y', (214, 176, 64), false));
|
||||
cv.cell(I_PAD_NIN_A, face('A', (200, 202, 210), true));
|
||||
cv.cell(I_PAD_NIN_B, face('B', (200, 202, 210), true));
|
||||
cv.cell(I_PAD_NIN_X, face('X', (200, 202, 210), true));
|
||||
cv.cell(I_PAD_NIN_Y, face('Y', (200, 202, 210), true));
|
||||
// PlayStation: the shape symbols on dark discs
|
||||
let symbol = |sym: u8, col: Color| {
|
||||
move |x: i32, y: i32| {
|
||||
let disc = pad_disc(x, y, (58, 60, 68))?;
|
||||
let (dx, dy) = (x - 8, y - 8);
|
||||
let on = match sym {
|
||||
0 => dx.abs() <= 3 && dy.abs() <= 3 && (dx == dy || dx == -dy),
|
||||
1 => {
|
||||
let d = ((dx * dx + dy * dy) as f32).sqrt();
|
||||
d >= 2.6 && d <= 4.1
|
||||
}
|
||||
2 => dx.abs() <= 3 && dy.abs() <= 3 && (dx.abs() == 3 || dy.abs() == 3),
|
||||
_ => dy >= -3 && dy <= 3 && (dx.abs() == (dy + 3) / 2 || (dy == 3 && dx.abs() <= 3)),
|
||||
};
|
||||
Some(if on { col } else { disc })
|
||||
}
|
||||
};
|
||||
cv.cell(I_PAD_PS_CROSS, symbol(0, c(96, 154, 255)));
|
||||
cv.cell(I_PAD_PS_CIRCLE, symbol(1, c(240, 96, 96)));
|
||||
cv.cell(I_PAD_PS_SQUARE, symbol(2, c(238, 130, 234)));
|
||||
cv.cell(I_PAD_PS_TRIANGLE, symbol(3, c(100, 214, 152)));
|
||||
// d-pad: a cross with a dark centre
|
||||
cv.cell(I_PAD_DPAD, |x, y| {
|
||||
let arm = (5..=10).contains(&x) || (5..=10).contains(&y);
|
||||
if !arm || !(1..=14).contains(&x) || !(1..=14).contains(&y) {
|
||||
return None;
|
||||
}
|
||||
let mid = (x - 8).abs() <= 1 && (y - 8).abs() <= 1;
|
||||
let edge = x == 1 || y == 1 || x == 14 || y == 14;
|
||||
Some(if mid { c(70, 72, 80) } else if edge { c(116, 118, 128) } else { c(172, 174, 186) })
|
||||
});
|
||||
// sticks: a ring with a knob, L/R in the corner
|
||||
let stick = |side: char| {
|
||||
move |x: i32, y: i32| {
|
||||
let (dx, dy) = (x - 8, y - 8);
|
||||
let d = ((dx * dx + dy * dy) as f32).sqrt();
|
||||
if d <= 6.0 {
|
||||
return Some(if d >= 4.6 {
|
||||
c(148, 150, 162)
|
||||
} else if d <= 2.8 {
|
||||
c(208, 210, 220)
|
||||
} else {
|
||||
c(88, 90, 102)
|
||||
});
|
||||
}
|
||||
if pad_glyph(side, x - 11, y - 10) {
|
||||
return Some(c(190, 192, 202));
|
||||
}
|
||||
None
|
||||
}
|
||||
};
|
||||
cv.cell(I_PAD_STICK_L, stick('L'));
|
||||
cv.cell(I_PAD_STICK_R, stick('R'));
|
||||
// shoulders (LB/RB) and triggers (LT/RT): a rounded body with its letters
|
||||
let bumper = |a: char, b: char, tall: bool| {
|
||||
move |x: i32, y: i32| {
|
||||
let (y0, y1) = if tall { (2, 10) } else { (4, 11) };
|
||||
let corner = (x == 3 || x == 12) && (y == y0 || y == y1);
|
||||
if !(3..=12).contains(&x) || !(y0..=y1).contains(&y) || corner {
|
||||
return None;
|
||||
}
|
||||
if pad_glyph(a, x - 4, y - (y0 + 1)) || pad_glyph(b, x - 8, y - (y0 + 1)) {
|
||||
return Some(c(52, 54, 62));
|
||||
}
|
||||
Some(if x == 3 || x == 12 || y == y0 {
|
||||
c(128, 130, 140)
|
||||
} else if y == y1 {
|
||||
c(94, 96, 106)
|
||||
} else {
|
||||
c(190, 192, 202)
|
||||
})
|
||||
}
|
||||
};
|
||||
cv.cell(I_PAD_LB, bumper('L', 'B', false));
|
||||
cv.cell(I_PAD_RB, bumper('R', 'B', false));
|
||||
cv.cell(I_PAD_LT, bumper('L', 'T', true));
|
||||
cv.cell(I_PAD_RT, bumper('R', 'T', true));
|
||||
// start: two bars (pause); select: two lines (list)
|
||||
cv.cell(I_PAD_START, |x, y| {
|
||||
if !(3..=12).contains(&x) || !(4..=11).contains(&y) {
|
||||
return None;
|
||||
}
|
||||
let bars = (x == 5 || x == 6 || x == 9 || x == 10) && (6..=9).contains(&y);
|
||||
Some(if bars {
|
||||
c(232, 234, 242)
|
||||
} else if x == 3 || y == 4 || x == 12 || y == 11 {
|
||||
c(68, 70, 80)
|
||||
} else {
|
||||
c(118, 120, 132)
|
||||
})
|
||||
});
|
||||
cv.cell(I_PAD_SELECT, |x, y| {
|
||||
if !(2..=13).contains(&x) || !(4..=11).contains(&y) {
|
||||
return None;
|
||||
}
|
||||
let lines = (5..=10).contains(&x) && (y == 6 || y == 9);
|
||||
Some(if lines {
|
||||
c(232, 234, 242)
|
||||
} else if x == 2 || y == 4 || x == 13 || y == 11 {
|
||||
c(68, 70, 80)
|
||||
} else {
|
||||
c(118, 120, 132)
|
||||
})
|
||||
});
|
||||
cv.img
|
||||
}
|
||||
|
||||
@@ -3563,6 +3746,34 @@ mod atlas_dump {
|
||||
}
|
||||
out.export_png("/tmp/atlases.png");
|
||||
}
|
||||
|
||||
// `cargo test --bins dump_pad_icons -- --ignored`: the gamepad pictograms, 6x scaled, in
|
||||
// /tmp/pad-icons.png (Xbox, PlayStation, Nintendo, then d-pad, sticks and co.)
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn dump_pad_icons() {
|
||||
let atlas = gen_atlas();
|
||||
let ids: [u32; 21] = [
|
||||
I_PAD_XBOX_A, I_PAD_XBOX_B, I_PAD_XBOX_X, I_PAD_XBOX_Y,
|
||||
I_PAD_PS_CROSS, I_PAD_PS_CIRCLE, I_PAD_PS_SQUARE, I_PAD_PS_TRIANGLE,
|
||||
I_PAD_NIN_A, I_PAD_NIN_B, I_PAD_NIN_X, I_PAD_NIN_Y,
|
||||
I_PAD_DPAD, I_PAD_STICK_L, I_PAD_STICK_R, I_PAD_LB, I_PAD_RB, I_PAD_LT, I_PAD_RT,
|
||||
I_PAD_START, I_PAD_SELECT,
|
||||
];
|
||||
let k = 6;
|
||||
let cols = 7;
|
||||
let rows = (ids.len() as u32).div_ceil(cols);
|
||||
let mut out = Image::gen_image_color((cols * 16 * k) as u16, (rows * 16 * k) as u16, BLACK);
|
||||
for (i, &id) in ids.iter().enumerate() {
|
||||
let (ox, oy) = ((id % ATLAS_COLS) * 16, (id / ATLAS_COLS) * 16);
|
||||
for y in 0..16 * k {
|
||||
for x in 0..16 * k {
|
||||
out.set_pixel((i as u32 % cols) * 16 * k + x, (i as u32 / cols) * 16 * k + y, atlas.get_pixel(ox + x / k, oy + y / k));
|
||||
}
|
||||
}
|
||||
}
|
||||
out.export_png("/tmp/pad-icons.png");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+70
-16
@@ -30,6 +30,7 @@ mod personality;
|
||||
mod platform;
|
||||
mod portraits;
|
||||
mod orders;
|
||||
mod pad;
|
||||
mod particles;
|
||||
mod props;
|
||||
mod research;
|
||||
@@ -933,6 +934,7 @@ async fn main() {
|
||||
apply_game_settings(&mut app.game, &app.settings);
|
||||
let mut inp = Input { ldown: None, rdown: None, rlast: (0.0, 0.0), rdrag: false, mlast: None, mclick: -1.0, minimap_drag: false, tlast: None, touch_world: false, inspect: None };
|
||||
let mut touch = TouchInput::new();
|
||||
let mut pad = pad::Pad::new();
|
||||
let mut auto_touch = platform::coarse_pointer();
|
||||
let mut menu_swipe: Option<(f32, f32)> = None; // a swipe over a menu: last finger y, unused distance
|
||||
let mut menu_dragged = false; // the finger on a menu has swiped (no click when it lifts)
|
||||
@@ -1024,8 +1026,19 @@ async fn main() {
|
||||
let p = touch.finger().or(gpos).unwrap_or(vec2(-1e5, -1e5));
|
||||
ui::set_pointer(p.x, p.y);
|
||||
}
|
||||
// ---------------- gamepad: the browser's Gamepad API (the desktop build has none)
|
||||
pad.poll(app.settings.gamepad);
|
||||
ui::set_gamepad(pad.connected());
|
||||
// the pad's pointer starts where the mouse is and moves with the right stick; a mouse
|
||||
// movement takes it over again, so both can be used side by side (a finger still wins)
|
||||
if pad.connected() && !(touch_mode && touch.finger().is_some()) {
|
||||
let m = mouse_position();
|
||||
let moved = mouse_delta_position() != Vec2::ZERO;
|
||||
let (px, py) = pad.aim(dt, ui::sw(), ui::sh(), (m.0 * ui::dpr(), m.1 * ui::dpr()), moved);
|
||||
ui::set_pointer(px, py);
|
||||
}
|
||||
let (ux, uy) = ui::mouse(); // device pixels (interface)
|
||||
let (mx, my) = if touch_mode { (ux / ui::dpr(), uy / ui::dpr()) } else { mouse_position() }; // CSS pixels (3D picking)
|
||||
let (mx, my) = if touch_mode || ui::gamepad_ui() { (ux / ui::dpr(), uy / ui::dpr()) } else { mouse_position() }; // CSS pixels (3D picking)
|
||||
|
||||
// ---------------- savegame files: file picker result or dropped onto the window
|
||||
let mut incoming: Option<Vec<u8>> = platform::take_file();
|
||||
@@ -1242,8 +1255,8 @@ async fn main() {
|
||||
let keys = app.settings.clone();
|
||||
let key = |a: Action| keys.key(a);
|
||||
|
||||
// ---------------- Esc: chat, dialogs, menus, pause menu
|
||||
if is_key_pressed(KeyCode::Escape) {
|
||||
// ---------------- Esc (or the pad's B / Start): chat, dialogs, menus, pause menu
|
||||
if is_key_pressed(KeyCode::Escape) || pad.pressed(pad::Btn::East) || pad.pressed(pad::Btn::Start) {
|
||||
if ui.chat_open {
|
||||
ui.chat_open = false;
|
||||
ui.chat_text.clear();
|
||||
@@ -1293,9 +1306,28 @@ async fn main() {
|
||||
inp.ldown = None;
|
||||
}
|
||||
}
|
||||
if is_key_pressed(key(Action::Help)) {
|
||||
if is_key_pressed(key(Action::Help)) || pad.pressed(pad::Btn::Select) {
|
||||
ui.show_help = true;
|
||||
}
|
||||
// the pad cycles the tools: LB back, RB forward
|
||||
let tool_step = if pad.pressed(pad::Btn::Rb) {
|
||||
1
|
||||
} else if pad.pressed(pad::Btn::Lb) {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if tool_step != 0 {
|
||||
let cur = TOOLS.iter().position(|&t| t == ui.tool).unwrap_or(0) as i32;
|
||||
for k in 1..=TOOLS.len() as i32 {
|
||||
let i = (cur + tool_step * k).rem_euclid(TOOLS.len() as i32) as usize;
|
||||
if app.game.tool_allowed(i) && app.game.tool_visible(i) {
|
||||
ui.tool = TOOLS[i];
|
||||
inp.ldown = None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_key_pressed(key(Action::Pause)) && app.game.state == 0 {
|
||||
app.game.paused = !app.game.paused;
|
||||
}
|
||||
@@ -1346,11 +1378,19 @@ async fn main() {
|
||||
if is_key_down(key(Action::CamRight)) || is_key_down(KeyCode::Right) {
|
||||
move_cam(pan, 0.0, cam);
|
||||
}
|
||||
// the pad's left stick moves the map, as W/A/S/D do
|
||||
if pad.connected() {
|
||||
let s = pad.stick(pad::Stick::Left);
|
||||
if s != Vec2::ZERO {
|
||||
let k = pan * 1.8;
|
||||
move_cam(s.x * k, -s.y * k, cam);
|
||||
}
|
||||
}
|
||||
let edge = 6.0;
|
||||
if !mouse_moved && mouse_delta_position() != Vec2::ZERO {
|
||||
mouse_moved = true;
|
||||
}
|
||||
if keys.edge_scroll && mouse_moved && !touch_mode && mx >= 0.0 && my >= 0.0 && mx < screen_width() && my < screen_height() {
|
||||
if keys.edge_scroll && mouse_moved && !touch_mode && !ui::gamepad_ui() && mx >= 0.0 && my >= 0.0 && mx < screen_width() && my < screen_height() {
|
||||
if mx < edge {
|
||||
move_cam(-pan, 0.0, cam);
|
||||
} else if mx > screen_width() - edge {
|
||||
@@ -1363,18 +1403,22 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
let rot = 1.8 * dt * keys.rotate_speed;
|
||||
if is_key_down(key(Action::RotateLeft)) {
|
||||
if is_key_down(key(Action::RotateLeft)) || pad.down(pad::Btn::Left) {
|
||||
cam.yaw += rot;
|
||||
}
|
||||
if is_key_down(key(Action::RotateRight)) {
|
||||
if is_key_down(key(Action::RotateRight)) || pad.down(pad::Btn::Right) {
|
||||
cam.yaw -= rot;
|
||||
}
|
||||
if is_key_down(key(Action::TiltUp)) {
|
||||
if is_key_down(key(Action::TiltUp)) || pad.down(pad::Btn::Up) {
|
||||
cam.pitch += rot * 0.6;
|
||||
}
|
||||
if is_key_down(key(Action::TiltDown)) {
|
||||
if is_key_down(key(Action::TiltDown)) || pad.down(pad::Btn::Down) {
|
||||
cam.pitch -= rot * 0.6;
|
||||
}
|
||||
// the pad's left stick click resets the view
|
||||
if pad.pressed(pad::Btn::LStick) {
|
||||
cam.reset_view();
|
||||
}
|
||||
// a double click with the middle button: the standard angle and turn (same place)
|
||||
if is_mouse_button_pressed(MouseButton::Middle) && !touch_mode {
|
||||
let now = get_time();
|
||||
@@ -1405,6 +1449,15 @@ async fn main() {
|
||||
cam.tx += before.x - after.x;
|
||||
cam.tz += before.y - after.y;
|
||||
}
|
||||
// the pad's triggers zoom (RT in, LT out)
|
||||
let zoom_in = if pad.down(pad::Btn::Rt) { 1.0 } else { 0.0 } - if pad.down(pad::Btn::Lt) { 1.0 } else { 0.0 };
|
||||
if zoom_in != 0.0 && !over_ui {
|
||||
let before = cam.ground_point(mx, my);
|
||||
cam.zoom = (cam.zoom * (1.0 - zoom_in * 1.4 * dt * keys.zoom_speed)).clamp(6.0, 45.0);
|
||||
let after = cam.ground_point(mx, my);
|
||||
cam.tx += before.x - after.x;
|
||||
cam.tz += before.y - after.y;
|
||||
}
|
||||
// touch: two fingers move, zoom and rotate the camera
|
||||
for g in &gestures {
|
||||
if let Gesture::Camera { center, pan, zoom, rotate } = *g {
|
||||
@@ -1421,8 +1474,8 @@ async fn main() {
|
||||
cam.tz += before.y - after.y;
|
||||
}
|
||||
}
|
||||
// right button: drag = pan, click = drop
|
||||
if is_mouse_button_pressed(MouseButton::Right) {
|
||||
// right button (or the pad's X / square): drag = pan, click = drop
|
||||
if is_mouse_button_pressed(MouseButton::Right) || pad.pressed(pad::Btn::West) {
|
||||
inp.rdown = Some((mx, my));
|
||||
inp.rlast = (mx, my);
|
||||
inp.rdrag = false;
|
||||
@@ -1441,7 +1494,7 @@ async fn main() {
|
||||
inp.rlast = (mx, my);
|
||||
}
|
||||
}
|
||||
if is_mouse_button_released(MouseButton::Right) {
|
||||
if is_mouse_button_released(MouseButton::Right) || pad.released(pad::Btn::West) {
|
||||
if !inp.rdrag && !over_ui && app.game.state == 0 {
|
||||
if inp.ldown.is_some() {
|
||||
inp.ldown = None;
|
||||
@@ -1459,13 +1512,13 @@ async fn main() {
|
||||
// (in menus and dialogs a touch clicks when the finger lifts without a swipe, so that
|
||||
// swiping a list scrolls it instead of picking the entry under the finger)
|
||||
let menu_touch = touch_mode && (in_menu || dialog);
|
||||
let pressed = if menu_touch {
|
||||
let pressed = (if menu_touch {
|
||||
!menu_dragged && gestures.iter().any(|g| matches!(g, Gesture::Release(_)))
|
||||
} else if touch_mode {
|
||||
gestures.iter().any(|g| matches!(g, Gesture::Press(_)))
|
||||
} else {
|
||||
is_mouse_button_pressed(MouseButton::Left)
|
||||
};
|
||||
}) || pad.pressed(pad::Btn::South);
|
||||
if touch_mode && pressed {
|
||||
inp.touch_world = playing && !over_ui;
|
||||
inp.inspect = None;
|
||||
@@ -1916,7 +1969,7 @@ app.pending_briefing = briefing_of(&campaign, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
let held = if touch_mode { touch.finger().is_some() } else { is_mouse_button_down(MouseButton::Left) };
|
||||
let held = (if touch_mode { touch.finger().is_some() } else { is_mouse_button_down(MouseButton::Left) }) || pad.down(pad::Btn::South);
|
||||
if inp.minimap_drag {
|
||||
if held && ui.minimap_contains(ux, uy) {
|
||||
let (x, y) = ui.minimap_world(ux, uy);
|
||||
@@ -1976,7 +2029,8 @@ app.pending_briefing = briefing_of(&campaign, i);
|
||||
inp.inspect = None;
|
||||
}
|
||||
}
|
||||
if !touch_mode && is_mouse_button_released(MouseButton::Left) {
|
||||
let released = (if touch_mode { false } else { is_mouse_button_released(MouseButton::Left) }) || pad.released(pad::Btn::South);
|
||||
if released {
|
||||
if let Some(start) = inp.ldown.take() {
|
||||
if playing {
|
||||
finish_rect(&mut app, ui.tool, start, mx, my);
|
||||
|
||||
+21
-1
@@ -67,6 +67,7 @@ pub enum Item {
|
||||
Hints,
|
||||
Events,
|
||||
TouchUi,
|
||||
Gamepad,
|
||||
Voice,
|
||||
Track,
|
||||
}
|
||||
@@ -77,7 +78,7 @@ fn tab_items(tab: usize) -> &'static [Item] {
|
||||
0 => &[Language, Volume, MusicVolume, Track, Mute, Voice],
|
||||
1 => &[RotateSpeed, PanSpeed, ZoomSpeed, EdgeScroll, InvertMouse],
|
||||
2 => &[Outlines, WallDetail, Lights, UiScale, RenderScale],
|
||||
3 => &[GameSpeed, Hints, Events, TouchUi],
|
||||
3 => &[GameSpeed, Hints, Events, TouchUi, Gamepad],
|
||||
4 => &[DebugFps, DebugFrameTime, DebugPing, DebugTriggerZones, DebugCommandMarkers, DebugPathLines, DebugHoliday],
|
||||
_ => &[],
|
||||
}
|
||||
@@ -110,6 +111,7 @@ fn item_label(it: Item) -> String {
|
||||
Item::Hints => "set.hints",
|
||||
Item::Events => "set.events",
|
||||
Item::TouchUi => "set.touch_ui",
|
||||
Item::Gamepad => "set.gamepad",
|
||||
Item::Voice => "set.voice",
|
||||
Item::Track => "set.track",
|
||||
})
|
||||
@@ -169,6 +171,12 @@ fn item_value(it: Item, s: &Settings) -> String {
|
||||
2 => tr("val.off"),
|
||||
_ => trf("val.auto_state", &[tr(if crate::ui::touch_ui() { "val.on" } else { "val.off" })]),
|
||||
},
|
||||
Item::Gamepad => tr(match s.gamepad {
|
||||
1 => "val.xbox",
|
||||
2 => "val.playstation",
|
||||
3 => "val.nintendo",
|
||||
_ => "val.auto_plain",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +229,7 @@ fn item_change(it: Item, s: &mut Settings, d: i32) {
|
||||
Item::Hints => s.hints = !s.hints,
|
||||
Item::Events => s.events = !s.events,
|
||||
Item::TouchUi => s.touch_ui = cycle(&[0u8, 1, 2], s.touch_ui, d),
|
||||
Item::Gamepad => s.gamepad = cycle(&[0u8, 1, 2, 3], s.gamepad, d),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1514,6 +1523,17 @@ impl Menu {
|
||||
// value with < > buttons
|
||||
let vw = 18.0 * CW();
|
||||
let vx = x + cols as f32 * CW() - vw - CW() * 3.0;
|
||||
if it == Item::Gamepad {
|
||||
// the type's face buttons, right before the value: the pictograms the game uses
|
||||
let kind = crate::pad::kind();
|
||||
let isz = 2.0 * CH();
|
||||
let gap = CW();
|
||||
let mut ix = vx - CW() - 4.0 * isz - 3.0 * gap;
|
||||
for b in [crate::pad::Btn::North, crate::pad::Btn::West, crate::pad::Btn::East, crate::pad::Btn::South] {
|
||||
gfx.draw_cell(crate::ui::pad_icon(kind, b), ix, by, isz, isz, WHITE);
|
||||
ix += isz + gap;
|
||||
}
|
||||
}
|
||||
if it == Item::Language {
|
||||
self.lang_field(gfx, vx, by, vw);
|
||||
continue;
|
||||
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
// Gamepads via the browser's Gamepad API (see www/dc_plugin.js): polling, buttons, sticks and
|
||||
// the virtual pointer. Native builds have no gamepad input (miniquad does not poll pads), so
|
||||
// platform's stubs report "no gamepad" there.
|
||||
//
|
||||
// The standard Gamepad API mapping: buttons 0-3 = face buttons, 4/5 = shoulders, 6/7 = triggers,
|
||||
// 8/9 = select/start, 10/11 = stick clicks, 12-15 = d-pad; axes 0/1 = left stick, 2/3 = right.
|
||||
|
||||
use macroquad::prelude::Vec2;
|
||||
use std::cell::Cell;
|
||||
|
||||
// the standard Gamepad API button order; every variant keeps its index (RStick is 11), even the
|
||||
// ones no action uses yet
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Btn {
|
||||
South, // A (Xbox, Nintendo), cross (PlayStation)
|
||||
East, // B (Xbox), circle (PlayStation), A (Nintendo)
|
||||
West, // X (Xbox), square (PlayStation), Y (Nintendo)
|
||||
North, // Y (Xbox), triangle (PlayStation), X (Nintendo)
|
||||
Lb,
|
||||
Rb,
|
||||
Lt,
|
||||
Rt,
|
||||
Select,
|
||||
Start,
|
||||
LStick,
|
||||
RStick, // kept for the standard button index (11); no action uses it yet
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Btn {
|
||||
fn bit(self) -> u32 {
|
||||
1 << self as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Stick {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
// The button pictograms the interface shows (Xbox, PlayStation, Nintendo).
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Kind {
|
||||
Xbox,
|
||||
PlayStation,
|
||||
Nintendo,
|
||||
}
|
||||
|
||||
// Settings value: 0 automatic, 1 Xbox, 2 PlayStation, 3 Nintendo.
|
||||
pub fn kind_for(setting: u8, detected: i32) -> Kind {
|
||||
match setting {
|
||||
1 => Kind::Xbox,
|
||||
2 => Kind::PlayStation,
|
||||
3 => Kind::Nintendo,
|
||||
_ => match detected {
|
||||
2 => Kind::PlayStation,
|
||||
3 => Kind::Nintendo,
|
||||
_ => Kind::Xbox,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static KIND: Cell<Kind> = const { Cell::new(Kind::Xbox) };
|
||||
}
|
||||
|
||||
// The kind whose pictograms the interface draws: the setting, or the automatic choice.
|
||||
pub fn kind() -> Kind {
|
||||
KIND.with(|c| c.get())
|
||||
}
|
||||
|
||||
// Below this deflection a stick rests (controllers do not return to exactly 0).
|
||||
const DEAD: f32 = 0.18;
|
||||
|
||||
pub struct Pad {
|
||||
connected: bool,
|
||||
buttons: u32,
|
||||
prev: u32,
|
||||
axes: [f32; 4],
|
||||
pointer: (f32, f32), // device pixels of the interface
|
||||
pointer_ready: bool,
|
||||
}
|
||||
|
||||
impl Pad {
|
||||
pub fn new() -> Pad {
|
||||
Pad { connected: false, buttons: 0, prev: 0, axes: [0.0; 4], pointer: (0.0, 0.0), pointer_ready: false }
|
||||
}
|
||||
|
||||
// Read the pad for this frame; kind_setting = Settings::gamepad.
|
||||
pub fn poll(&mut self, kind_setting: u8) {
|
||||
self.prev = if self.connected { self.buttons } else { 0 };
|
||||
let slot = crate::platform::gamepad_slot();
|
||||
self.connected = slot >= 0;
|
||||
let detected = if self.connected { crate::platform::gamepad_kind(slot) } else { 0 };
|
||||
KIND.with(|c| c.set(kind_for(kind_setting, detected)));
|
||||
if !self.connected {
|
||||
self.buttons = 0;
|
||||
self.axes = [0.0; 4];
|
||||
return;
|
||||
}
|
||||
self.buttons = crate::platform::gamepad_buttons(slot);
|
||||
for (i, a) in self.axes.iter_mut().enumerate() {
|
||||
*a = crate::platform::gamepad_axis(slot, i as i32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connected(&self) -> bool {
|
||||
self.connected
|
||||
}
|
||||
|
||||
pub fn down(&self, b: Btn) -> bool {
|
||||
self.connected && self.buttons & b.bit() != 0
|
||||
}
|
||||
|
||||
pub fn pressed(&self, b: Btn) -> bool {
|
||||
self.connected && self.buttons & b.bit() != 0 && self.prev & b.bit() == 0
|
||||
}
|
||||
|
||||
pub fn released(&self, b: Btn) -> bool {
|
||||
self.connected && self.buttons & b.bit() == 0 && self.prev & b.bit() != 0
|
||||
}
|
||||
|
||||
// stick deflection, dead zone removed and scaled back to 0..1
|
||||
pub fn stick(&self, side: Stick) -> Vec2 {
|
||||
let (i, j) = match side {
|
||||
Stick::Left => (0, 1),
|
||||
Stick::Right => (2, 3),
|
||||
};
|
||||
let v = Vec2::new(self.axes[i], self.axes[j]);
|
||||
let l = v.length();
|
||||
if l <= DEAD {
|
||||
Vec2::ZERO
|
||||
} else {
|
||||
v / l * ((l - DEAD) / (1.0 - DEAD))
|
||||
}
|
||||
}
|
||||
|
||||
// Move the interface pointer with the right stick. Mouse movements take it over at once, so
|
||||
// mouse and pad can be used side by side; (w, h) is the interface size in device pixels.
|
||||
// Returns the pointer in device pixels.
|
||||
pub fn aim(&mut self, dt: f32, w: f32, h: f32, mouse: (f32, f32), mouse_moved: bool) -> (f32, f32) {
|
||||
let w = w.max(1.0);
|
||||
let h = h.max(1.0);
|
||||
if !self.pointer_ready || mouse_moved {
|
||||
self.pointer = (mouse.0.clamp(0.0, w), mouse.1.clamp(0.0, h));
|
||||
self.pointer_ready = true;
|
||||
}
|
||||
let s = self.stick(Stick::Right);
|
||||
let speed = w.max(640.0) * 1.15; // a full deflection crosses the screen in about a second
|
||||
self.pointer.0 = (self.pointer.0 + s.x * speed * dt).clamp(0.0, w);
|
||||
self.pointer.1 = (self.pointer.1 + s.y * speed * dt).clamp(0.0, h);
|
||||
self.pointer
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_setting_picks_the_pictograms() {
|
||||
assert_eq!(kind_for(0, 1), Kind::Xbox);
|
||||
assert_eq!(kind_for(0, 2), Kind::PlayStation);
|
||||
assert_eq!(kind_for(0, 3), Kind::Nintendo);
|
||||
assert_eq!(kind_for(0, 0), Kind::Xbox);
|
||||
assert_eq!(kind_for(2, 1), Kind::PlayStation);
|
||||
assert_eq!(kind_for(3, 1), Kind::Nintendo);
|
||||
assert_eq!(kind_for(1, 2), Kind::Xbox);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sticks_have_a_dead_zone() {
|
||||
let mut p = Pad::new();
|
||||
p.connected = true;
|
||||
p.axes = [0.1, -0.12, 0.0, 0.0];
|
||||
assert_eq!(p.stick(Stick::Left), Vec2::ZERO);
|
||||
p.axes = [1.0, 0.0, 0.0, 0.0];
|
||||
let s = p.stick(Stick::Left);
|
||||
assert!((s.x - 1.0).abs() < 0.001 && s.y.abs() < 0.001);
|
||||
p.axes = [0.0, -0.59, 0.0, 0.0];
|
||||
let s = p.stick(Stick::Left);
|
||||
assert!((s.y + 0.5).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buttons_report_edges() {
|
||||
let mut p = Pad::new();
|
||||
p.connected = true;
|
||||
p.buttons = 0;
|
||||
p.prev = 0;
|
||||
assert!(!p.down(Btn::South));
|
||||
p.buttons = Btn::South.bit();
|
||||
assert!(p.pressed(Btn::South) && p.down(Btn::South) && !p.released(Btn::South));
|
||||
p.prev = p.buttons;
|
||||
assert!(!p.pressed(Btn::South) && p.down(Btn::South));
|
||||
p.buttons = 0;
|
||||
p.prev = Btn::South.bit();
|
||||
assert!(!p.down(Btn::South) && p.released(Btn::South));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_disconnected_pad_stays_silent() {
|
||||
let mut p = Pad::new();
|
||||
p.buttons = Btn::South.bit();
|
||||
p.prev = 0;
|
||||
assert!(!p.down(Btn::South));
|
||||
assert!(!p.pressed(Btn::South));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_pointer_follows_the_mouse_and_the_stick() {
|
||||
let mut p = Pad::new();
|
||||
p.connected = true;
|
||||
// the first frame takes the mouse position
|
||||
assert_eq!(p.aim(0.016, 1000.0, 600.0, (100.0, 200.0), false), (100.0, 200.0));
|
||||
// the right stick moves it
|
||||
p.axes = [0.0, 0.0, 1.0, 0.0];
|
||||
let (x, _) = p.aim(0.1, 1000.0, 600.0, (0.0, 0.0), false);
|
||||
assert!(x > 200.0);
|
||||
// a mouse movement takes it over again (a resting stick moves nothing)
|
||||
p.axes = [0.0, 0.0, 0.0, 0.0];
|
||||
assert_eq!(p.aim(0.1, 1000.0, 600.0, (5.0, 5.0), true), (5.0, 5.0));
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,10 @@ mod imp {
|
||||
fn dc_ws_take(id: i32) -> i32;
|
||||
fn dc_ws_close(id: i32);
|
||||
fn dc_renderer() -> i32;
|
||||
fn dc_gamepad_slot() -> i32;
|
||||
fn dc_gamepad_buttons(slot: i32) -> u32;
|
||||
fn dc_gamepad_axis(slot: i32, axis: i32) -> f32;
|
||||
fn dc_gamepad_kind(slot: i32) -> i32;
|
||||
}
|
||||
|
||||
// starts a request; poll the returned id with http_poll()
|
||||
@@ -189,6 +193,25 @@ mod imp {
|
||||
String::from_utf8(buf).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
// the first connected gamepad (-1: none), see www/dc_plugin.js
|
||||
pub fn gamepad_slot() -> i32 {
|
||||
unsafe { dc_gamepad_slot() }
|
||||
}
|
||||
|
||||
// bit i: standard button i is down
|
||||
pub fn gamepad_buttons(slot: i32) -> u32 {
|
||||
unsafe { dc_gamepad_buttons(slot) }
|
||||
}
|
||||
|
||||
pub fn gamepad_axis(slot: i32, axis: i32) -> f32 {
|
||||
unsafe { dc_gamepad_axis(slot, axis) }
|
||||
}
|
||||
|
||||
// 0 unknown, 1 Xbox, 2 PlayStation, 3 Nintendo
|
||||
pub fn gamepad_kind(slot: i32) -> i32 {
|
||||
unsafe { dc_gamepad_kind(slot) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -364,6 +387,21 @@ mod imp {
|
||||
return "OpenGL".into();
|
||||
}
|
||||
}
|
||||
|
||||
// The Gamepad API only exists in browsers. The desktop build has no gamepad input yet
|
||||
// (miniquad does not poll gamepads; that would need a crate like gilrs).
|
||||
pub fn gamepad_slot() -> i32 {
|
||||
-1
|
||||
}
|
||||
pub fn gamepad_buttons(_slot: i32) -> u32 {
|
||||
0
|
||||
}
|
||||
pub fn gamepad_axis(_slot: i32, _axis: i32) -> f32 {
|
||||
0.0
|
||||
}
|
||||
pub fn gamepad_kind(_slot: i32) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub use imp::*;
|
||||
|
||||
@@ -153,6 +153,7 @@ pub struct Settings {
|
||||
pub render_scale: u8, // old pixel-look divisor (no longer used, kept for old settings files)
|
||||
pub pixel_rows: u16, // graphics style: rows of the world on every screen (270 pixel art, 1080 high-res)
|
||||
pub touch_ui: u8, // touch interface: 0 = automatic (after the first touch), 1 = on, 2 = off
|
||||
pub gamepad: u8, // gamepad pictograms: 0 automatic (from the pad's id), 1 Xbox, 2 PlayStation, 3 Nintendo
|
||||
pub debug_fps: bool, // show FPS and rendering backend
|
||||
pub debug_frame_time: bool, // show the frame-time line and graph
|
||||
pub debug_ping: bool, // show network ping
|
||||
@@ -195,6 +196,7 @@ impl Default for Settings {
|
||||
render_scale: 1,
|
||||
pixel_rows: PIXEL_ART_ROWS,
|
||||
touch_ui: 0,
|
||||
gamepad: 0,
|
||||
debug_fps: false,
|
||||
debug_frame_time: false,
|
||||
debug_ping: false,
|
||||
|
||||
@@ -19,6 +19,7 @@ use macroquad::prelude::*;
|
||||
// setting; automatic from the resolution in device pixels).
|
||||
thread_local! {
|
||||
static TOUCH: std::cell::Cell<bool> = std::cell::Cell::new(false);
|
||||
static GAMEPAD: std::cell::Cell<bool> = std::cell::Cell::new(false);
|
||||
static POINTER: std::cell::Cell<(f32, f32)> = std::cell::Cell::new((-1e5, -1e5));
|
||||
static VIRTUAL_ROWS: std::cell::Cell<u32> = std::cell::Cell::new(0);
|
||||
static UI_TARGET: std::cell::RefCell<Option<(RenderTarget, u32, u32)>> = std::cell::RefCell::new(None);
|
||||
@@ -50,6 +51,13 @@ pub fn set_touch(on: bool) {
|
||||
pub fn touch_ui() -> bool {
|
||||
TOUCH.with(|c| c.get())
|
||||
}
|
||||
// a gamepad is connected: the interface shows its button pictograms (see pad.rs)
|
||||
pub fn set_gamepad(on: bool) {
|
||||
GAMEPAD.with(|c| c.set(on));
|
||||
}
|
||||
pub fn gamepad_ui() -> bool {
|
||||
GAMEPAD.with(|c| c.get())
|
||||
}
|
||||
// in touch mode the pointer is the finger (device pixels), set by the input code each frame
|
||||
pub fn set_pointer(x: f32, y: f32) {
|
||||
POINTER.with(|c| c.set((x, y)));
|
||||
@@ -70,7 +78,7 @@ pub fn sh() -> f32 {
|
||||
(screen_height() * dpr()).round()
|
||||
}
|
||||
pub fn mouse() -> (f32, f32) {
|
||||
if touch_ui() {
|
||||
if touch_ui() || gamepad_ui() {
|
||||
return POINTER.with(|c| c.get());
|
||||
}
|
||||
let (x, y) = mouse_position();
|
||||
@@ -443,6 +451,35 @@ pub const CATS: [(&str, &[usize]); 7] = [
|
||||
("cat.orders", &[8, 45, 46, 7]),
|
||||
];
|
||||
|
||||
// The atlas cell of a gamepad button's pictogram, in the pad type's own symbols: Xbox A/B/X/Y,
|
||||
// PlayStation cross/circle/square/triangle, Nintendo A/B/X/Y.
|
||||
pub fn pad_icon(kind: crate::pad::Kind, b: crate::pad::Btn) -> u32 {
|
||||
use crate::pad::{Btn, Kind};
|
||||
match (kind, b) {
|
||||
(Kind::Xbox, Btn::South) => I_PAD_XBOX_A,
|
||||
(Kind::Xbox, Btn::East) => I_PAD_XBOX_B,
|
||||
(Kind::Xbox, Btn::West) => I_PAD_XBOX_X,
|
||||
(Kind::Xbox, Btn::North) => I_PAD_XBOX_Y,
|
||||
(Kind::PlayStation, Btn::South) => I_PAD_PS_CROSS,
|
||||
(Kind::PlayStation, Btn::East) => I_PAD_PS_CIRCLE,
|
||||
(Kind::PlayStation, Btn::West) => I_PAD_PS_SQUARE,
|
||||
(Kind::PlayStation, Btn::North) => I_PAD_PS_TRIANGLE,
|
||||
(Kind::Nintendo, Btn::South) => I_PAD_NIN_A,
|
||||
(Kind::Nintendo, Btn::East) => I_PAD_NIN_B,
|
||||
(Kind::Nintendo, Btn::West) => I_PAD_NIN_X,
|
||||
(Kind::Nintendo, Btn::North) => I_PAD_NIN_Y,
|
||||
(_, Btn::Lb) => I_PAD_LB,
|
||||
(_, Btn::Rb) => I_PAD_RB,
|
||||
(_, Btn::Lt) => I_PAD_LT,
|
||||
(_, Btn::Rt) => I_PAD_RT,
|
||||
(_, Btn::Start) => I_PAD_START,
|
||||
(_, Btn::Select) => I_PAD_SELECT,
|
||||
(_, Btn::LStick) => I_PAD_STICK_L,
|
||||
(_, Btn::RStick) => I_PAD_STICK_R,
|
||||
_ => I_PAD_DPAD,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
enum TBtn {
|
||||
Cat(usize),
|
||||
@@ -897,7 +934,8 @@ impl Ui {
|
||||
}
|
||||
|
||||
// ---------------- toolbar: buttons of 6 x 3 characters, icons and labels on the grid
|
||||
let tb_rows = 5;
|
||||
// (with a gamepad one row more: the pad's buttons as pictograms underneath)
|
||||
let tb_rows = if gamepad_ui() { 6 } else { 5 };
|
||||
let tb_y = sh - tb_rows as f32 * CH();
|
||||
let tb_y = (tb_y / CH()).floor() * CH();
|
||||
let tb_h = sh - tb_y;
|
||||
@@ -954,7 +992,7 @@ impl Ui {
|
||||
let label = if tools.len() == 1 { ti.label.clone() } else { tr(key) };
|
||||
let hovered = !touch_ui() && Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH()).contains(vec2(mx, my));
|
||||
// single tools show their key (desktop) and their description when hovered
|
||||
let key = if tools.len() == 1 && !touch_ui() { keys[tools[0]].as_str() } else { "" };
|
||||
let key = if tools.len() == 1 && !touch_ui() && !gamepad_ui() { keys[tools[0]].as_str() } else { "" };
|
||||
if hovered && tools.len() == 1 {
|
||||
hover_tool = Some(tools[0]);
|
||||
}
|
||||
@@ -1010,6 +1048,44 @@ impl Ui {
|
||||
let ml: String = ml.chars().take(maxc).collect();
|
||||
gfx.text(&ml, bx + (bw - gfx.measure(&ml, SMALL())) * 0.5, by + 2.0 * CH() + 4.0 * px, SMALL(), TXT);
|
||||
self.touch_btns.push((r, TBtn::Menu));
|
||||
// gamepad: its buttons as pictograms, on the extra row under the buttons
|
||||
if gamepad_ui() {
|
||||
let kind = crate::pad::kind();
|
||||
let icon = 2.0 * CH(); // a 16 px atlas cell at 2*F
|
||||
let gap = CW();
|
||||
let south = [pad_icon(kind, crate::pad::Btn::South)];
|
||||
let east = [pad_icon(kind, crate::pad::Btn::East)];
|
||||
let shoulders = [I_PAD_LB, I_PAD_RB];
|
||||
let start = [I_PAD_START];
|
||||
let sticks = [I_PAD_STICK_L, I_PAD_STICK_R];
|
||||
let triggers = [I_PAD_LT, I_PAD_RT];
|
||||
let entries: [(&[u32], &str); 6] = [
|
||||
(&south, "pad.use"),
|
||||
(&east, "pad.back"),
|
||||
(&shoulders, "pad.tool"),
|
||||
(&start, "pad.menu"),
|
||||
(&sticks, "pad.camera"),
|
||||
(&triggers, "pad.zoom"),
|
||||
];
|
||||
let width = |icons: &[u32], label: &str| icon * icons.len() as f32 + gap * 0.5 * (icons.len() - 1) as f32 + gap + gfx.measure(label, SMALL());
|
||||
let total = |n: usize| entries[..n].iter().map(|(i, k)| width(i, &tr(k))).sum::<f32>() + gap * 1.5 * (n - 1) as f32;
|
||||
let avail = sw - 2.0 * CW();
|
||||
let mut n = entries.len();
|
||||
while n > 2 && total(n) > avail {
|
||||
n -= 1;
|
||||
}
|
||||
let mut lx = grid_center(sw, total(n));
|
||||
let ly = tb_y + 4.0 * CH();
|
||||
for (icons, key) in entries.iter().take(n) {
|
||||
for &id in icons.iter() {
|
||||
gfx.draw_cell(id, lx, ly, icon, icon, WHITE);
|
||||
lx += icon + gap * 0.5;
|
||||
}
|
||||
let label = tr(key);
|
||||
gfx.text(&label, lx, ly + (icon - GH * SMALL()) * 0.5, SMALL(), TXT);
|
||||
lx += gfx.measure(&label, SMALL()) + gap * 1.5;
|
||||
}
|
||||
}
|
||||
// open category row: (category, x0, button width, columns)
|
||||
let popup = self.open_cat.map(|c| (c, x0, bw, bcols));
|
||||
// touch: after choosing a tool its description is shown for a moment
|
||||
@@ -1524,7 +1600,9 @@ impl Ui {
|
||||
}
|
||||
|
||||
fn draw_help(&mut self, gfx: &Gfx) {
|
||||
let controls: &[&str] = if touch_ui() {
|
||||
let controls: &[&str] = if gamepad_ui() {
|
||||
&["help.p_move", "help.p_aim", "help.p_rot", "help.p_zoom", "help.p_use", "help.p_back", "help.p_tool"]
|
||||
} else if touch_ui() {
|
||||
&["help.t_tap", "help.t_drag", "help.t_two", "help.t_long", "help.t_double", "help.t_tools"]
|
||||
} else {
|
||||
&["help.k_left", "help.k_right", "help.k_slap", "help.k_pan", "help.k_rot", "help.k_reset", "help.k_zoom", "help.k_tools", "help.k_misc", "help.k_misc2"]
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
var fetched = { state: 0 }; // download of a file from the server: 0 idle, 1 running, 2 done, 3 failed
|
||||
var ws = {}; // socket id -> { state, queue, sock }
|
||||
var wsNext = 1;
|
||||
var gpPad = null; // the gamepad handed out by dc_gamepad_slot (reused for its buttons/axes)
|
||||
var gpSlot = -1;
|
||||
// browsers offer the Gamepad API only in a secure context (https or localhost)
|
||||
function gamepads() {
|
||||
try {
|
||||
return navigator.getGamepads ? navigator.getGamepads() : [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function bytes(ptr, len) {
|
||||
return new Uint8Array(wasm_memory.buffer, ptr, len);
|
||||
@@ -193,6 +203,66 @@
|
||||
env.dc_coarse_pointer = function () {
|
||||
try { return window.matchMedia("(pointer: coarse)").matches ? 1 : 0; } catch (e) { return 0; }
|
||||
};
|
||||
// ---- gamepads: the game polls these functions once per frame (see src/pad.rs).
|
||||
// The Gamepad API needs a secure context (https or localhost) and shows a pad only after
|
||||
// its first button press. slot = the index in navigator.getGamepads(), -1 = no gamepad.
|
||||
env.dc_gamepad_slot = function () {
|
||||
gpPad = null;
|
||||
gpSlot = -1;
|
||||
var pads = gamepads();
|
||||
for (var i = 0; i < pads.length; i++) {
|
||||
if (pads[i] && pads[i].connected && pads[i].mapping === "standard") {
|
||||
gpPad = pads[i];
|
||||
gpSlot = i;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// a pad without the standard mapping (old DirectInput devices): use it anyway
|
||||
for (var j = 0; j < pads.length; j++) {
|
||||
if (pads[j] && pads[j].connected) {
|
||||
gpPad = pads[j];
|
||||
gpSlot = j;
|
||||
return j;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
function gamepad(slot) {
|
||||
if (gpPad && slot === gpSlot) return gpPad;
|
||||
var pads = gamepads();
|
||||
return pads[slot] && pads[slot].connected ? pads[slot] : null;
|
||||
}
|
||||
// bit i set: standard button i is down (triggers count from 40 % on)
|
||||
env.dc_gamepad_buttons = function (slot) {
|
||||
var p = gamepad(slot);
|
||||
if (!p) return 0;
|
||||
var m = 0;
|
||||
for (var i = 0; i < p.buttons.length && i < 16; i++) {
|
||||
var b = p.buttons[i];
|
||||
if (b && (b.pressed || b.value > 0.4)) m |= 1 << i;
|
||||
}
|
||||
return m;
|
||||
};
|
||||
// axis 0/1 = left stick x/y, 2/3 = right stick x/y (all -1..1)
|
||||
env.dc_gamepad_axis = function (slot, axis) {
|
||||
var p = gamepad(slot);
|
||||
if (!p || !p.axes || axis < 0 || axis >= p.axes.length) return 0;
|
||||
return p.axes[axis];
|
||||
};
|
||||
// 0 unknown, 1 Xbox, 2 PlayStation, 3 Nintendo (from the pad's id)
|
||||
// (the Xbox check comes first: its id contains "Wireless Controller" too)
|
||||
env.dc_gamepad_kind = function (slot) {
|
||||
var p = gamepad(slot);
|
||||
if (!p) return 0;
|
||||
var id = (p.id || "").toLowerCase();
|
||||
if (id.indexOf("xbox") >= 0 || id.indexOf("xinput") >= 0 || id.indexOf("microsoft") >= 0) return 1;
|
||||
if (id.indexOf("nintendo") >= 0 || id.indexOf("switch") >= 0 || id.indexOf("joy-con") >= 0 ||
|
||||
id.indexOf("joycon") >= 0 || id.indexOf("pro controller") >= 0) return 3;
|
||||
if (id.indexOf("playstation") >= 0 || id.indexOf("dualshock") >= 0 || id.indexOf("dualsense") >= 0 ||
|
||||
id.indexOf("sony") >= 0 || id.indexOf("wireless controller") >= 0 || id.indexOf("054c") >= 0 ||
|
||||
/(^|[^0-9])ps[345]([^0-9]|$)/.test(id)) return 2;
|
||||
return 0;
|
||||
};
|
||||
env.dc_storage_set = function (kp, kl, vp, vl) {
|
||||
try { window.localStorage.setItem(str(kp, kl), str(vp, vl)); } catch (e) { }
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user