/sgl/app/app.py
Python | 406 lines | 388 code | 16 blank | 2 comment | 8 complexity | 642945a67b171e49cf33a975dc630c8b MD5 | raw file
- import pygame
- from pygame.locals import *
- from sgl.video.display import Display
- import sgl.audio.sound as sound
- from copy import copy
- from sgl.core.containers import AttrDict
- from sgl.core.tuples import Point
- from sgl.core import xml, path
- from sgl.app.console import Console
- from sgl.app.events import *
- from sgl.gui.stack import Stack
- import sgl.core.log as logger
- from optparse import OptionParser, Option, OptionValueError
- import datetime
- from sgl.app.input import *
- def check_size_option(option, opt, value):
- try:
- if not value:
- return None
- else:
- w, h = map(int, value.split('x'))
- return w, h
- except:
- raise OptionValueError('option %s: invalid size value: %r' % (opt, value))
- class MyOption(Option):
- TYPES = Option.TYPES + ('size',)
- TYPE_CHECKER = copy(Option.TYPE_CHECKER)
- TYPE_CHECKER['size'] = check_size_option
-
- class _Frame:
- def __init__(self):
- self.app = None
- self.exit = False
- self.events = []
- self.ticks = 0
- self.count = 0
- self.time = 0
-
- @property
- def display(self):
- return self.app.display
-
- def to_event(self, e):
- if e.type == Event.MOUSEMOTION:
- r = EventObject(Event.MOUSEMOTION, pos=e.pos, rel=e.rel, button=e.buttons)
- elif e.type == Event.MOUSEBUTTONUP:
- r = EventObject(Event.MOUSEBUTTONUP, pos=e.pos, button=e.button)
- elif e.type == Event.MOUSEBUTTONDOWN:
- r = EventObject(Event.MOUSEBUTTONDOWN, pos=e.pos, button=e.button)
- else:
- r = DumbEvent(e)
- r.frame = self
- return r
-
- class EventObject(EventBase):
- def __init__(self, type, pos = (0,0), rel = (0,0), button = 0):
- self.type = type
- self.pos = Point(pos)
- self.rel = Point(rel)
- self.button = button
-
- @property
- def mod(self):
- return pygame.key.get_mods()
-
- def translate(self, v):
- r = copy(self)
- r.pos = self.pos + v
- return r
-
- class App:
- def __init__(self):
- self.console = Console()
- self.joysticks = []
-
- self.options = None
- self.set_default_options()
-
- pygame.mixer.pre_init(44100, -16, 2, 4096)
- pygame.init()
- for i in range(pygame.joystick.get_count()):
- j = pygame.joystick.Joystick(i)
- j.init()
- log('Joystick found: "%s"' % j.get_name(), type='Input')
- self.joysticks.append(j)
- self.display = Display()
- self.framerate = 60
- self.calculated_framerate = 0
- self.calculated_framerate_ticks = 0
- self.calculated_framerate_count = 0
-
- def set_default_options(self):
- display = xml.Element('display')
-
- # TODO: 'auto' display size that fits resolution when fullscreen
- # maybe seperate w/h for fullscreen
- display.set('width', '640')
- display.set('height', '480')
-
- display.set('fullscreen', 'false')
-
- audio = xml.Element('audio')
- audio.set('enabled', 'true')
-
- logger = xml.Element('log')
- logger.set('level', '2')
- logger.set('enable', 'all')
- logger.set('disable', '')
-
- e = xml.Element('options')
- e.append(display)
- e.append(audio)
- e.append(logger)
-
- self.options = e
-
- def init(self):
- self.display.init(self.options.find('display'))
- logger.disable(*self.options.find('log').get('disable').split(','))
- logger.set_logger(self.console.log)
-
- sound.ENABLED = self.options.find('audio').get('enabled')
-
- from sgl.gui.theme import Theme
- if not Theme.Global:
- Theme.Global = Theme()
-
- def run(self, fn):
- clock = pygame.time.Clock()
- frame = _Frame()
- frame.app = self
- while not frame.exit:
- self.tick(fn, frame, clock)
-
- def tick(self, fn, frame, clock):
- frame.exit = False
-
- fn(frame)
-
- self.console.draw(self.display)
-
- self.display.update()
-
- frame.ticks = clock.tick(self.framerate)
- frame.time += frame.ticks
- frame.count += 1
- frame.events = [frame.to_event(e) for e in pygame.event.get()]
-
- self.calculated_framerate_ticks += frame.ticks
- self.calculated_framerate_count += 1
- if self.calculated_framerate_ticks > 1000:
- self.calculated_framerate_ticks -= 1000
- self.calculated_framerate = self.calculated_framerate_count
- self.calculated_framerate_count = 0
-
- class Routine(object):
- def init(self):
- pass
-
- def deinit(self):
- pass
-
- def exit(self):
- parent = self.parent
- if len(parent.routines) and parent.routines[-1] == self:
- parent.pop()
-
- def input(self, e):
- pass
-
- def update(self, frame):
- pass
-
- def draw(self, display):
- pass
-
- def _main_loop(self, frame):
- d = self.app.display
- self.update(frame)
- self.draw(d)
- self.theme.drawInterface(self.gui, d)
- for e in frame.events:
- try:
- if self.root.input(e):
- e.stop()
- elif e.type == Event.QUIT:
- self.exit()
- e.stop()
- elif e.type == Event.KEYDOWN:
- if e.key == Key.ESCAPE:
- self.exit()
- e.stop()
- elif e.key == Key.BACKQUOTE:
- self.app.console.hidden = not self.app.console.hidden
- e.stop()
- self.input(e)
- except StopEvent:
- pass
- class Application(object):
- def __init__(self):
- from sgl.gui.controller import Controller
- from sgl.gui.theme import Theme
- self.app = App()
- Theme.Global = self.theme = Theme()
- self.gui = Stack()
- self.root = Controller(self.gui)
- self.routines = []
- self.services = []
-
- self.options = None
- self.args = []
-
- p = OptionParser(option_class=MyOption)
- p.add_option('-f', '--fullscreen', action='store_true', dest='fullscreen',
- help='run fullscreen')
- p.add_option('-w', '--window', action='store_false', dest='fullscreen',
- help='run windowed')
- p.add_option('-n', '--no-sound', action='store_true', dest='no_sound',
- help='disable audio')
- p.add_option('-v', '--verbose', action='store_true', dest='verbose',
- help='enable all log output')
- #p.add_option('-q', '--quiet', action='store_true', dest='quiet',
- # help='disable all log output')
- p.add_option('-p', '--profile', action='store_true', dest='profile',
- help='profile mode (slow)')
- p.add_option('-o', '--optimize', action='store_true', dest='optimize',
- help='optimize using psyco')
- p.add_option('-l', '--log-level', dest='log_level', type='int', default=0,
- help='print logs up to N verbosity (min 0, max 5)', metavar='N')
- p.add_option('--size', dest='display_size', type='size',
- default='', metavar='WIDTHxHEIGHT')
- p.add_option('--audio-frequency', dest='audio_frequency', type='int',
- default=0, metavar='FREQUENCY')
- p.add_option('--audio-buffer', dest='audio_frequency', type='int',
- default=0, metavar='BUFFER')
- self.parser = p
-
- self._init = False
-
- def parse(self):
- if self.options is None:
- self.options, self.args = self.parser.parse_args()
- if self.options.display_size:
- w, h = self.options.display_size
- self.app.options.find('display').set('width', w)
- self.app.options.find('display').set('height', h)
- if self.options.fullscreen is not None:
- self.app.options.find('display').set('fullscreen', ('false','true')[self.options.fullscreen])
-
- def saveConfig(self, filename = 'config.xml'):
- tree = xml.ElementTree()
- tree._root = self.app.options
- tree._root.cleanup()
- tree.write(filename)
-
- def loadConfig(self, filename = 'config.xml'):
- if path.exists(filename):
- self.app.options = xml.read(filename)
-
- def init(self):
- if not self._init:
- self.parse()
- self.app.init()
- self.gui.size = self.app.display.size
- self._init = True
-
- if self.options.optimize:
- try:
- import psyco
- psyco.full()
- log('Pysco optimization complete')
- except ImportError:
- pass
-
- def push(self, routine):
- from sgl.gui.panel import Panel
- routine.parent = self
- routine.app = self.app
- routine.gui = Panel()
- routine.root = self.root
- routine.theme = self.theme
- self.gui.add(routine.gui)
- routine.init()
- self.gui.pack()
- self.routines.append(routine)
- log('Starting %s' % routine.__class__.__name__)
-
- def pop(self):
- r = self.routines.pop()
- log('Stopping %s' % r.__class__.__name__)
- r.deinit()
- self.gui.pop()
-
- def switch(self, routine):
- self.pop()
- self.push(routine)
-
- def loop(self, routine = None):
- self.init()
- if routine:
- self.push(routine)
-
- if self.options.profile:
- import pstats, cProfile as profile
- profile.runctx('self._do_loop()', globals(), locals(), 'profile.bin')
- p = pstats.Stats('profile.bin')
- p.strip_dirs().sort_stats('cumulative').print_stats(20)
- else:
- self._do_loop()
-
- def _do_loop(self):
- clock = pygame.time.Clock()
- frame = _Frame()
- frame.app = self.app
-
- start_time = datetime.datetime.now()
- frame_count = 0
-
- while len(self.routines):
- r = self.routines[-1]
- self.app.tick(r._main_loop, frame, clock)
- if frame.exit:
- self.pop()
- frame_count += 1
-
- seconds = (datetime.datetime.now() - start_time).seconds
- log('%s frames in %s seconds. Frames per second: %s' % (frame_count, seconds, float(frame_count) / (seconds or 1)))
- class ConsoleApplication(object):
- def __init__(self):
- from sgl.core.terminal import Terminal
- self.idle = 30
- self.routines = []
- self.services = set()
- self.parser = OptionParser(option_class=MyOption)
- self.options = None
- self.args = []
- self.terminal = Terminal()
-
- def parse(self):
- if self.options is None:
- self.options, self.args = self.parser.parse_args()
-
- def init(self):
- self.parse()
-
- def loop(self, routine = None):
- self.init()
- if routine:
- self.push(routine)
-
- clock = pygame.time.Clock()
- frame = _Frame()
- event = EventBase()
- event.type = Event.KEYDOWN
-
- while len(self.routines):
-
- # Update background services
- for s in self.services:
- if (not hasattr(s, 'paused')) or (not s.paused):
- s.update(frame)
-
- r = self.routines[-1]
-
- for key in self.terminal.events:
- event.key = event.unicode = key
- r.input(event)
- r.update(frame)
- r.draw(self.terminal)
-
- frame.ticks = clock.tick(self.idle)
- frame.time += frame.ticks
- frame.count += 1
- if frame.exit:
- self.pop()
-
- def push(self, routine):
- routine.parent = self
- routine.init()
- self.routines.append(routine)
- log('Starting %s' % routine.__class__.__name__)
-
- def pop(self):
- r = self.routines.pop()
- log('Stopping %s' % r.__class__.__name__)
- r.deinit()
-
- def switch(self, routine):
- self.pop()
- self.push(routine)
-
- def exit(self):
- while len(self.routines):
- self.pop()
-
- def addService(self, service):
- service.parent = self
- service.init()
- self.services.add(service)
-
- def removeService(self, service):
- self.services = set([s for s in self.services if s is not service])