1
2
3
4
5
6
7
8
9 from PyQt4 import QtCore, QtGui
10 from PyQt4.QtCore import Qt
11
12 from PyQt4.QtGui import QWidget as QGLWidget
13 from pilas import actores, colores, depurador, eventos, fps
14 from pilas import imagenes, simbolos, utils
15 import copy
16 import os
17 import pilas
18 import sys
19 import traceback
20
21
22 -class Ventana(QtGui.QMainWindow):
23
25 QtGui.QMainWindow.__init__(self, parent)
26 self.setStyleSheet("QWidget {background-color : #222}")
27
29 self.canvas = canvas
30 self.canvas.setParent(self)
31
33 self.canvas.resize_to(self.width(), self.height())
34
287
295
297
299 self._rotacion = 0
300 self._transparencia = 0
301 self.centro_x = 0
302 self.centro_y = 0
303 self._escala_x = 1
304 self._escala_y = 1
305 self._espejado = False
306 self.fijo = 0
307
309 self.centro_x = x
310 self.centro_y = y
311
313 return self.x, self.y
314
316 self.x, self.y = x, y
317
319 return self._escala_x
320
322 self._escala_x = s
323 self._escala_y = s
324
327
330
332 self._transparencia = nuevo_valor
333
335 return self._transparencia
336
338 return self._rotacion
339
342
345
348
350 self.ruta_original = ruta
351
352 if ruta.lower().endswith("jpeg") or ruta.lower().endswith("jpg"):
353 try:
354 self._imagen = self.cargar_jpeg(ruta)
355 except:
356 self._imagen = QtGui.QPixmap(ruta)
357 else:
358 self._imagen = QtGui.QPixmap(ruta)
359
361 from PIL import Image
362 import StringIO
363
364 pilImage = Image.open(ruta)
365 stringIO = StringIO.StringIO()
366 pilImage.save(stringIO, format="png")
367
368 pixmapImage = QtGui.QPixmap()
369 pixmapImage.loadFromData(stringIO.getvalue())
370
371 return pixmapImage
372
374 return self._imagen.size().width()
375
377 return self._imagen.size().height()
378
380 "Retorna una tupla con la coordenada del punto medio del la imagen."
381 return (self.ancho()/2, self.alto()/2)
382
385
386 - def dibujar(self, painter, x, y, dx=0, dy=0, escala_x=1, escala_y=1, rotacion=0, transparencia=0):
387 """Dibuja la imagen sobre la ventana que muestra el motor.
388
389 x, y: indican la posicion dentro del mundo.
390 dx, dy: es el punto centro de la imagen (importante para rotaciones).
391 escala_x, escala_yindican cambio de tamano (1 significa normal).
392 rotacion: angulo de inclinacion en sentido de las agujas del reloj.
393 """
394
395 painter.save()
396 centro_x, centro_y = pilas.mundo.motor.centro_fisico()
397 painter.translate(x + centro_x, centro_y - y)
398 painter.rotate(rotacion)
399 painter.scale(escala_x, escala_y)
400
401 if transparencia:
402 painter.setOpacity(1 - transparencia/100.0)
403
404 self._dibujar_pixmap(painter, -dx, -dy)
405 painter.restore()
406
408 painter.drawPixmap(x, y, self._imagen)
409
411 nombre_imagen = os.path.basename(self.ruta_original)
412 return "<Imagen del archivo '%s'>" %(nombre_imagen)
413
416
417 """Representa una grilla regular, que se utiliza en animaciones.
418
419 La grilla regular se tiene que crear indicando la cantidad
420 de filas y columnas. Una vez definida se puede usar como
421 una imagen normal, solo que tiene dos metodos adicionales
422 para ``definir_cuadro`` y ``avanzar`` el cuadro actual.
423 """
424
425 - def __init__(self, ruta, columnas=1, filas=1):
433
436
439
442
444 self._cuadro = cuadro
445
446 frame_col = cuadro % self.columnas
447 frame_row = cuadro / self.columnas
448
449 self.dx = frame_col * self.cuadro_ancho
450 self.dy = frame_row * self.cuadro_alto
451
453 ha_avanzado = True
454 cuadro_actual = self._cuadro + 1
455
456 if cuadro_actual >= self.cantidad_de_cuadros:
457 cuadro_actual = 0
458 ha_avanzado = False
459
460 self.definir_cuadro(cuadro_actual)
461 return ha_avanzado
462
465
468
469
470 -class Texto(Imagen):
471 CACHE_FUENTES = {}
472
473 - def __init__(self, texto, magnitud, motor, vertical=False, fuente=None):
474 self.vertical = vertical
475 self.fuente = fuente
476 self._ancho, self._alto = motor.obtener_area_de_texto(texto, magnitud, vertical, fuente)
477
478 - def _dibujar_pixmap(self, painter, dx, dy):
479 if self.fuente:
480 nombre_de_fuente = Texto.cargar_fuente_desde_cache(self.fuente)
481 else:
482 nombre_de_fuente = painter.font().family()
483
484 fuente = QtGui.QFont(nombre_de_fuente, self.magnitud)
485 metrica = QtGui.QFontMetrics(fuente)
486
487 r, g, b, a = self.color.obtener_componentes()
488 painter.setPen(QtGui.QColor(r, g, b))
489 painter.setFont(fuente)
490
491 if self.vertical:
492 lines = [t for t in self.texto]
493 else:
494 lines = self.texto.split('\n')
495
496 for line in lines:
497 painter.drawText(dx, dy + self._alto, line)
498 dy += metrica.height()
499
500 @classmethod
501 - def cargar_fuente_desde_cache(kclass, fuente_como_ruta):
502 """Carga o convierte una fuente para ser utilizada dentro del motor.
503
504 Permite a los usuarios referirse a las fuentes como ruta a archivos, sin
505 tener que preocuparse por el font-family.
506
507 :param fuente_como_ruta: Ruta al archivo TTF que se quiere utilizar.
508
509 Ejemplo:
510
511 >>> Texto.cargar_fuente_desde_cache('myttffile.ttf')
512 'Visitor TTF1'
513 """
514
515 if not fuente_como_ruta in Texto.CACHE_FUENTES.keys():
516 ruta_a_la_fuente = pilas.utils.obtener_ruta_al_recurso(fuente_como_ruta)
517 fuente_id = QtGui.QFontDatabase.addApplicationFont(ruta_a_la_fuente)
518 Texto.CACHE_FUENTES[fuente_como_ruta] = fuente_id
519 else:
520 fuente_id = Texto.CACHE_FUENTES[fuente_como_ruta]
521
522 return str(QtGui.QFontDatabase.applicationFontFamilies(fuente_id)[0])
523
526
529
532
535
536 - def texto(self, painter, cadena, x=0, y=0, magnitud=10, fuente=None, color=colores.negro):
537 "Imprime un texto respespetando el desplazamiento de la camara."
538 self.texto_absoluto(painter, cadena, x, y, magnitud, fuente, color)
539
540 - def texto_absoluto(self, painter, cadena, x=0, y=0, magnitud=10, fuente=None, color=colores.negro):
541 "Imprime un texto sin respetar al camara."
542 x, y = utils.hacer_coordenada_pantalla_absoluta(x, y)
543
544 r, g, b, a = color.obtener_componentes()
545 painter.setPen(QtGui.QColor(r, g, b))
546
547 if not fuente:
548 fuente = painter.font().family()
549
550 painter.setFont(QtGui.QFont(fuente, magnitud))
551 painter.drawText(x, y, cadena)
552
553 - def pintar(self, painter, color):
554 r, g, b, a = color.obtener_componentes()
555 ancho, alto = pilas.mundo.motor.obtener_area()
556 painter.fillRect(0, 0, ancho, alto, QtGui.QColor(r, g, b))
557
559 x0, y0 = utils.hacer_coordenada_pantalla_absoluta(x0, y0)
560 x1, y1 = utils.hacer_coordenada_pantalla_absoluta(x1, y1)
561
562 r, g, b, a = color.obtener_componentes()
563 color = QtGui.QColor(r, g, b)
564 pen = QtGui.QPen(color, grosor)
565 painter.setPen(pen)
566 painter.drawLine(x0, y0, x1, y1)
567
569 x, y = puntos[0]
570 if cerrado:
571 puntos.append((x, y))
572
573 for p in puntos[1:]:
574 nuevo_x, nuevo_y = p
575 self.linea(motor, x, y, nuevo_x, nuevo_y, color, grosor)
576 x, y = nuevo_x, nuevo_y
577
579 t = 3
580 self.linea(painter, x - t, y - t, x + t, y + t, color, grosor)
581 self.linea(painter, x + t, y - t, x - t, y + t, color, grosor)
582
584 x, y = utils.hacer_coordenada_pantalla_absoluta(x, y)
585
586 r, g, b, a = color.obtener_componentes()
587 color = QtGui.QColor(r, g, b)
588 pen = QtGui.QPen(color, grosor)
589 painter.setPen(pen)
590 painter.drawEllipse(x -radio, y-radio, radio*2, radio*2)
591
593 x, y = utils.hacer_coordenada_pantalla_absoluta(x, y)
594
595 r, g, b, a = color.obtener_componentes()
596 color = QtGui.QColor(r, g, b)
597 pen = QtGui.QPen(color, grosor)
598 painter.setPen(pen)
599 painter.drawRect(x, y, ancho, alto)
600
603
605 self._imagen = QtGui.QPixmap(ancho, alto)
606 self._imagen.fill(QtGui.QColor(255, 255, 255, 0))
607 self.canvas = QtGui.QPainter()
608
612
614 self.canvas.begin(self._imagen)
615 self.canvas.drawPixmap(x, y, imagen._imagen, origen_x, origen_y, ancho, alto)
616 self.canvas.end()
617
620
621 - def texto(self, cadena, x=0, y=0, magnitud=10, fuente=None, color=colores.negro):
622 self.canvas.begin(self._imagen)
623 r, g, b, a = color.obtener_componentes()
624 self.canvas.setPen(QtGui.QColor(r, g, b))
625 dx = x
626 dy = y
627
628 if not fuente:
629 fuente = self.canvas.font().family()
630
631 font = QtGui.QFont(fuente, magnitud)
632 self.canvas.setFont(font)
633 metrica = QtGui.QFontMetrics(font)
634
635 for line in cadena.split('\n'):
636 self.canvas.drawText(dx, dy, line)
637 dy += metrica.height()
638
639 self.canvas.end()
640
642 self.canvas.begin(self._imagen)
643
644 r, g, b, a = color.obtener_componentes()
645 color = QtGui.QColor(r, g, b)
646 pen = QtGui.QPen(color, grosor)
647 self.canvas.setPen(pen)
648
649 if relleno:
650 self.canvas.setBrush(color)
651
652 self.canvas.drawEllipse(x -radio, y-radio, radio*2, radio*2)
653 self.canvas.end()
654
656 self.canvas.begin(self._imagen)
657
658 r, g, b, a = color.obtener_componentes()
659 color = QtGui.QColor(r, g, b)
660 pen = QtGui.QPen(color, grosor)
661 self.canvas.setPen(pen)
662
663 if relleno:
664 self.canvas.setBrush(color)
665
666 self.canvas.drawRect(x, y, ancho, alto)
667 self.canvas.end()
668
670 self.canvas.begin(self._imagen)
671
672 r, g, b, a = color.obtener_componentes()
673 color = QtGui.QColor(r, g, b)
674 pen = QtGui.QPen(color, grosor)
675 self.canvas.setPen(pen)
676
677 self.canvas.drawLine(x, y, x2, y2)
678 self.canvas.end()
679
680 - def poligono(self, puntos, color, grosor, cerrado=False):
681 x, y = puntos[0]
682
683 if cerrado:
684 puntos.append((x, y))
685
686 for p in puntos[1:]:
687 nuevo_x, nuevo_y = p
688 self.linea(x, y, nuevo_x, nuevo_y, color, grosor)
689 x, y = nuevo_x, nuevo_y
690
693
695 self._imagen.fill(QtGui.QColor(0, 0, 0, 0))
696
697
698 -class Actor(BaseActor):
699
700 - def __init__(self, imagen="sin_imagen.png", x=0, y=0):
710
717
720
722 escala_x, escala_y = self._escala_x, self._escala_y
723
724 if self._espejado:
725 escala_x *= -1
726
727 if not self.fijo:
728 x = self.x - pilas.mundo.motor.camara_x
729 y = self.y - pilas.mundo.motor.camara_y
730 else:
731 x = self.x
732 y = self.y
733
734 self.imagen.dibujar(painter, x, y, self.centro_x, self.centro_y,
735 escala_x, escala_y, self._rotacion, self._transparencia)
736
757
762
766 deshabilitado = False
767
769 self.ruta = ruta
770 self.sonido = player
771 self.sonido.set_property('uri','file://'+ruta)
772
774 import gst
775 if not self.deshabilitado:
776 self.sonido.set_state(gst.STATE_NULL)
777 self.sonido.set_state(gst.STATE_PLAYING)
778
780 import gst
781 self.sonido.set_state(gst.STATE_NULL)
782
788
792 deshabilitado = False
793
795 from PyQt4 import phonon
796 self.media = media
797 self.ruta = ruta
798
799 self.source = phonon.Phonon.MediaSource(ruta)
800 self.sonido = phonon.Phonon.createPlayer(phonon.Phonon.GameCategory, self.source)
801
806
808 "Detiene el audio."
809 self.sonido.stop()
810
812 "Hace una pausa del audio."
813 self.sonido.pause()
814
819
825
828 """Representa la ventana principal de pilas.
829
830 Esta clase construirá el objeto apuntado por el atributo
831 ``pilas.motor``, asi que será el representante de todas
832 las funcionalidades multimedia.
833
834 Internamente, este motor, tratará de usar OpenGl para acelerar
835 el dibujado en pantalla si la tarjeta de video lo soporta.
836 """
837
838 - def __init__(self, usar_motor, permitir_depuracion, audio):
849
851 self.app = QtGui.QApplication([])
852 self.app.setApplicationName("pilas")
853
855 self.camara_x = 0
856 self.camara_y = 0
857
859 sistemas_de_sonido = ['deshabilitado', 'phonon', 'gst']
860
861 if audio not in sistemas_de_sonido:
862 error = "El sistema de audio '%s' es invalido" %(audio)
863 sugerencia = ". Use alguno de los siguientes: %s" %(str(sistemas_de_sonido))
864 raise Exception(error + sugerencia)
865
866 if audio == 'gst':
867 try:
868 import gst
869 except ImportError:
870 print "Nota: El sistema de audio gstreamer no está disponible, usando phonon en su lugar."
871 audio = 'phonon'
872
873 if audio == 'deshabilitado':
874 self.player = None
875 self.clase_sonido = SonidoDeshabilitado
876 self.clase_musica = MusicaDeshabilitada
877 elif audio == 'phonon':
878 from PyQt4 import phonon
879 self.media = phonon.Phonon.MediaObject()
880 self.audio = phonon.Phonon.AudioOutput(phonon.Phonon.MusicCategory)
881 self.path = phonon.Phonon.createPath(self.media, self.audio)
882 self.player = self.media
883 self.clase_sonido = SonidoPhonon
884 self.clase_musica = MusicaPhonon
885 elif audio == 'gst':
886 import gst
887 self.player = gst.element_factory_make("playbin", "player")
888 self.clase_sonido = SonidoGST
889 self.clase_musica = MusicaGST
890
893
894 - def iniciar_ventana(self, ancho, alto, titulo, pantalla_completa, gestor_escenas, rendimiento):
895 self.ventana = Ventana()
896 self.ventana.resize(ancho, alto)
897
898 if self.usar_motor in ['qtwidget', 'qtsugar']:
899 mostrar_ventana = False
900 self.canvas = CanvasWidgetSugar(self, actores.todos, ancho, alto, gestor_escenas, self.permitir_depuracion, rendimiento)
901 else:
902 mostrar_ventana = True
903 self.canvas = CanvasWidget(self, actores.todos, ancho, alto, gestor_escenas, self.permitir_depuracion, rendimiento)
904
905 self.ventana.set_canvas(self.canvas)
906 self.canvas.setFocus()
907
908 self.ancho_original = ancho
909 self.alto_original = alto
910 self.titulo = titulo
911 self.ventana.setWindowTitle(self.titulo)
912
913 if mostrar_ventana:
914 self.ventana.show()
915 self.ventana.raise_()
916
917 if pantalla_completa:
918 self.canvas.pantalla_completa()
919
931
933 self.canvas.setCursor(QtGui.QCursor(Qt.BlankCursor))
934
936 self.canvas.setCursor(QtGui.QCursor(Qt.ArrowCursor))
937
939 if getattr(self, 'app', None):
940 sys.exit(self.app.exec_())
941
943 self.camara_x = x
944 self.camara_y = y
945
947 return (self.camara_x, self.camara_y)
948
950 "Centro de la ventana para situar el punto (0, 0)"
951 return self.ancho_original/2, self.alto_original/2
952
954 return (self.ancho_original, self.alto_original)
955
956 - def obtener_area_de_texto(self, texto, magnitud=10, vertical=False, fuente=None):
957 ancho = 0
958 alto = 0
959
960 if fuente:
961 nombre_de_fuente = Texto.cargar_fuente_desde_cache(fuente)
962 else:
963 nombre_de_fuente = ''
964
965 fuente = QtGui.QFont(nombre_de_fuente, magnitud)
966 metrica = QtGui.QFontMetrics(fuente)
967
968 if vertical:
969 lineas = [t for t in texto]
970 else:
971 lineas = texto.split('\n')
972
973 for linea in lineas:
974 ancho = max(ancho, metrica.width(linea))
975 alto += metrica.height()
976
977 return ancho, alto
978
981
982 - def obtener_texto(self, texto, magnitud, vertical=False, fuente=None):
983 return Texto(texto, magnitud, self, vertical, fuente)
984
986 return Grilla(ruta, columnas, filas)
987
989 return self.clase_sonido(self.player, ruta)
990
992 return self.clase_musica(self.player, ruta)
993
996
999
1002
1005
1008