Interakcija z miško ali tipkovnico


Vsak malo bolj resen grafični program predvideva interakcijo uporabnika s pomočjo miške oziroma tipkovnice.
Spomnimo se, da pri VPythonu potrebujemo desni mišji gumb za vrtenje našega pogleda na sceno, kolešček pa za približevanje ali oddaljevanje scene. Za kakšno drugo interakcijo preostane levi mišji gumb. In seveda tipkovnica.

Oglejmo si preprost primer: Vidimo sceno s kroglo. Program v neskončni zanki čaka na klik z levim mišjim gumbom, kar dosežemo s stavkom scene.pause('Za nadaljevanje klikni z miško').
Program nas obvesti, da čaka. Po kliku z levim mišjim gumbom pa izpiše trenutni položaj miške:

GlowScript 3.0 VPython
sphere()
while True:
ev = scene.pause('Za nadaljevanje klikni z miško')
print("Položaj miške:"+ ev.pos) # Izpis polozaja miške

Sicer pa lahko prestrezamo tudi druge dogodke. Imamo naslednje možnosti:

scene.waitfor('click') # čakamo na klik z miško
scene.waitfor('mousedown') # Čakamo na pritisk na mišji gumb
scene.waitfor('mouseup') # Čakamo na sprostitev mišjega gumba
scene.waitfor('mousemove') # Čakamo na premik miške
scene.waitfor('mouseenter') # Čakamo na vstop miške v sceno (na takoimenovano platno)
scene.waitfor('mouseleave') # čakamo, da z miško zapistimo sceno (platno)
scene.waitfor('keydown') # čakamo na pritisk tipke na tipkovnici
scene.waitfor('keyup') # čakamo na sprostitev tipke na tipkovnici

Program pa običajno ne čaka na nek dogodek, saj bi bil v tem primeru neodziven na druge dogodke, (primer, čakamo na klik z miško in se tako ne moremo odzivati na tipke s tipkovnice) Uporabiti moramo drugačen način interakcije.


Prestrezanje dogodkov

Imamo dva načina prestrezanja dogodkov z miško oziroma tipkovnico: izpraševanje (polling) in odzivne funkcije (callback).

Pri izpraševanju stalno preverjamo, ali kakšen dogodek čaka na obdelavo. Taki dogodki se po kronološkem zaporedju nabirajo v čakalni vrsti in z ukazom tipa

scene.mouse.getevent()  #dobimo naslednji dogodek in ga obdelamo.

Pri načinu z odzivnimi funkcijami pa definiramo funkcijo, ki naj se izvede ob nastopu določenega dogodka. Tak način je najbolj primeren za obravnavo dogodkov z miško ali tipkovnico.

Preprost primer:


GlowScript 3.0 VPython
s = sphere(color=color.cyan) def SpremeniBarvo(): if s.color.equals(color.cyan): s.color = color.red else: s.color = color.cyan scene.bind('click', SpremeniBarvo)

V tem zgledu smo definirali funkcijo SpremeniBarvo.  Nato to funkcijo povežemo (bind)s klikanjem na našo sceno. Kadarkoli bomo kliknili na sceno, bo program poklical funkcijo SpremeniBarvo  in v njej spremenil barvo krogle.

Tako bi lahko povezali z ustreznimi funkcijami tudi druge tipe dogodkov:

V našem primeru nastopa tudi spremenljivka ev (lahko bi imela tudo kakšno drugo ime). Ta spremenljivka vsebuje podrobnosti o dogodku, ki jih lahko uporabimo. Tako lahko na trenutni položaj miške postavimo nov objekt, na primer tako:

box(pos=ev.pos) # na trenutni položaj miške postavimo nek objekt (na primer kocko)


V spodnjem primeru imamo eno funkcijo, ki se sproži, ko kliknemo z miško. Druga funkcija pa se sproži, ko pritisnemo na tipko tipkovnice:
GlowScript 3.0 VPython
def premakniObroc(ev): # odzivna funkcija, ki reagira na tipke
kodaTipke= ev.which
print(ev.event, " koda tipke =" + kodaTipke)
if kodaTipke == 39: #puščica v desno
obroc.pos.x = obroc.pos.x + 0.1
else if kodaTipke == 37: #puščica v levo
obroc.pos.x = obroc.pos.x - 0.1
else if kodaTipke == 38: #puščica gor
obroc.pos.y = obroc.pos.y + 0.1
else if kodaTipke == 40: #puščica gor
obroc.pos.y = obroc.pos.y - 0.1
else if kodaTipke ==189: #+ približa obroč
obroc.pos.z = obroc.pos.z + 0.1
else if kodaTipke ==187: #- oddalji obroč
obroc.pos.z = obroc.pos.z -0.1

def dodajKroglo(ev): # odzivna funkcija, ki reagira na klik miške
sphere(pos=ev.pos, radius = 0.1)

