I am back working on Full Count, formerly known as Pitcher’s Duel.

I spent a couple of years pushing a business venture, ‘Rdbhost’, which has not yet gotten any traction, and I wore out on it.  Temporarily, Rdbhost is back-burnered, and I am working on Full Count for a while.

Full Count has been my primary side-line since about July, but haven’t had much to say publicly about it until now.

 

This implements an iterable root-widget for Albow.   Sometimes the monolithic ‘run’ is fine, sometimes you want your own main loop.    This is basically Greg Ewings .run_modal() code, reorganized as multiple methods.  The methods to call are .run_start() once, .process_input() when relevant events are available,  and .draw_gui() at each frame (or when redrawing is necessary).

from albow import root
from pygame.locals import *
from pygame.event import Event
import pygame

class NewRootWidget(root.RootWidget):

    def __init__(self, surface):
        root.RootWidget.__init__(self, surface)
        self._progen = None
        self.bg_color = None

    def run_modal(self, modal_widget):
        #print 'new root widget'
        self.run_start(modal_widget)
        while True:
            events = [pygame.event.wait()]
            events.extend(pygame.event.get())
            self.process_input(events)
            if redraw:
                self.draw_gui()

    def run_start(self, modal_widget):
        self._progen = self._input_process_generator(modal_widget)
        self._progen.next()

    def process_input(self, events):
        """process mouse and keyboard events.
        @param events: a list or tuple of pygame events
        @returns: True if any widgets need redrawing, otherwise False
        """
        return self._progen.send(events)

    def _input_process_generator(self, modal_widget):
        is_modal = modal_widget is not None
        modal_widget = modal_widget or self

        old_top_widget = root.top_widget
        root.top_widget = modal_widget
        was_modal = modal_widget.is_modal
        modal_widget.is_modal = True
        # modal_widget.modal_result = None
        if not modal_widget.focus_switch:
            modal_widget.tab_to_first()
        # mouse_widget = None
        if root.clicked_widget:
            root.clicked_widget = modal_widget
        num_clicks = 0
        last_click_time = 0
        do_draw = True

        events = []
        while True:
            for event in events:
                type = event.type
                if type == QUIT:
                    self.quit()
                elif type == MOUSEBUTTONDOWN:
                    do_draw = True
                    t = root.get_ticks()
                    if t - last_click_time <= root.double_click_time:
                        num_clicks += 1
                    else:
                        num_clicks = 1
                    last_click_time = t
                    event.dict['num_clicks'] = num_clicks
                    root.add_modifiers(event)
                    mouse_widget = self.find_widget(event.pos)
                    if not mouse_widget.is_inside(modal_widget):
                        mouse_widget = modal_widget
                    root.clicked_widget = mouse_widget
                    root.last_mouse_event_handler = mouse_widget
                    root.last_mouse_event = event
                    mouse_widget.notify_attention_loss()
                    mouse_widget.handle_mouse('mouse_down', event)
                elif type == MOUSEMOTION:
                    root.add_modifiers(event)
                    modal_widget.dispatch_key('mouse_delta', event)
                    mouse_widget = self.find_widget(event.pos)
                    root.last_mouse_event = event
                    if root.clicked_widget:
                        root.last_mouse_event_handler = mouse_widget
                        root.clicked_widget.handle_mouse('mouse_drag', event)
                    else:
                        if not mouse_widget.is_inside(modal_widget):
                            mouse_widget = modal_widget
                        root.last_mouse_event_handler = mouse_widget
                        mouse_widget.handle_mouse('mouse_move', event)
                elif type == MOUSEBUTTONUP:
                    root.add_modifiers(event)
                    do_draw = True
                    #mouse_widget = self.find_widget(event.pos)
                    if root.clicked_widget:
                        root.last_mouse_event_handler = root.clicked_widget
                        root.last_mouse_event = event
                        root.clicked_widget = None
                        root.last_mouse_event_handler.handle_mouse('mouse_up', event)
                elif type == KEYDOWN:
                    key = event.key
                    root.set_modifier(key, True)
                    do_draw = True
                    self.send_key(modal_widget, 'key_down', event)
                    if root.last_mouse_event_handler:
                        event.dict['pos'] = root.last_mouse_event.pos
                        event.dict['local'] = root.last_mouse_event.local
                        root.last_mouse_event_handler.setup_cursor(event)
                elif type == KEYUP:
                    key = event.key
                    root.set_modifier(key, False)
                    do_draw = True
                    self.send_key(modal_widget, 'key_up', event)
                    if root.last_mouse_event_handler:
                        event.dict['pos'] = root.last_mouse_event.pos
                        event.dict['local'] = root.last_mouse_event.local
                        root.last_mouse_event_handler.setup_cursor(event)
                elif type == root.MUSIC_END_EVENT:
                    self.music_end()
                elif type == USEREVENT:
                    root.make_scheduled_calls()
                    if not is_modal:
                        do_draw = self.redraw_every_frame
                        if root.last_mouse_event_handler:
                            event.dict['pos'] = root.last_mouse_event.pos
                            event.dict['local'] = root.last_mouse_event.local
                            root.add_modifiers(event)
                            root.last_mouse_event_handler.setup_cursor(event)
                        self.begin_frame()
            events = yield do_draw

        modal_widget.is_modal = was_modal
        root.top_widget = old_top_widget
        root.clicked_widget = None

    def draw_gui(self):
        if self.is_gl:
            self.gl_clear()
            self.gl_draw_all(self, (0, 0))
        else:
            self.draw_all(self.surface)
        pygame.display.flip()
