pygo/sgc/widgets/_locals.py

369 lines
11 KiB
Python
Raw Normal View History

2012-04-14 22:38:47 +00:00
#!/usr/bin/env python
# Copyright (C) 2012 Sam Bull
"""
A collection of things for widgets to use.
Constants:
GUI: Widgets should use this for the event type of any events emitted.
get_screen(): Returns the screen object.
"""
import pygame.sprite
from pygame.locals import *
try:
import opengl
except ImportError: pass
# Things for widgets to import
__all__ = ["GUI", "get_screen", "Font"]
# Event type
GUI = USEREVENT
SCREEN = None
get_screen = lambda: SCREEN
# Cursor queue for set_cursor() and remove_cursor()
cursors = []
# ----- EXTERNAL FUNCTIONS -----
def update(time):
"""Updates all active widgets or modal widgets each frame."""
def _fade(widget):
"""Fade widget."""
if widget._fade is not None:
widget.image.set_alpha(widget._fade)
if widget._fade_up:
widget._fade += time / 3.
else:
widget._fade -= time / 4.
if widget._fade <= 0:
# Remove after fading
widget.kill()
# Reset widget to be added again
widget._fade = None
elif widget._fade >= 255:
widget._fade = None
widget.image.set_alpha(255)
if SCREEN._opengl:
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
w,h = SCREEN.get_size()
glOrtho(0, w, h, 0, 0, 1)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
glDisable(GL_LIGHTING)
glDisable(GL_DEPTH_TEST)
glEnable(GL_SCISSOR_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
# Fade widgets
for widget in layer_widgets:
_fade(widget)
for widget in active_widgets:
_fade(widget)
# Update widgets
active_widgets.update(time)
if not SCREEN._opengl:
active_widgets.draw(SCREEN)
else:
for widget in active_widgets:
widget.image.draw()
# Update layered widgets
layer_widgets.update(time)
if not SCREEN._opengl:
layer_widgets.draw(SCREEN)
else:
for widget in layer_widgets:
widget.image.draw()
if SCREEN._opengl:
glDisable(GL_SCISSOR_TEST)
glPopMatrix()
def event(event):
"""Send event to focused widget and handle widget focus."""
if modal_widgets and not focus:
modal_widgets.sprites()[-1].add(0)
# Mouse focus
if event.type == MOUSEBUTTONDOWN:
if not modal_widgets:
hit = False
for widget_list in (reversed(layer_widgets.sprites()),
active_widgets):
for widget in widget_list:
# Check if user clicked a widget
if widget._can_focus and \
widget.rect.collidepoint(event.pos):
focus.add(2, widget)
hit = True
if widget in layer_widgets:
layer_widgets.move_to_front(widget)
break
if hit: break
# Lose focus if clicking away from widgets
if not hit:
focus.empty()
# Keyboard focus
elif event.type == KEYDOWN and event.key == K_TAB:
if not modal_widgets and focus_order:
# Flattened focus_order
order = sum(focus_order,())
if focus.sprite not in order:
curr_num = None
else:
# Focus number for current focused widget
curr_num = order[order.index(focus.sprite)-1]
# Sorted list of the focus numbers being used
list_num = sorted(order[::2])
if not event.mod & KMOD_SHIFT: # Move focus to next widget
if curr_num is None:
# If nothing focused, focus first widget
new_num = list_num[0]
elif not focus.sprite._change_focus(True):
# Don't change when not at end of container widget
new_num = curr_num
elif list_num.index(curr_num) == len(list_num)-1:
# Jump back to first widget
new_num = list_num[0]
else:
# Next focus number in the list
new_num = list_num[list_num.index(curr_num)+1]
else: # Shift key - move focus to previous widget
if curr_num is None:
new_num = list_num[-1]
elif not focus.sprite._change_focus(False):
new_num = curr_num
elif list_num.index(curr_num) == 0:
# Jump back to last widget
new_num = list_num[len(list_num)-1]
else:
new_num = list_num[list_num.index(curr_num)-1]
if curr_num != new_num:
# Set widget at new focus number
focus.add(1, order[order.index(new_num)+1])
# Send event to focused widget
if focus:
focus.sprite._event(event)
# ----- FONTS -----
class _Font():
"""Wrapper class for font objects."""
__slots__ = ("_font",)
_font = None
def replace(self, font):
"""Replace the font in-place."""
self._font = font
def __getattr__(self, atr):
return getattr(self._font, atr)
def __nonzero__(self):
return True if self._font else False
class FontMetaclass(type):
"""Font metaclass to allow indexing of class."""
def __getitem__(cls, item):
return cls._fonts[item]
class Font():
"""
Class containing fonts available for use.
Index class to get fonts, such as ``Font["widget"]`` for the widget font.
The default fonts are:
widget: The default font for widgets.
title: A larger title font.
mono: A monospaced font.
Attributes:
col: (r,g,b) tuple, containing the default font colour.
"""
__metaclass__ = FontMetaclass
__slots__ = ("_fonts", "col")
_fonts = {"widget": _Font(), "title": _Font(), "mono": _Font()}
col = (255,255,255)
@classmethod
def set_fonts(cls, fonts={}):
"""
Set fonts to a specific font. If a font exists, it will be replaced,
otherwise it will be newly created.
Args:
fonts: Dictionary containing fonts to use.
Key should be name of font. Value should be string
naming either custom FreeType or a system font.
"""
for font in fonts:
if font not in cls._fonts:
cls._fonts[font] = _Font()
cls._fonts[font].replace(cls._create_font(fonts[font], 16))
if not cls._fonts["widget"]:
cls._fonts["widget"].replace(cls._create_font("Arial", 16))
if not cls._fonts["title"]:
name = fonts["widget"] if ("widget" in fonts) else "Arial"
cls._fonts["title"].replace(cls._create_font(name, 30))
if not cls._fonts["mono"]:
cls._fonts["mono"].replace(cls._create_font(
"FreeMono, Monospace", 16))
if SCREEN._opengl:
cls.mono_w = cls["mono"].font.Advance("e")
else:
cls.mono_w = cls["mono"].render("e", False, (0,0,0)).get_size()[0]
@classmethod
def _create_font(cls, font, size):
"""
Returns the correct font object for FreeType or system font, and
for OpenGL or Pygame.
"""
if font[-4:] in (".ttf", ".otf"):
if not SCREEN._opengl:
return pygame.font.Font(font, size)
else:
return opengl.OpenGLFont(font, size)
else:
if not SCREEN._opengl:
return pygame.font.SysFont(font, size)
else:
font = str(pygame.font.match_font(font))
return opengl.OpenGLFont(font, size)
# ----- WIDGET GROUPS -----
class Focus(pygame.sprite.GroupSingle):
"""
Contains currently focused widget.
"""
def add(self, focus=0, *sprites):
"""Extend add to call _focus_exit and _focus_enter methods."""
if self.sprite: self.sprite._focus_exit()
pygame.sprite.GroupSingle.add(self, *sprites)
self.sprite._focus_enter(focus)
def empty(self):
"""Extend empty to call _focus_exit method."""
if self.sprite: self.sprite._focus_exit()
pygame.sprite.GroupSingle.empty(self)
# Widget groups
active_widgets = pygame.sprite.Group()
modal_widgets = pygame.sprite.OrderedUpdates()
layer_widgets = pygame.sprite.LayeredUpdates()
# The widget that currently has focus
focus = Focus()
# Order the widgets should receive focus through TAB
focus_order = []
# ----- WIDGET FUNCTIONS -----
def add_widget(widget, order):
"""
Add widget to screen. Used by the base widget.
Returns:
True if widget has been added. False if already added.
"""
added = False
# Add to group of active widgets
if widget not in active_widgets and not widget._layered:
active_widgets.add(widget)
added = True
if order is not None and widget._can_focus:
focus_order.append((order,widget))
# Add to layered group
elif widget._layered and widget not in layer_widgets:
layer_widgets.add(widget)
added = True
# Add to group of modal widgets
if widget._modal and widget not in modal_widgets:
modal_widgets.add(widget)
added = True
# Focus newly added modal widgets
if widget._modal:
focus.add(0, widget)
return added
def remove_widget_order(widget):
"""Remove widget from focus order. Called by the base widget."""
order = sum(focus_order,())
if widget in order:
# Remove from focus_order
num = (order.index(widget)-1)/2
del focus_order[num]
def has_focus(widget):
"""Checks if a widget currently has focus."""
for group in widget.groups():
if isinstance(group, Focus):
return True
return False
def is_active(widget):
"""Checks if widget is onscreen."""
return widget in active_widgets or widget in layer_widgets
def set_cursor(widget, size, hotspot, xormasks, andmasks):
"""
Sets a cursor and adds to a queue.
Args:
widget: The widget that set the cursor, used as an ID in the queue.
size,hotspot,xormasks,andmasks: Arguments for pygame.mouse.set_cursor().
"""
if not cursors:
cursors.append((None, pygame.mouse.get_cursor()))
cursors.append((widget, (size, hotspot, xormasks, andmasks)))
pygame.mouse.set_cursor(size, hotspot, xormasks, andmasks)
def remove_cursor(widget):
"""
Removes the cursor set by widget and sets cursor to whichever cursor
is now at the end of the queue.
"""
for w, c in cursors:
if w == widget:
cursors.remove((w, c))
pygame.mouse.set_cursor(*cursors[-1][1])
if len(cursors) <= 1:
del cursors[:]