scene.bind('keydown', premakniObroc ) # povezava pritiskanja tipk in odzivne funkcije
scene.bind('click', dodajKroglo) # povezava klikanja z miško in odzivne funkcije
obroc = ring( axis=vector(0,0,1), radius=0.5, thickness=0.1, color=color.green)
scene.autoscale = False # da se scena ne bo avtomatsko manjšala ali večala

Sicer pa lahko z odzivnimi funkcijami navezujemo naslednje dogodke:

Miška:    click, mousedown, mousemove, mouseup
Tipkovnica: keydown, keyup
Drugo:    redraw, draw_complete

Dogodka 'mousedown' oziroma 'mouseup' nastopita pri pritisku ali sprostitvi levega mišjega gumba. Dogodek 'mousemove' nastopi pri premikanju miške.Dogodek 'redraw' nastopi tik pred osvežitvijo 3D scene na zaslonu, dogodek 'draw_complete' pa takoj po tej osvežitvi.(ta dva dogodka imata bolj tehnično uporabo, ker lahko tako osveževanje terja nekaj časa).

Lahko pa eno odzivno funkcijo povežemo z več dogodki. V spodnjem primeru sprožita isto odzivno funkcijo tako pritisk kot sprostitev mišjega gumba:

scene.bind('mouseup mousedown', spremeniNekaj)


Podrobnosti o dogodku

Podrobnosti o dogodku lahko dobimo z odzivno funkcijo, ki ji kot argument podamo ime dogodka (v spodnjem primeru smo za to uporabili spremenljivko z imenom evt). Naslednji primer ponazoruje definicijo in uporabo take funkcije:

def Podrobnosti(evt):
print(evt.event, evt.pos)

evt = scene.bind('mouseup mousedown mousemove, click keyup keydown', Podrobnosti)

Vrednost evt.event bo "keydown", če smo pritisnili na neko tipko tipkovnice. Vrednost evt.which pa poda numerično kodo tipke ali indikator gumba (če smo kliknili z miško). Tako je na primer  evt.which enak 65 za črko 'A'. Če smo pri tem tiščali tipko shift, bo spremenljivka  scene.mouse.shift enaka true.

Analogno velja za spremenljivki  scene.mouse.ctrl oziroma  scene.mouse.alt.



Razvezanje odzivnih funkcij (Unbinding)

Predpostavimo, da smo pravkar izvedli scene.bind('mousedown mousemove', Povleci), a ne želimo več pošiljati dogodkov 'mousemove' funkciji Povleci. To dosežemo s stavkom :

scene.unbind('mousemove', Povleci)

Isto dosežemo tudi tako, da odzivno funkcijo začasno ustavimo ali jo spet omogočimo:

evt = scene.bind('mousemove', Povleci)
.....
evt.stop() # začasno prekini proženje funkcije Povleci s premikanjem miške
.....
evt.start() # začni proženje funkcije Povleci s premikanjem miške

S preverjanjem evt.enabled lahko ugotovimo, ali je odzivna funkcija trenutno ustavljena ali onemogočena.


Izbiranje teles z miško

Za konec si oglejmo še primer, kako lahko z miško izbiramo predmete na sceni in z njimi nato nekaj naredimo. Uporabimo spremenljivko scene.mouse.pick, ki kaže na izbrani objekt. Če pa smo kliknini v prazno, je ta spremenljivka enaka None. Spodnji primer je interaktiven in si lahko ogledamo, kako tak zgled vključimo na svojo spletno stran:
GlowScript 3.0 VPython

selected= None
oldPos = vector(0,0,0)

def izberiPredmet():
global selected, oldPos
obroc.color= color.blue # privzeta barva obroča
kvader.color=color.red # privzeta barva kvadra
if scene.mouse.pick!= None: #Kateri predmet smo izbrali
selected =scene.mouse.pick
oldPos = scene.mouse.pos
selected.color = color.yellow

def opraviNalogo():
global selected
if selected==kvader:
kvader.pos = scene.mouse.pos

else if selected ==obroc:
alpha = (scene.mouse.pos.x - oldPos.x)*0.3
obroc.rotate(alpha, vector(0,1,0))

kvader = box( pos=vector(0.5,0,0), size = vector(0.2,1,1), color = color.red, pickable = True)
obroc = ring(pos=vector(-0.5,0,-0.5), axis=vector(0,0,1), radius=0.5, thickness=0.1,color=color.blue)
scene.bind('click', izberiPredmet)
scene.bind('mousemove',opraviNalogo)
Z miško izberemo enega od predmetov. Izbrani predmet porumeni.

Če smo izbrali kvader, lahko tega z vlečenjem miške premikamo po sceni.

Če smo izbrali obroč, ga lahko z vlečenjem miške vrtimo okrog ene od osi.