# As part of the Pitcher's Duel Project, I am conducting a comparative
# analysis of pygame GUI modules, and publishing the results on my blog.
# The comparison consists of implementing the same sample interface on
# each of the various GUIs.
#
# This code implements the interface using the Albow GUI library. For
# details on this library, see:
# http://www.cosc.canterbury.ac.nz/greg.ewing/python/Albow/
#
# The module author is: Gregory Ewing
#
# This source code is the work of David Keeney, dkeeney at travelbyroad dot net
#

# Import Modules
import pygame
from pygame.locals import *
import time
import math

# import gui stuff
from albow import widget, controls, fields
from newroot import NewRootWidget

screenSize = (640, 430)

# define the necessary callback functions.
# these are invoked by the gui widgets
#
def logAction(ed, text):
  """Add the text to the 'edit' window (callback function)"""
  ed.items.append(text)
  ed.text = '\n'.join(ed.items[-10:])
  ed.invalidate() # mark as dirty

# these classes extend the Albow classes slightly, to
# add writing to the log widget
class LoggingRadioButton(controls.RadioButton, controls.ButtonBase):
    pass

class LoggingCheckBox(controls.CheckBox, controls.ButtonBase):
    pass

class LoggingTextField(fields.TextField, controls.ButtonBase):
    def __init__(self, width=None, **kws):
        fields.TextField.__init__(self, width, **kws)
        self._prevText = self.text

    def attention_lost(self):
        if self.text != self._prevText:
            self.action()
            self._prevText = self.text

widget.Widget.fg_color = (10,0,10)
controls.Button.fg_color = (100,75,0)

# the body of the program is here
#
def main():
  """This function is called when the program starts. It initializes
  everything it needs, then runs in a loop until the function returns.
  """

  #Initialize Everything
  pygame.init()
  screen = pygame.display.set_mode(screenSize)
  pygame.display.set_caption('GUI Test - Albow')
  pygame.time.set_timer(pygame.USEREVENT, 200)

  # create GUI object
  gui = NewRootWidget(screen)
  gui.run_start(None)

  # create page label
  lbl = controls.Label('Pygame GUI Test Page - Albow', fg_color=(75,150,0))
  lbl.topleft = 29, 13
  gui.add(lbl)

  # create progress bar label
  #  progress bar itself is not implemented
  lbl4 = controls.Label('Progress Bar')
  lbl4.topleft = 356, 355
  gui.add(lbl4)

  # create edit box
  ed = controls.Label('\n'.join(('',)*15), width=250)
  ed.border_color = (10,0,10)
  ed.border_width = 1
  ed.margin = 15
  ed.topleft = 377, 22
  gui.add(ed)
  ed.items = [] #(250, 320)
  logAction(ed,'top line of input')
  logAction(ed,'second line of input')
  ed.text = '\n'.join(ed.items[-10:])

  # create CheckBoxes and add to gui
  cb1Val = [False,True,None]
  cb1 = LoggingCheckBox()
  cb1.action = lambda: logAction(ed, 'Check Box #1 clicked %s'%cb1.ref.get())
  cb1.ref = controls.ItemRef(cb1Val,0)
  cb1.topleft = 42, 45
  cb1L = controls.Label('Check box #1')
  cb1L.topleft = 62, 45
  gui.add((cb1, cb1L))
  cb2 = LoggingCheckBox()
  cb2.action = lambda: logAction(ed, 'Check Box #2 clicked %s'%cb2.ref.get())
  cb2.ref = controls.ItemRef(cb1Val,1)
  cb2.topleft = 42, 75
  cb2L = controls.Label('Check box #2')
  cb2L.topleft = 62, 75
  gui.add((cb2, cb2L))
  cb3 = LoggingCheckBox()
  cb3.action = lambda: logAction(ed, 'Check Box #3 clicked %s'%cb3.ref.get())
  cb3.topleft = 42, 103
  cb3.ref = controls.ItemRef(cb1Val,2)
  cb3L = controls.Label('Check box #3')
  cb3L.topleft = 62, 103
  gui.add((cb3, cb3L))

  # create radio buttons, put in table, and add to gui
  rb1Val = [None]
  rb1Ref = controls.ItemRef(rb1Val,0)
  rb1 = LoggingRadioButton()
  rb1.ref = rb1Ref
  rb1.setting = 1
  rb1.topleft = 200, 45
  rb1.action = lambda: logAction(ed, 'Radio Button #1 clicked %s'%rb1.ref.get())
  rb1L = controls.Label('Radio Button #1')
  rb1L.topleft = 220, 45
  gui.add((rb1, rb1L))
  rb2 = LoggingRadioButton()
  rb2.ref = rb1Ref
  rb2.setting = 2
  rb2.topleft = 200, 75
  rb2.action = lambda: logAction(ed, 'Radio Button #2 clicked %s'%rb2.ref.get())
  rb2L = controls.Label('Radio Button #2')
  rb2L.topleft = 220, 75
  gui.add((rb2, rb2L))
  rb3 = LoggingRadioButton()
  rb3.topleft = 200, 103
  rb3.ref = rb1Ref
  rb3.setting = 3
  rb3.action = lambda: logAction(ed, 'Radio Button #3 clicked %s'%rb3.ref.get())
  rb3L = controls.Label('Radio Button #3')
  rb3L.topleft = 220, 103
  gui.add((rb3, rb3L))

  # create txt box label
  lbl2 = controls.Label('Text Box')
  lbl2.topleft = 31, 145
  gui.add(lbl2)
  # create text box
  en = LoggingTextField('                                ')
  en.border_color = (0,0,0)
  en.topleft = 31, 170
  en.action = lambda: logAction(ed, 'Text field value changed %d'%len(en.text))
  gui.add(en)

  # add button
  btn1 = controls.Button('Button 1', action=lambda: logAction(ed,'Button 1 Clicked'))
  btn1.topleft = 30, 250
  gui.add(btn1)
  btn2 = controls.Button('Button 2', action=lambda: logAction(ed,'Button 2 Clicked'))
  btn2.topleft = 130, 250
  gui.add(btn2)
  btn3 = controls.Button('Button 3', action=lambda: logAction(ed,'Button 3 Clicked'))
  btn3.topleft = 30, 290
  gui.add(btn3)
  btn4 = controls.Button('Button 4', action=lambda: logAction(ed,'Button 4 Clicked'))
  btn4.topleft = 130, 290
  gui.add(btn4)

  # add image map
  imap = controls.ImageButton("clear.png", action=lambda: logAction(ed, 'ImageMap Clicked '))
  imap.topleft = 31, 340
  gui.add(imap)

  # make some insensitive
  btn2.enabled = False
  cb3.enabled = False

  #Main Loop
  while 1:

    #Handle Input Events
    for event in pygame.event.get():
      if event.type == QUIT:
          return
      elif event.type == KEYDOWN and event.key == K_ESCAPE:
          return

      # pass event to gui
      gui.process_input((event,))

      # clear background, and draw clock-spinner
      screen.fill((250, 250, 250))
      radius = 30
      spinPos = 240, 362
      sp2 = spinPos[0]+1, spinPos[1]
      progressAngle = int(time.time() % 15 * 24 - 90) #60
      pygame.draw.circle(screen, (180, 180, 180), spinPos, radius, 0)
      for angle in range(-90, progressAngle):
        a = angle*math.pi/180
        tgt = radius*math.cos(a)+spinPos[0], radius*math.sin(a)+spinPos[1]
        pygame.draw.line(screen, (254,254,254), spinPos, tgt, 2)
        pygame.draw.circle(screen, (0,0,0), spinPos, radius, 2)
        pygame.draw.circle(screen, (0,0,0), spinPos, radius+1, 3)
        pygame.draw.circle(screen, (0,0,0), sp2, radius, 2)
        pygame.draw.circle(screen, (0,0,0), sp2, radius+1, 3)
        pygame.draw.line(screen, (0,0,0), spinPos, tgt, 2)
        tgt = spinPos[0], spinPos[1]-radius
        pygame.draw.line(screen, (0,0,0), spinPos, tgt, 2)

      # Draw GUI
      gui.draw_gui()

      pygame.display.flip()

main()

Edited to include ButtonBase, and uncomment cb3.enabled line.

Years ago, I started posting reviews here of various pygame GUIs, and reviewed 3 or 4.

My own choice was OcempGUI, but have recently fallen out with it.   I have been looking at Albow as a replacement candidate, and implemented my comparison app in it.

To start, I had to write a new RootWidget that allows iterative operation,  called per-frame from my own loop.  That sub-class RootWidget will be included here in a later post.

Albow has a decent collection of widgets; the only widgets my app wanterd that weren’t included were a progress-bar and toggle-buttons.  The progress bar could be implemented fairly easily, and the toggle-button is just a different look for a check-box, so neither omission is a great loss.

Only the Button widget was complete, for my purposes.  Other widgets, I had to sub-class to add the logging feature.  It was necessary to sub-class, incorporating the ButtonBase mixin, to add an ‘action’ attribute calling the logging function.   None of these sub-classing chores were difficult, as the module is fairly straightforward to understand and classes easy to derive from.   I was able to disable the check-box and radio button widgets,  but they showed no outward sign of being disabled.  The ‘action’ attribute just stopped working.

Styling Albow widgets was as easy as any library I have reviewed.   Just set border_color and fg_color attributes to 3-tuple colors, and it’s good.

The code for the app is in the next post, and the NewRootWidget is in the post after that.  Feel free to use any as you wish.

I set out this Sunday to package up Full Count as a standalone app, with an installer, and spent the whole day frustrated by crashing problems.    SDL ‘parachute’ exceptions would crash the game on a certain GUI module call.  After tracing the source of the crash several call levels deep into the Ocemp GUI code, I gave up.    OcempGUI is just too internally elaborate, and that makes bug finding quite difficult.  After years of happy use of this library, I am moving on to finding another.

The two favored options, at this point, are ‘Albow’, and writing my own.

I looked at Albow a few years ago, and disregarded it, as ‘owning the loop’ disqualified it:

http://pitchersduel.wordpress.com/2007/09/26/albow/

Take another look at it yesterday, and wrote an alternative RootWidget that permits the system to be pumped from an external run-loop.  As part of my exploration, I will be posting (this week) an addition to my GUI review series to cover Albow.

My thoughts on creating a new Pygame GUI toolkit will be in another post another time.

 

http://webusers.npl.uiuc.edu/~a-nathan/pob/AJP-Aug2008.pdf

When I was testing the batter demo on pitcher’s duel, I observed some behaviour that seemed very odd.  The ball when hit sharply up, would seem to wander around.  Tracking it via its shadow directly below the ball, it would move back and forth over the foul line.  I thought at first this must be a bug, but the realized that is how popups behave.  The ball leaving the bat is severely undercut, and has a lot of backspin; the ball with backspin curves back toward the backstop as it rises.  When it  reaches its apex, though, it reverses direction without reversing spin.  As it falls, what was backspin is  now topspin, and pushes the ball towards the pitchers mound.  The path is thus a loop, and the shadow moves backward, then forward.  Follow the link for a much more detailed explanation.

The ball behaviour explains the way fielders, observed in MLB games, seem to wander back and forth tracking a pop fly.  They aren’t lost, they are just tracking the ball.

David

Nate Robins – OpenGL – Tutors

 These are ‘Tutors’, not ‘Tutorials’.  More accurately, they are little laboratories for experimenting with OpenGL parameters.  Each laboratory has a graphic pane showing an object, and a pane showing the key functions for that aspect of OpenGL.  You can change the parameter values by dragging, and see immediately what is the visual effect of a parameter value change.

I just yesterday found the fog tutor very helpful in understanding why my own fog generation was not looking as it should (and in making it right).   Take a look.

 David

Follow

Get every new post delivered to your Inbox.