Added a copy of SGC
This commit is contained in:
parent
90ff7ceba8
commit
52eb18994d
22
sgc/LICENSE
Normal file
22
sgc/LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2010-2012, Sam Bull
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
14
sgc/__init__.py
Normal file
14
sgc/__init__.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
"""
|
||||
Module Packages:
|
||||
:py:mod:`widgets<sgc.widgets>`: All the widgets available for use in this toolkit.
|
||||
|
||||
Modules:
|
||||
:py:mod:`locals<sgc.locals>`: Constants to be imported into the local namespace for convenience.
|
||||
:py:mod:`surface<sgc.surface>`: Extended pygame.surface classes.
|
||||
|
||||
"""
|
||||
|
||||
import surface
|
||||
import locals
|
||||
import widgets
|
||||
from widgets._locals import Font
|
16
sgc/example/menu
Normal file
16
sgc/example/menu
Normal file
|
@ -0,0 +1,16 @@
|
|||
("m:Main Menu",
|
||||
("m:Sub-menu",
|
||||
("w:input_box","name=input","label=Input","default=start typing"),
|
||||
("c:Category/divider",),
|
||||
("w:button","name=btn","label=Click\nhere","func=print_input")
|
||||
),
|
||||
("m:Settings",
|
||||
("m:Graphic Settings",
|
||||
("c:Graphic stuff",)
|
||||
),
|
||||
("m:Sound Settings",
|
||||
("c:Sound stuff",)
|
||||
)
|
||||
),
|
||||
("f:remove", "Quit")
|
||||
)
|
151
sgc/example/test.py
Normal file
151
sgc/example/test.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2012 Sam Bull
|
||||
|
||||
"""
|
||||
An example file demonstrating proper use of widgets.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import random
|
||||
|
||||
import pygame
|
||||
from pygame.locals import *
|
||||
try:
|
||||
from OpenGL.GL import *
|
||||
except: pass
|
||||
|
||||
pygame.display.init()
|
||||
pygame.font.init()
|
||||
|
||||
sys.path.insert(0, "../..")
|
||||
|
||||
import sgc
|
||||
from sgc.locals import *
|
||||
|
||||
ver_no = "0.1.3"
|
||||
|
||||
screen = sgc.surface.Screen((640,480)) #, flags=OPENGL
|
||||
clock = pygame.time.Clock()
|
||||
pygame.scrap.init()
|
||||
|
||||
def clear():
|
||||
"""Clear input box when enter key is pressed."""
|
||||
input_box.text = ""
|
||||
|
||||
|
||||
class MainMenu(sgc.widgets.Menu):
|
||||
"""Create a subclass for custom functions."""
|
||||
|
||||
func_dict = lambda self: {"print_input": self.print_input,
|
||||
"remove": self.remove}
|
||||
|
||||
def print_input(self):
|
||||
print self["input"].text
|
||||
|
||||
sgc.Font.col = (150,150,150) # TODO Button font colour
|
||||
|
||||
|
||||
# Title
|
||||
title = sgc.widgets.Label(text="Simple Game Code " + ver_no,
|
||||
font=sgc.Font["title"], col=sgc.Font.col)
|
||||
title.rect.center = (screen.rect.w/2, 40)
|
||||
title.add()
|
||||
|
||||
# Create input_box
|
||||
input_box = sgc.widgets.InputBox(label="Input Box", default="default text...")
|
||||
input_box.config(pos=(30,120))
|
||||
input_box.add(0)
|
||||
# Change colour button, activate event caught in event loop
|
||||
button = sgc.widgets.Button(label="Change\ncolour", pos=(40,200))
|
||||
# Create FPS counter
|
||||
fps = sgc.widgets.FPSCounter(clock=clock)
|
||||
fps.rect.midbottom = (screen.rect.w/2, screen.rect.h)
|
||||
fps.add()
|
||||
# Pass config file as argument, to have Menu parse file
|
||||
with open("menu") as menu_file:
|
||||
menu = MainMenu(menu=menu_file)
|
||||
|
||||
# Display menu on button click, activate replaced through assignment
|
||||
btn_menu = sgc.widgets.Button(label="Menu", pos=(250,200))
|
||||
btn_menu.activate = menu.add
|
||||
|
||||
|
||||
# Input_box for dialog window
|
||||
password_box = sgc.widgets.InputBox(label="Password", default="Enter password...")
|
||||
password_box.pos = (0,10)
|
||||
# Button for dialog window
|
||||
def print_pass():
|
||||
print password_box.text
|
||||
dialogs[-1].remove()
|
||||
btn_ok = sgc.widgets.Button(label="OK", pos=(30,60))
|
||||
btn_ok.activate = print_pass
|
||||
# Place widgets into a container
|
||||
dialog_container = sgc.widgets.Container(widgets=(password_box, btn_ok),
|
||||
border=10)
|
||||
# Display dialog window, activate replaced through inheritance
|
||||
dialogs = []
|
||||
class BtnDialog(sgc.widgets.Button):
|
||||
def activate(self):
|
||||
dialogs.append(sgc.widgets.Dialog(widget=dialog_container,
|
||||
title="Window title here..."))
|
||||
dialogs[-1].rect.center = screen.rect.center
|
||||
dialogs[-1].add()
|
||||
btn_dialog = BtnDialog(label="Dialog", pos=(460,200))
|
||||
|
||||
box_btn = sgc.widgets.HBox(widgets=[button, btn_menu, btn_dialog], spacing=70)
|
||||
|
||||
scroll_box = sgc.widgets.ScrollBox((300, box_btn.rect.h), widget=box_btn)
|
||||
scroll_box.rect.center = screen.rect.center
|
||||
scroll_box.add(1)
|
||||
|
||||
# Radio Buttons
|
||||
radio1 = sgc.widgets.Radio(group="group1", label="Option 1", active=True)
|
||||
radio2 = sgc.widgets.Radio(group="group1", label="Option 2")
|
||||
radio3 = sgc.widgets.Radio(group="group1", label="Option 3")
|
||||
radio_box = sgc.widgets.VBox(widgets=(radio1, radio2, radio3), pos=(40,320))
|
||||
radio_box.add(2)
|
||||
|
||||
# Toggle Button
|
||||
toggle = sgc.widgets.Toggle(label="Toggle", pos=(200,320))
|
||||
toggle.add(3)
|
||||
|
||||
# Selectable Label
|
||||
label = sgc.widgets.Label(text="This is a selectable label", selectable=True)
|
||||
label.rect.midtop = title.rect.midbottom
|
||||
label.add()
|
||||
|
||||
while True:
|
||||
time = clock.tick(30)
|
||||
for event in pygame.event.get():
|
||||
# Send event to widgets
|
||||
sgc.widgets.event(event)
|
||||
if event.type == GUI:
|
||||
if event.widget_type is sgc.widgets.Button:
|
||||
print "Button event"
|
||||
if event.widget is button and event.gui_type == "activate":
|
||||
button.config(col=[random.randrange(1,200) for x in range(3)])
|
||||
elif event.widget is input_box:
|
||||
clear()
|
||||
elif event.type == KEYDOWN:
|
||||
if event.key == K_f:
|
||||
fps.toggle()
|
||||
elif event.type == QUIT:
|
||||
exit()
|
||||
|
||||
# Cleanup removed windows
|
||||
for widget in dialogs:
|
||||
if not widget.active():
|
||||
dialogs.remove(widget)
|
||||
|
||||
if not screen._opengl:
|
||||
screen.fill((0,0,255))
|
||||
else:
|
||||
glClearColor(0,0,1,1)
|
||||
glClear(GL_COLOR_BUFFER_BIT)
|
||||
# Update the widgets once for each frame
|
||||
sgc.widgets.update(time)
|
||||
|
||||
pygame.display.flip()
|
13
sgc/locals.py
Normal file
13
sgc/locals.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2012 Sam Bull
|
||||
|
||||
"""
|
||||
Imports useful objects into the local namespace.
|
||||
|
||||
Constants:
|
||||
GUI: Event type for any event emitted by this toolkit.
|
||||
|
||||
"""
|
||||
|
||||
from widgets._locals import GUI
|
47
sgc/surface.py
Normal file
47
sgc/surface.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
#Copyright (C) 2010-2012 Sam Bull
|
||||
|
||||
"""
|
||||
Screen class to store rect information with the screen and setup the toolkit.
|
||||
|
||||
"""
|
||||
|
||||
import pygame.display
|
||||
from pygame.locals import *
|
||||
|
||||
import widgets._locals
|
||||
|
||||
class Screen():
|
||||
|
||||
"""
|
||||
Class for the screen.
|
||||
|
||||
This must be used instead of ``pygame.display.set_mode()``.
|
||||
|
||||
Attributes:
|
||||
image: The pygame.display screen.
|
||||
rect: ``pygame.Rect`` containing screen size.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("_a", "rect", "image", "_opengl")
|
||||
|
||||
_a = 1 # Base alpha for OpenGLImage's
|
||||
_opengl = False
|
||||
|
||||
def __init__(self, size, flags=0, depth=0):
|
||||
"""
|
||||
Args:
|
||||
size, flags, depth: Arguments for pygame.display.set_mode()
|
||||
|
||||
"""
|
||||
self.rect = Rect((0,0), size)
|
||||
self.image = pygame.display.set_mode(size, flags, depth)
|
||||
if flags & OPENGL:
|
||||
self._opengl = True
|
||||
widgets._locals.SCREEN = self
|
||||
widgets._locals.Font.set_fonts()
|
||||
|
||||
def __getattr__(self, atr):
|
||||
return getattr(self.image, atr)
|
40
sgc/widgets/__init__.py
Normal file
40
sgc/widgets/__init__.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
"""
|
||||
All widgets are imported into widget's namespace. This means you can access
|
||||
widgets without their modules, such as ``sgc.widgets.Button``.
|
||||
|
||||
Widgets:
|
||||
:py:class:`Simple<base_widget.Simple>`: Simple widget that does nothing. May be useful for images etc.
|
||||
:py:class:`Button<button.Button>`: Clickable button.
|
||||
:py:class:`FPSCounter<fps_counter.FPSCounter>`: FPS counter.
|
||||
:py:class:`InputBox<input_box.InputBox>`: Input box.
|
||||
:py:class:`Label<label.Label>`: Label.
|
||||
:py:class:`Menu<menu.Menu>`: Game menu.
|
||||
:py:class:`Radio<radio_button.Radio>`: Radio button.
|
||||
:py:class:`settings`: TODO (Stay away). Common user settings (keymap etc.)
|
||||
:py:class:`Toggle<toggle.Toggle>`: Toggle button.
|
||||
|
||||
Container widgets:
|
||||
:py:class:`Container<container.Container>`: Basic container, holds a group of other widgets and handles
|
||||
focus between them.
|
||||
:py:class:`VBox<boxes.VBox>`: Automatically aligns widgets into a vertical column.
|
||||
:py:class:`HBox<boxes.HBox>`: Automatically aligns widgets into a horizontal row.
|
||||
:py:class:`Dialog<dialog.Dialog>`: Dialog window.
|
||||
:py:class:`ScrollBox<scroll_box.ScrollBox>`: Allows another widget to be scrollable.
|
||||
|
||||
"""
|
||||
|
||||
from . import *
|
||||
from _locals import update, event
|
||||
from base_widget import Simple
|
||||
from boxes import VBox, HBox
|
||||
from button import Button
|
||||
from container import Container
|
||||
from dialog import Dialog
|
||||
from fps_counter import FPSCounter
|
||||
from input_box import InputBox
|
||||
from label import Label
|
||||
from menu import Menu
|
||||
from radio_button import Radio
|
||||
from scroll_box import ScrollBox
|
||||
from settings import Keys
|
||||
from toggle import Toggle
|
368
sgc/widgets/_locals.py
Normal file
368
sgc/widgets/_locals.py
Normal file
|
@ -0,0 +1,368 @@
|
|||
#!/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[:]
|
299
sgc/widgets/base_widget.py
Normal file
299
sgc/widgets/base_widget.py
Normal file
|
@ -0,0 +1,299 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2012 Sam Bull
|
||||
|
||||
"""
|
||||
Base widget, all widgets inherit from this.
|
||||
|
||||
"""
|
||||
|
||||
import pygame
|
||||
from pygame.locals import Rect
|
||||
|
||||
from _locals import *
|
||||
from _locals import (has_focus, is_active, add_widget, remove_widget_order,
|
||||
set_cursor, remove_cursor)
|
||||
try:
|
||||
import opengl
|
||||
except ImportError:
|
||||
print "OpenGL widgets disabled."
|
||||
|
||||
class Simple(pygame.sprite.Sprite):
|
||||
|
||||
"""
|
||||
Widget foundations all widgets should inherit from.
|
||||
This can also be used as a simple widget that does nothing, such as
|
||||
displaying an image.
|
||||
|
||||
Attributes:
|
||||
image: The current surface that will be drawn to the screen.
|
||||
rect: The ``pygame.Rect`` used for the widget's position and size.
|
||||
rect_abs: A ``pygame.Rect`` using the absolute screen position.
|
||||
pos: The widget's position. Can be retrieved or assigned as a shortcut
|
||||
for rect.topleft. Also a shortcut for setting pos through config().
|
||||
pos_abs: The widget's absolute screen position.
|
||||
|
||||
"""
|
||||
|
||||
# Widget settings
|
||||
_can_focus = False
|
||||
_modal = False
|
||||
_layered = False # Layered updates for dialog windows etc.
|
||||
_default_size = None
|
||||
_parent = None
|
||||
_available_images = ()
|
||||
_extra_images = {}
|
||||
_settings_default = {}
|
||||
|
||||
_fade = None # Alpha level when fading
|
||||
_fade_up = True
|
||||
_custom_image = False
|
||||
|
||||
def __init__(self, surf=None, **kwargs):
|
||||
"""
|
||||
Args:
|
||||
surf: The surface that should be drawn to screen, of type:
|
||||
pygame.Surface: Use an existing surface.
|
||||
tuple,list: Contains size as (width,height), creates a new surface.
|
||||
str: Contains file name to load an image.
|
||||
dict: Contains multiple images to be loaded. The documentation will
|
||||
specify if a widget uses multiple images and what names to use.
|
||||
kwargs: Any number of keyword arguments matching those for config().
|
||||
|
||||
"""
|
||||
pygame.sprite.Sprite.__init__(self)
|
||||
|
||||
# Implicitly pass attributes into _draw()
|
||||
# Used to reduce complexity for widget developers
|
||||
draw = self._draw
|
||||
self._draw = lambda d=self.get_draw(): draw(d)
|
||||
|
||||
# Initialise attributes
|
||||
self._images = {}
|
||||
self._available_images = ("image",) + self._available_images
|
||||
self._settings = self._settings_default.copy()
|
||||
self.rect = Rect((0,0), (0,0))
|
||||
|
||||
# Use default size if none specified
|
||||
if surf is None:
|
||||
surf = self._default_size
|
||||
|
||||
# Create base surfaces if not None.
|
||||
# If None, widget is expected to call this function later.
|
||||
if surf is not None:
|
||||
self._create_base_images(surf)
|
||||
|
||||
self.config(init=None, **kwargs)
|
||||
|
||||
|
||||
def config(self, **kwargs):
|
||||
"""
|
||||
Update widget configuration and redraw the widget.
|
||||
|
||||
Keyword Args:
|
||||
pos: ``tuple`` (x,y) Position to set widget to.
|
||||
|
||||
"""
|
||||
if "pos" in kwargs:
|
||||
self.rect.topleft = kwargs["pos"]
|
||||
self._config(**kwargs)
|
||||
self._draw()
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""Widgets should overload for custom widget configuration."""
|
||||
pass
|
||||
|
||||
def add(self, order=None, fade=True):
|
||||
"""
|
||||
Add widget to screen.
|
||||
|
||||
Args:
|
||||
order: Integer representing the order widget should receive focus
|
||||
when user presses TAB. The widget with the lowest order will
|
||||
receive focus first, then moving up with increasing values.
|
||||
fade: True if widget should fade in, False if not.
|
||||
|
||||
"""
|
||||
added = add_widget(self, order)
|
||||
|
||||
# Fade widget in
|
||||
if fade:
|
||||
self._fade_up = True
|
||||
if added and self._fade is None: self._fade = 1
|
||||
self.image.set_alpha(self._fade)
|
||||
else:
|
||||
self._fade = None
|
||||
self.image.set_alpha(255)
|
||||
|
||||
def remove(self, fade=True):
|
||||
"""
|
||||
Remove widget from screen.
|
||||
|
||||
Args:
|
||||
fade: True if widget should fade out.
|
||||
|
||||
"""
|
||||
if fade: # Fade widget out
|
||||
self._fade_up = False
|
||||
if self._fade == None: self._fade = 250
|
||||
else: # Remove widget immediately
|
||||
self.kill()
|
||||
remove_widget_order(self)
|
||||
|
||||
def active(self):
|
||||
"""Return True if widget is active (onscreen)."""
|
||||
return is_active(self)
|
||||
|
||||
def has_focus(self):
|
||||
"""Return True if this widget has focus."""
|
||||
return has_focus(self)
|
||||
|
||||
def get_draw(self):
|
||||
"""
|
||||
Return appropriate draw module for pygame or OpenGL.
|
||||
|
||||
Use like:
|
||||
``draw = self.get_draw()``
|
||||
``draw.rect(self.image, ...)``
|
||||
|
||||
"""
|
||||
if not get_screen()._opengl:
|
||||
return pygame.draw
|
||||
else:
|
||||
return opengl.draw
|
||||
|
||||
def update(self, time):
|
||||
"""Placeholder for function that updates the widget per frame."""
|
||||
pass
|
||||
|
||||
def _event(self, event):
|
||||
"""Placeholder for function that receives event for the widget."""
|
||||
pass
|
||||
|
||||
def _draw(self, draw):
|
||||
"""Widgets should overload to draw default images."""
|
||||
pass
|
||||
|
||||
def _create_base_images(self, surf, parent=None):
|
||||
"""
|
||||
Creates the base surfaces to draw on, or uses existing images.
|
||||
|
||||
If self._default_size is None, widget is expected to call this
|
||||
function manually when no size is given.
|
||||
|
||||
"""
|
||||
Image = opengl.OpenGLImage if get_screen()._opengl else pygame.Surface
|
||||
def create_image(surf):
|
||||
"""Return a created surface."""
|
||||
if isinstance(surf, pygame.Surface):
|
||||
surf.set_colorkey(0)
|
||||
self._custom_image = True
|
||||
return surf
|
||||
elif isinstance(surf, (tuple,list)):
|
||||
self._custom_image = False
|
||||
if isinstance(surf[0], (tuple,list)):
|
||||
surf = (self.rect.w * surf[0][0] + surf[0][1],
|
||||
self.rect.h * surf[1][0] + surf[1][1])
|
||||
surf = Image(surf)
|
||||
surf.set_colorkey(0)
|
||||
return surf
|
||||
elif isinstance(surf, str):
|
||||
self._custom_image = True
|
||||
return pygame.image.load(surf).convert_alpha()
|
||||
|
||||
# Create base images
|
||||
if isinstance(surf, dict):
|
||||
for img in surf:
|
||||
assert (img in self._available_images or
|
||||
img in self._extra_images), "Incorrect image."
|
||||
self._images[img] = create_image(surf[img])
|
||||
else:
|
||||
self._images["image"] = create_image(surf)
|
||||
|
||||
# Copy other images, if any have not been supplied.
|
||||
assert "image" in self._images, "Must supply 'image'"
|
||||
for count, name in enumerate(self._available_images):
|
||||
if name not in self._images:
|
||||
img = self._images[self._available_images[count-1]]
|
||||
self._images[name] = img.copy()
|
||||
|
||||
self.image = self._images["image"].copy()
|
||||
self.rect.size = self.image.get_size()
|
||||
|
||||
# Set up extra images
|
||||
for name in self._extra_images:
|
||||
if name not in self._images:
|
||||
self._images[name] = create_image(self._extra_images[name])
|
||||
|
||||
def _change_focus(self, forward=True):
|
||||
"""
|
||||
Called when focus should be changed. Container widget
|
||||
should override this function.
|
||||
|
||||
Args:
|
||||
forward: True if toggling focus forwards, False if backwards.
|
||||
|
||||
Returns:
|
||||
True if widget should change focus from this widget.
|
||||
|
||||
"""
|
||||
return True
|
||||
|
||||
def _focus_enter(self, focus=0):
|
||||
"""
|
||||
Called when the widget gains focus.
|
||||
|
||||
Args:
|
||||
focus: 1 if focused by keyboard, 2 if by mouse.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def _focus_exit(self):
|
||||
"""Called when the widget loses focus."""
|
||||
pass
|
||||
|
||||
def _dotted_rect(self, col=(255,255,255)):
|
||||
"""Draw a dotted rectangle to show keyboard focus."""
|
||||
self.image.lock()
|
||||
for i in range(0, self.rect.w, 3):
|
||||
# Draw horizontal lines
|
||||
self.image.set_at((i, 0), col)
|
||||
self.image.set_at((i, self.rect.h-1), col)
|
||||
for i in range(0, self.rect.h, 2):
|
||||
# Draw vertical lines
|
||||
self.image.set_at((0, i), col)
|
||||
self.image.set_at((self.rect.w-1, i), col)
|
||||
self.image.unlock()
|
||||
|
||||
def _set_cursor(self, size, hotspot, xormasks, andmasks):
|
||||
set_cursor(self, size, hotspot, xormasks, andmasks)
|
||||
|
||||
def _remove_cursor(self):
|
||||
remove_cursor(self)
|
||||
|
||||
|
||||
# --PROPERTIES--
|
||||
|
||||
@property
|
||||
def rect_abs(self):
|
||||
if self._parent is None:
|
||||
return self.rect
|
||||
else:
|
||||
p_abs = self._parent.pos_abs
|
||||
p = (self.rect.x + p_abs[0], self.rect.y + p_abs[1])
|
||||
return Rect(p, self.rect.size)
|
||||
|
||||
@property
|
||||
def pos(self):
|
||||
return self.rect.topleft
|
||||
@pos.setter
|
||||
def pos(self, value):
|
||||
self.rect.topleft = value
|
||||
@property
|
||||
def pos_abs(self):
|
||||
if self._parent is None:
|
||||
return self.rect.topleft
|
||||
else:
|
||||
p_abs = self._parent.pos_abs
|
||||
return (self.rect.x + p_abs[0], self.rect.y + p_abs[1])
|
74
sgc/widgets/boxes.py
Normal file
74
sgc/widgets/boxes.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2011-2012 Sam Bull
|
||||
|
||||
"""
|
||||
Boxes are container widgets with automatic positioning/padding of widgets.
|
||||
|
||||
"""
|
||||
|
||||
from container import Container
|
||||
|
||||
class VBox(Container):
|
||||
|
||||
"""
|
||||
VBox is a container widget which sorts widgets into a vertical
|
||||
structure.
|
||||
|
||||
If ``surf`` is not given, container will be the right size to fit all
|
||||
widgets.
|
||||
|
||||
"""
|
||||
|
||||
_settings_default = {"border": 5, "spacing": 5, "col": 0, "widgets": None}
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
widgets: ``list`` Contains widgets to pack into box.
|
||||
The order of widgets in the list denotes order they are packed.
|
||||
border: ``int`` Number of pixels to space around edges when ``surf``
|
||||
is not given.
|
||||
col: ``tuple`` (r,g,b) Colour for background, 0 is transparent.
|
||||
spacing: ``int`` Number of pixels to space between widgets.
|
||||
|
||||
"""
|
||||
if "spacing" in kwargs:
|
||||
self._settings["spacing"] = kwargs["spacing"]
|
||||
if "widgets" in kwargs:
|
||||
pos = 0
|
||||
for w in kwargs["widgets"]:
|
||||
w.pos = (0, pos)
|
||||
pos += w.rect.h + self._settings["spacing"]
|
||||
Container._config(self, **kwargs)
|
||||
|
||||
class HBox(Container):
|
||||
|
||||
"""
|
||||
HBox is a container widget which sorts widgets into a horizontal
|
||||
structure.
|
||||
|
||||
If ``surf`` is not given, container will be the right size to fit all
|
||||
widgets.
|
||||
|
||||
"""
|
||||
|
||||
_settings_default = {"border": 5, "spacing": 5, "col": 0, "widgets": None}
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
widgets: ``list`` Contains widgets to pack into box.
|
||||
The order of widgets in the list denotes order they are packed.
|
||||
border: ``int`` Number of pixels to space around edges when ``surf``
|
||||
is not given.
|
||||
col: ``tuple`` (r,g,b) Colour for background, 0 is transparent.
|
||||
spacing: ``int`` Number of pixels to space between widgets.
|
||||
|
||||
"""
|
||||
if "spacing" in kwargs:
|
||||
self._settings["spacing"] = kwargs["spacing"]
|
||||
if "widgets" in kwargs:
|
||||
pos = 0
|
||||
for w in kwargs["widgets"]:
|
||||
w.pos = (pos, 0)
|
||||
pos += w.rect.w + self._settings["spacing"]
|
||||
Container._config(self, **kwargs)
|
162
sgc/widgets/button.py
Normal file
162
sgc/widgets/button.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2012 Sam Bull
|
||||
|
||||
"""
|
||||
Button widget, allows input from the user clicking the button.
|
||||
|
||||
"""
|
||||
|
||||
import pygame
|
||||
from pygame.locals import *
|
||||
|
||||
from _locals import *
|
||||
from base_widget import Simple
|
||||
|
||||
class Button(Simple):
|
||||
|
||||
"""
|
||||
A clickable button.
|
||||
|
||||
Images:
|
||||
'image': The default button state.
|
||||
'over': The image used when the cursor is hovering over the button.
|
||||
'down': The image used when the user is clicking down on the button.
|
||||
|
||||
"""
|
||||
|
||||
_can_focus = True
|
||||
_default_size = (110, 50)
|
||||
_available_images = ("over", "down")
|
||||
_settings_default = {"label": ("",), "col": (127, 127, 169),
|
||||
"label_col": Font.col}
|
||||
|
||||
_state = None
|
||||
_draw_rect = False
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
label: ``str`` Text to display on the button.
|
||||
col: ``tuple`` (r,g,b) The central colour used if no image is
|
||||
provided. If you want to avoid the colours saturating keep the
|
||||
RGB values below 200.
|
||||
label_col: ``tuple`` (r,g,b) The text colour for the button's label.
|
||||
|
||||
"""
|
||||
# Label in middle of button
|
||||
if "label" in kwargs:
|
||||
# Save string as first argument
|
||||
self._settings["label"] = [kwargs["label"]]
|
||||
self._draw_label()
|
||||
if "col" in kwargs:
|
||||
self._settings["col"] = kwargs["col"]
|
||||
if "label_col" in kwargs:
|
||||
self._settings["label_col"] = kwargs["label_col"]
|
||||
self._draw_label()
|
||||
|
||||
def _draw_label(self):
|
||||
# Clear previous renderings
|
||||
del self._settings["label"][1:]
|
||||
label = self._settings["label"][0].split("\n")
|
||||
for count, line in enumerate(label):
|
||||
lbl = Simple(Font["widget"].render(line, True,
|
||||
self._settings["label_col"]))
|
||||
self._settings["label"].append(lbl)
|
||||
y = (self.rect.h - (lbl.rect.h * len(label))) / 2 + \
|
||||
(lbl.rect.h * count)
|
||||
lbl.rect.midtop = (self.rect.w/2, y)
|
||||
|
||||
def _draw(self, draw):
|
||||
# Frames around edge of button
|
||||
x = min(self.image.get_size()) / 8
|
||||
self._frame_lt = ((0,0), (self.rect.w,0), (self.rect.w-x,x),
|
||||
(x,x), (x,self.rect.h-x), (0,self.rect.h))
|
||||
self._frame_rb = ((self.rect.w,self.rect.h),
|
||||
(0,self.rect.h), (x,self.rect.h-x),
|
||||
(self.rect.w-x,self.rect.h-x),
|
||||
(self.rect.w-x,x), (self.rect.w,0))
|
||||
cols = {}
|
||||
cols["image"] = self._settings["col"]
|
||||
cols["over"] = [min(c*1.1, 255) for c in self._settings["col"]]
|
||||
cols["down"] = [c*0.8 for c in self._settings["col"]]
|
||||
for img in cols:
|
||||
if not self._custom_image:
|
||||
self._images[img].fill(cols[img])
|
||||
# Draw a frame around the edges of the button
|
||||
frame_lt_c = [min(c*1.3,255) for c in cols[img]]
|
||||
frame_rb_c = [c*0.8 for c in cols[img]]
|
||||
draw.polygon(self._images[img], frame_lt_c, self._frame_lt)
|
||||
draw.polygon(self._images[img], frame_rb_c, self._frame_rb)
|
||||
# Blit label onto button
|
||||
for line in self._settings["label"][1:]:
|
||||
self._images[img].blit(line.image, line.pos)
|
||||
self._draw_button()
|
||||
|
||||
def activate(self):
|
||||
"""
|
||||
Called when the button is activated.
|
||||
|
||||
Emits an event with attribute 'gui_type' == "activate".
|
||||
|
||||
Override this function to use as a callback handler.
|
||||
|
||||
"""
|
||||
ev = pygame.event.Event(GUI, {"gui_type": "activate",
|
||||
"widget_type": self.__class__,
|
||||
"widget":self})
|
||||
pygame.event.post(ev)
|
||||
|
||||
def update(self, time):
|
||||
"""Update the button each frame."""
|
||||
if self.rect_abs.collidepoint(pygame.mouse.get_pos()):
|
||||
if self._state not in ("over","down"):
|
||||
# Draw over state
|
||||
self._state = "over"
|
||||
self._draw_button()
|
||||
elif self._state not in ("off","down"):
|
||||
# Draw normal state
|
||||
self._state = "off"
|
||||
self._draw_button()
|
||||
|
||||
def _event(self, event):
|
||||
"""Respond to events."""
|
||||
if event.type == MOUSEBUTTONDOWN and event.button == 1:
|
||||
# Draw down state
|
||||
self._state = "down"
|
||||
self._draw_button()
|
||||
elif event.type == MOUSEBUTTONUP and event.button == 1:
|
||||
self._state = None
|
||||
# If releasing mouse on button, call function
|
||||
if self.rect_abs.collidepoint(event.pos):
|
||||
self.activate()
|
||||
elif event.type == KEYDOWN:
|
||||
if event.key in (K_SPACE, K_RETURN):
|
||||
self._state = "down"
|
||||
self._draw_button()
|
||||
self.activate()
|
||||
elif event.type == KEYUP:
|
||||
if event.key in (K_SPACE, K_RETURN):
|
||||
self._state = None
|
||||
|
||||
def _focus_enter(self, focus):
|
||||
"""Draw rectangle when focus is gained from keyboard."""
|
||||
if focus == 1:
|
||||
self._draw_rect = True
|
||||
self._draw_button()
|
||||
|
||||
def _focus_exit(self):
|
||||
"""Stop drawing rectangle when focus is lost."""
|
||||
self._draw_rect = False
|
||||
self._draw_button()
|
||||
|
||||
def _draw_button(self):
|
||||
"""Draw the button."""
|
||||
if self._state == "off":
|
||||
self.image = self._images["image"].copy()
|
||||
elif self._state == "over":
|
||||
self.image = self._images["over"].copy()
|
||||
elif self._state == "down":
|
||||
self.image = self._images["down"].copy()
|
||||
# Draw dotted rectangle to show keyboard focus
|
||||
if self._draw_rect:
|
||||
self._dotted_rect()
|
132
sgc/widgets/container.py
Normal file
132
sgc/widgets/container.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2012 Sam Bull
|
||||
|
||||
"""
|
||||
Container widget, can be inherited to implement more complex behaviour.
|
||||
|
||||
"""
|
||||
|
||||
import pygame.sprite
|
||||
from pygame.locals import *
|
||||
|
||||
from _locals import *
|
||||
from _locals import Focus
|
||||
from base_widget import Simple
|
||||
|
||||
class Container(Simple):
|
||||
|
||||
"""
|
||||
Container widget. Handles focus and events of a group
|
||||
of widgets packed into a single container.
|
||||
|
||||
If ``surf`` is not given, container will be the right size to fit all
|
||||
widgets.
|
||||
|
||||
"""
|
||||
|
||||
_can_focus = True
|
||||
_settings_default = {"border": 0, "col": 0, "widgets": None}
|
||||
|
||||
_focus = None
|
||||
_order = None
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
widgets: ``list`` Contains widgets to be added at creation time.
|
||||
The order of widgets in the list denotes order they receive
|
||||
focus when user hits :kbd:`TAB`.
|
||||
border: ``int`` Number of pixels to space around edges when ``surf``
|
||||
is not given.
|
||||
col: ``tuple`` (r,g,b) Colour for background, 0 is transparent.
|
||||
|
||||
"""
|
||||
if "border" in kwargs:
|
||||
self._settings["border"] = kwargs["border"]
|
||||
if "col" in kwargs:
|
||||
self._settings["col"] = kwargs["col"]
|
||||
if "widgets" in kwargs:
|
||||
self._settings["widgets"] = pygame.sprite.Group()
|
||||
self._focus = Focus()
|
||||
self._order = []
|
||||
pad = self._settings["border"]
|
||||
for w in kwargs["widgets"]:
|
||||
w._parent = self
|
||||
w.pos = (w.rect.x + pad, w.rect.y + pad)
|
||||
self._settings["widgets"].add(w)
|
||||
if w._can_focus: self._order.append(w)
|
||||
if not hasattr(self, "image"):
|
||||
width = max(kwargs["widgets"], key=lambda w: w.rect.right)
|
||||
width = width.rect.right + pad
|
||||
height = max(kwargs["widgets"], key=lambda w: w.rect.bottom)
|
||||
height = height.rect.bottom + pad
|
||||
self._create_base_images((width, height))
|
||||
|
||||
def update(self, time):
|
||||
"""Update widgets each frame."""
|
||||
self.image.fill(self._settings["col"])
|
||||
self._settings["widgets"].update(time)
|
||||
for w in self._settings["widgets"]:
|
||||
self.image.blit(w.image, w.pos)
|
||||
|
||||
def _event(self, event):
|
||||
"""Handle focus and send events to sub-widgets."""
|
||||
if event.type == MOUSEBUTTONDOWN:
|
||||
hit = False
|
||||
for widget in self._settings["widgets"]:
|
||||
# Check if user clicked a widget
|
||||
if widget._can_focus:
|
||||
if widget.rect_abs.collidepoint(event.pos):
|
||||
self._focus.add(2, widget)
|
||||
hit = True
|
||||
break
|
||||
# Lose focus if clicking away from widgets
|
||||
if not hit:
|
||||
self._focus.empty()
|
||||
elif event.type == KEYDOWN and event.key == K_TAB:
|
||||
# Focus number for current focused widget
|
||||
if self._focus.sprite not in self._order:
|
||||
curr_num = None
|
||||
else:
|
||||
curr_num = self._order.index(self._focus.sprite)
|
||||
if not event.mod & KMOD_SHIFT: # Move focus to next widget
|
||||
# Next focus number in the list
|
||||
if curr_num is None:
|
||||
# If nothing focused, focus first widget
|
||||
new_num = 0
|
||||
elif not self._focus.sprite._change_focus(True):
|
||||
# Test for container widgets
|
||||
new_num = curr_num
|
||||
elif curr_num >= len(self._order)-1:
|
||||
new_num = 0
|
||||
else:
|
||||
new_num = curr_num + 1
|
||||
else: # Shift key - move focus to previous widget
|
||||
if curr_num is None:
|
||||
new_num = -1
|
||||
elif not self._focus.sprite._change_focus(False):
|
||||
new_num = curr_num
|
||||
elif curr_num <= 0:
|
||||
new_num = -1
|
||||
else:
|
||||
new_num = curr_num - 1
|
||||
if curr_num != new_num:
|
||||
self._focus.add(1, self._order[new_num])
|
||||
if self._focus:
|
||||
self._focus.sprite._event(event)
|
||||
|
||||
def _change_focus(self, forward=True):
|
||||
"""Override Simple and check if focus should leave yet."""
|
||||
if self._focus and not self._focus.sprite._change_focus(forward):
|
||||
return False
|
||||
if not self._focus:
|
||||
return False
|
||||
num = self._order.index(self._focus.sprite)
|
||||
if forward and num < len(self._order)-1:
|
||||
return False
|
||||
if not forward and num > 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _focus_exit(self):
|
||||
self._focus.empty()
|
125
sgc/widgets/dialog.py
Normal file
125
sgc/widgets/dialog.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2012 Sam Bull
|
||||
|
||||
"""
|
||||
Dialog window, creates a popup window.
|
||||
|
||||
"""
|
||||
|
||||
import pygame.mouse
|
||||
from pygame.locals import *
|
||||
|
||||
from _locals import *
|
||||
from base_widget import Simple
|
||||
|
||||
class Dialog(Simple):
|
||||
|
||||
"""
|
||||
Dialog Window
|
||||
|
||||
If ``surf`` is not given, window will be large enough to fit the
|
||||
given widget.
|
||||
|
||||
"""
|
||||
|
||||
_can_focus = True
|
||||
_modal = True
|
||||
_layered = True
|
||||
_settings_default = {"title": None, "widget": None, "col_bg": (240,240,240),
|
||||
"col_border": (50,40,90)}
|
||||
|
||||
_drag = _over = False
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
widget: Widget that should be displayed in the dialog window.
|
||||
title: ``str`` Text to display in the title bar.
|
||||
col_border: ``tuple`` (r,g,b) Window decoration colour.
|
||||
col_bg: ``tuple`` (r,g,b) Background colour.
|
||||
modal: ``bool`` ``True`` if window should be modal.
|
||||
Defaults to ``True``.
|
||||
|
||||
"""
|
||||
if "widget" in kwargs:
|
||||
self._settings["widget"] = kwargs["widget"]
|
||||
self._settings["widget"]._parent = self
|
||||
self._settings["widget"].pos = (2, 20)
|
||||
if not hasattr(self, "image"):
|
||||
r = self._settings["widget"].rect
|
||||
self._create_base_images((r.w + 4, r.h + 22))
|
||||
if "title" in kwargs:
|
||||
self._settings["title"] = kwargs["title"]
|
||||
if "col_border" in kwargs:
|
||||
self._settings["col_border"] = kwargs["col_border"]
|
||||
if "col_bg" in kwargs:
|
||||
self._settings["col_bg"] = kwargs["col_bg"]
|
||||
if "modal" in kwargs:
|
||||
self._modal = kwargs["modal"]
|
||||
|
||||
def _draw(self, draw):
|
||||
# Draw window
|
||||
inner_rect = Rect((2,20), (self.rect.w-4,self.rect.h-22))
|
||||
self._images["image"].fill(self._settings["col_border"])
|
||||
self._images["image"].fill(self._settings["col_bg"], inner_rect)
|
||||
if self._settings["title"]:
|
||||
t = Simple(Font["widget"].render(
|
||||
self._settings["title"], True, Font.col), pos = (22,0))
|
||||
self._images["image"].blit(t.image, t.pos)
|
||||
# Close button
|
||||
self._close_off = Simple((16,16), parent=self)
|
||||
self._close_off.image.fill(self._settings["col_border"])
|
||||
draw.circle(self._close_off.image, (140,6,15), (8,8), 8)
|
||||
draw.line(self._close_off.image, (0,0,1), (5,5), (11,11), 3)
|
||||
draw.line(self._close_off.image, (0,0,1), (5,11), (11,5), 3)
|
||||
self._close_over = Simple((16,16), parent=self)
|
||||
self._close_over.image.fill(self._settings["col_border"])
|
||||
draw.circle(self._close_over.image, (234,14,50), (8,8), 8)
|
||||
draw.line(self._close_over.image, (0,0,1), (5,5), (11,11), 5)
|
||||
draw.line(self._close_over.image, (0,0,1), (5,11), (11,5), 5)
|
||||
self._close_off.pos = self._close_over.pos = (1,1)
|
||||
|
||||
self.image = self._images["image"].copy()
|
||||
self.image.blit(self._close_off.image, self._close_off.pos)
|
||||
|
||||
def update(self, time):
|
||||
"""Update dialog window each frame."""
|
||||
r = self._close_off.rect_abs
|
||||
if not self._over and r.collidepoint(pygame.mouse.get_pos()):
|
||||
# Display over button
|
||||
self.image = self._images["image"].copy()
|
||||
self.image.blit(self._close_over.image, self._close_over.pos)
|
||||
self._over = True
|
||||
elif self._over and not r.collidepoint(pygame.mouse.get_pos()):
|
||||
# Display normal button
|
||||
self.image = self._images["image"].copy()
|
||||
self.image.blit(self._close_off.image, self._close_off.pos)
|
||||
self._over = False
|
||||
|
||||
self._settings["widget"].update(time)
|
||||
self.image.blit(self._settings["widget"].image,
|
||||
self._settings["widget"].pos)
|
||||
|
||||
def _event(self, event):
|
||||
"""Respond to events."""
|
||||
minus_pos = lambda p1, p2: (p1[0] - p2[0], p1[1] - p2[1])
|
||||
|
||||
if event.type == MOUSEBUTTONDOWN and event.button == 1 and \
|
||||
self.rect.collidepoint(event.pos) and event.pos[1] < self.rect.y + 20:
|
||||
# Clicking title bar of window
|
||||
if self._close_off.rect_abs.collidepoint(event.pos):
|
||||
# Close button
|
||||
self.remove()
|
||||
else:
|
||||
# Initialise window drag
|
||||
self._offset = minus_pos(event.pos, self.pos)
|
||||
self._drag = True
|
||||
elif event.type == MOUSEMOTION and self._drag:
|
||||
# Move window
|
||||
self.pos = minus_pos(event.pos, self._offset)
|
||||
elif event.type == MOUSEBUTTONUP and event.button == 1 and self._drag:
|
||||
# Stop moving window
|
||||
self.pos = minus_pos(event.pos, self._offset)
|
||||
self._drag = False
|
||||
else:
|
||||
self._settings["widget"]._event(event)
|
55
sgc/widgets/fps_counter.py
Normal file
55
sgc/widgets/fps_counter.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2012 Sam Bull
|
||||
|
||||
"""
|
||||
FPS counter, display current FPS performance to the user.
|
||||
|
||||
"""
|
||||
|
||||
from _locals import *
|
||||
from base_widget import Simple
|
||||
|
||||
class FPSCounter(Simple):
|
||||
|
||||
"""
|
||||
FPS counter
|
||||
|
||||
"""
|
||||
|
||||
_default_size = (80, 30)
|
||||
_settings_default = {"label": "", "clock": None}
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
clock: ``pygame.time.Clock`` Clock used to time the game loop.
|
||||
label: ``str`` Text to display in front of the value.
|
||||
|
||||
"""
|
||||
if "clock" in kwargs:
|
||||
self._settings["clock"] = kwargs["clock"]
|
||||
if "label" in kwargs:
|
||||
self._settings["label"] = kwargs["label"]
|
||||
|
||||
def toggle(self):
|
||||
"""Toggle the FPS counter, adding or removing this widget."""
|
||||
if self.active():
|
||||
if self._fade is not None:
|
||||
if self._fade_up:
|
||||
self.remove()
|
||||
else:
|
||||
self.add()
|
||||
else:
|
||||
self.remove()
|
||||
else:
|
||||
self.add()
|
||||
|
||||
def update(self, time):
|
||||
"""Update counter each frame."""
|
||||
text = Simple(Font["widget"].render(
|
||||
self._settings["label"] +
|
||||
str(round(self._settings["clock"].get_fps(), 1)),
|
||||
True, Font.col))
|
||||
text.rect.center = (self.rect.w/2, self.rect.h/2)
|
||||
self.image.fill(0)
|
||||
self.image.blit(text.image, text.pos)
|
325
sgc/widgets/input_box.py
Normal file
325
sgc/widgets/input_box.py
Normal file
|
@ -0,0 +1,325 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2012 Sam Bull
|
||||
|
||||
"""
|
||||
Input Box for receiving text input.
|
||||
|
||||
"""
|
||||
|
||||
import pygame
|
||||
from pygame.locals import *
|
||||
|
||||
from _locals import *
|
||||
from base_widget import Simple
|
||||
|
||||
class InputBox(Simple):
|
||||
|
||||
"""
|
||||
Input box
|
||||
|
||||
Attributes:
|
||||
text: Text entered in input box. Can be set or retrieved directly.
|
||||
|
||||
Images:
|
||||
'image': The background of the input box when focused.
|
||||
'inactive': The background of the input box when not focused.
|
||||
|
||||
"""
|
||||
|
||||
_can_focus = True # Override Simple
|
||||
_default_size = (240, 30)
|
||||
_available_images = ("inactive",)
|
||||
_settings_default = {"label": "", "default": "", "blink_interval": 600,
|
||||
"col_selection": (118, 45, 215),
|
||||
"col_focus": (255,255,255),
|
||||
"col_focus_not": (200,200,200), "max_chars": 80,
|
||||
"repeat_begin": 300, "repeat_interval": 30}
|
||||
|
||||
_blink_time = 0
|
||||
_blink = True
|
||||
__cursor_pos = 0
|
||||
_r = None # Rect for position and size of input box
|
||||
_repeat_key = None
|
||||
_repeat_time = 0
|
||||
_select = None # Starting point of selection
|
||||
_offset = 6 # Offset to render text in the input box
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
default: ``str`` Contains the default text displayed when nothing
|
||||
has been entered and input box does not have focus.
|
||||
label: ``str`` Contains text to be rendered to left of widget.
|
||||
blink_interval: ``int`` Milliseconds between cursor blink.
|
||||
col_focus: ``tuple`` (r,g,b) Background colour when focused.
|
||||
col_focus_not: ``tuple`` (r,g,b) Background colour when not focused.
|
||||
col_selection: ``tuple`` (r,g,b) Colour of selection rectangle.
|
||||
max_chars: ``int`` Maximum number of characters.
|
||||
repeat_begin: ``int`` Milliseconds key is held down before repeating.
|
||||
repeat_interval: ``int`` Milliseconds between key repeats.
|
||||
text: ``str`` Set the text entered in input box.
|
||||
|
||||
"""
|
||||
if "init" in kwargs:
|
||||
self._text = []
|
||||
if "default" in kwargs:
|
||||
self._settings["default"] = kwargs["default"]
|
||||
if "label" in kwargs:
|
||||
self._settings["label"] = kwargs["label"]
|
||||
if hasattr(self, "_label"):
|
||||
self._images["image"].fill(0, self._label.rect)
|
||||
# Label to left of input box
|
||||
self._label = Simple(Font["widget"].render(self._settings["label"],
|
||||
True, Font.col))
|
||||
self._label.rect.centery = self.rect.h/2
|
||||
# Update input rect
|
||||
self._r = Rect((self._label.rect.w+5, 0),
|
||||
(self.rect.w-(self._label.rect.w+5), self.rect.h))
|
||||
if "blink_interval" in kwargs:
|
||||
self._settings["blink_interval"] = kwargs["blink_interval"]
|
||||
if "col_focus" in kwargs:
|
||||
self._settings["col_focus"] = kwargs["col_focus"]
|
||||
if "col_focus_not" in kwargs:
|
||||
self._settings["col_focus_not"] = kwargs["col_focus_not"]
|
||||
if "col_selection" in kwargs:
|
||||
self._settings["col_selection"] = kwargs["col_selection"]
|
||||
if "max_chars" in kwargs:
|
||||
self._settings["max_chars"] = kwargs["max_chars"]
|
||||
if "repeat_begin" in kwargs:
|
||||
self._settings["repeat_begin"] = kwargs["repeat_begin"]
|
||||
if "repeat_interval" in kwargs:
|
||||
self._settings["repeat_interval"] = kwargs["repeat_interval"]
|
||||
if "text" in kwargs:
|
||||
self._text = [unicode(char) for char in kwargs["text"]]
|
||||
|
||||
def _draw(self, draw):
|
||||
# Active state background
|
||||
self._images["image"].fill(self._settings["col_focus"], self._r)
|
||||
draw.rect(self._images["image"], (0,0,1), self._r, 4)
|
||||
self._images["image"].blit(self._label.image, self._label.pos)
|
||||
|
||||
# Inactive state background
|
||||
self._images["inactive"].fill(self._settings["col_focus_not"], self._r)
|
||||
draw.rect(self._images["inactive"], (0,0,1), self._r, 4)
|
||||
self._images["inactive"].blit(self._label.image, self._label.pos)
|
||||
|
||||
# Draw image in non-focus state
|
||||
self._focus_exit()
|
||||
|
||||
# Store the input text as a list
|
||||
@property
|
||||
def text(self):
|
||||
return "".join(self._text)
|
||||
@text.setter
|
||||
def text(self, txt):
|
||||
self._text = [unicode(char) for char in txt]
|
||||
# Re-evaluate cursor position.
|
||||
self._cursor_pos = self._cursor_pos
|
||||
|
||||
def activate(self):
|
||||
"""
|
||||
Called when the user hits the enter key.
|
||||
|
||||
Emits an event with attribute 'gui_type' == "activate".
|
||||
|
||||
Override this function to use as a callback handler.
|
||||
|
||||
"""
|
||||
ev = pygame.event.Event(GUI, {"gui_type": "activate",
|
||||
"widget_type": self.__class__,
|
||||
"widget": self})
|
||||
pygame.event.post(ev)
|
||||
|
||||
def update(self, time):
|
||||
"""Update the input box each frame."""
|
||||
if self.has_focus():
|
||||
draw = self.get_draw()
|
||||
# Repeat key if held down
|
||||
if self._repeat_key:
|
||||
self._repeat_time += time
|
||||
while self._repeat_time > self._settings["repeat_begin"]:
|
||||
self._repeat_time -= self._settings["repeat_interval"]
|
||||
self._event(self._repeat_key)
|
||||
# Draw input box
|
||||
text = Font["mono"].render(self.text, True, (0,0,0))
|
||||
y = (self._r.h - text.get_height()) / 2
|
||||
self.image = self._images["image"].copy()
|
||||
area = ((6-self._offset,0), (self._r.w-8, self._r.h))
|
||||
self.image.blit(text, (self._r.x+6, y), area)
|
||||
# If enough time has passed, blink cursor
|
||||
self._blink_time += time
|
||||
if self._blink_time > self._settings["blink_interval"]:
|
||||
self._blink_time -= self._settings["blink_interval"]
|
||||
self._blink = not self._blink
|
||||
# Draw cursor in box
|
||||
if self._blink and self._select is None:
|
||||
x = self._cursor_pos*Font.mono_w + self._r.x + self._offset
|
||||
draw.line(self.image, (0,0,1), (x, 6), (x, self._r.h-6))
|
||||
# Draw selection highlighting
|
||||
if self._select is not None:
|
||||
select = self._select_fix()
|
||||
# Semi-transparent selection rectangle
|
||||
w = ((select[1]*Font.mono_w + self._offset) -
|
||||
max(4, (select[0]*Font.mono_w + self._offset)))
|
||||
selection = Simple((w, self._r.h - 11))
|
||||
selection.pos = (self._r.x + select[0] * Font.mono_w +
|
||||
self._offset, 6)
|
||||
selection.image.fill(self._settings["col_selection"])
|
||||
selection.image.set_alpha(100)
|
||||
# Border around selection rectangle
|
||||
selection_b = Simple((selection.rect.w+2, selection.rect.h+2))
|
||||
draw.rect(selection_b.image, self._settings["col_selection"],
|
||||
selection_b.rect, 1)
|
||||
pos = (max(self._r.x+4, selection.rect.x), selection.rect.y)
|
||||
self.image.blit(selection.image, pos)
|
||||
self.image.blit(selection_b.image, (pos[0]-1, pos[1]-1))
|
||||
|
||||
def _event(self, event):
|
||||
"""Update text field based on input."""
|
||||
if event.type == KEYDOWN:
|
||||
# Reset cursor blink when typing
|
||||
self._blink_time = 0
|
||||
self._blink = True
|
||||
# Save last key press for repeat
|
||||
if self._repeat_key != event:
|
||||
self._repeat_key = event
|
||||
self._repeat_time = 0
|
||||
if event.key in (9,): # Keys to ignore
|
||||
pass
|
||||
elif event.key == K_ESCAPE:
|
||||
self._select = None
|
||||
elif event.key == K_RETURN:
|
||||
self.activate()
|
||||
elif event.key == K_BACKSPACE:
|
||||
if self._select is not None:
|
||||
self._delete_selection()
|
||||
elif self._cursor_pos > 0:
|
||||
self._cursor_pos -= 1
|
||||
self._text.pop(self._cursor_pos)
|
||||
elif event.key == K_DELETE:
|
||||
if self._select is not None:
|
||||
self._delete_selection()
|
||||
elif self._cursor_pos < len(self._text):
|
||||
self._text.pop(self._cursor_pos)
|
||||
elif event.key == K_LEFT:
|
||||
if not event.mod & KMOD_SHIFT:
|
||||
self._select = None # Break selection
|
||||
elif self._select is None:
|
||||
# Reset selection if not selecting
|
||||
self._select = self._cursor_pos
|
||||
self._cursor_pos -= 1
|
||||
# Remove selection when cursor is at same position
|
||||
if self._select == self._cursor_pos:
|
||||
self._select = None
|
||||
elif event.key == K_RIGHT:
|
||||
if not event.mod & KMOD_SHIFT:
|
||||
self._select = None # Break selection
|
||||
elif self._select is None:
|
||||
self._select = self._cursor_pos
|
||||
self._cursor_pos += 1
|
||||
if self._select == self._cursor_pos:
|
||||
self._select = None
|
||||
elif event.unicode:
|
||||
if event.mod & KMOD_CTRL:
|
||||
if event.key == K_a: # Select all
|
||||
self._select = 0
|
||||
self._cursor_pos = len(self._text)
|
||||
elif event.key == K_c and self._select is not None: # Copy
|
||||
select = self._select_fix()
|
||||
string = "".join(self._text[select[0]:select[1]])
|
||||
try:
|
||||
pygame.scrap.put(SCRAP_TEXT, string)
|
||||
except pygame.error:
|
||||
print "Please run 'pygame.scrap.init()'" \
|
||||
" to use the clipboard."
|
||||
elif event.key == K_v: # Paste
|
||||
text = pygame.scrap.get(SCRAP_TEXT)
|
||||
if text:
|
||||
if self._select is not None:
|
||||
self._delete_selection()
|
||||
# Get list of text to insert into input_text
|
||||
text = [unicode(char) for char in text]
|
||||
self._text[self._cursor_pos:self._cursor_pos] = text
|
||||
self._cursor_pos += len(text)
|
||||
elif event.key == K_x and self._select is not None: # Cut
|
||||
select = self._select_fix()
|
||||
string = "".join(self._text[select[0]:select[1]])
|
||||
try:
|
||||
pygame.scrap.put(SCRAP_TEXT, string)
|
||||
except pygame.error:
|
||||
print "Please run 'pygame.scrap.init()'" \
|
||||
" to use the clipboard"
|
||||
self._delete_selection()
|
||||
else:
|
||||
# Delete selection
|
||||
if self._select is not None:
|
||||
self._delete_selection()
|
||||
# Insert new character
|
||||
if len(self._text) < self._settings["max_chars"]:
|
||||
self._text.insert(self._cursor_pos, event.unicode)
|
||||
self._cursor_pos += 1
|
||||
elif event.type == KEYUP:
|
||||
if self._repeat_key and self._repeat_key.key == event.key:
|
||||
self._repeat_key = None # Stop repeat
|
||||
elif event.type == MOUSEBUTTONDOWN:
|
||||
# Begin drawing selection
|
||||
if pygame.key.get_mods() & KMOD_SHIFT and self._select is None:
|
||||
self._select = self._cursor_pos
|
||||
self._cursor_pos = self._mouse_cursor(event.pos)
|
||||
if not pygame.key.get_mods() & KMOD_SHIFT:
|
||||
self._select = self._cursor_pos
|
||||
elif event.type == MOUSEMOTION and event.buttons[0]:
|
||||
# Continue drawing selection while mouse held down
|
||||
self._cursor_pos = self._mouse_cursor(event.pos)
|
||||
elif event.type == MOUSEBUTTONUP:
|
||||
# Set cursor position with mouse click
|
||||
self._cursor_pos = self._mouse_cursor(event.pos)
|
||||
if self._select == self._cursor_pos:
|
||||
self._select = None
|
||||
|
||||
def _focus_exit(self):
|
||||
"""Draw non-focused input box when focus is lost."""
|
||||
self.image = self._images["inactive"].copy()
|
||||
if self._text: # Blit input text into box...
|
||||
text = Simple(Font["mono"].render(self.text, True, (70,70,70)))
|
||||
else: # ...or default text if empty.
|
||||
text = Simple(Font["mono"].render(self._settings["default"], True,
|
||||
(70,70,70)))
|
||||
text.rect.midleft = (self._r.x + self._offset, self._r.h / 2)
|
||||
self.image.blit(text.image, text.pos)
|
||||
# Stop repeat key
|
||||
self._repeat_key = None
|
||||
|
||||
def _mouse_cursor(self, mouse_pos):
|
||||
"""Return the text cursor position of the mouse."""
|
||||
pos = mouse_pos[0] - self.rect_abs.x - self._offset - self._r.x
|
||||
pos = int(round(float(pos) / Font.mono_w))
|
||||
return max(min(pos, len(self._text)), 0)
|
||||
|
||||
def _select_fix(self):
|
||||
"""If selection is right-to-left then reverse positions."""
|
||||
if self._select > self._cursor_pos:
|
||||
return (self._cursor_pos, self._select)
|
||||
else:
|
||||
return (self._select, self._cursor_pos)
|
||||
|
||||
def _delete_selection(self):
|
||||
"""Delete the current selection of text."""
|
||||
select = self._select_fix()
|
||||
del self._text[select[0]:select[1]]
|
||||
self._select = None
|
||||
self._cursor_pos = select[0]
|
||||
|
||||
@property
|
||||
def _cursor_pos(self):
|
||||
return self.__cursor_pos
|
||||
@_cursor_pos.setter
|
||||
def _cursor_pos(self, value):
|
||||
# Keep cursor position within text
|
||||
self.__cursor_pos = min(max(value, 0), len(self._text))
|
||||
# Scroll text in input box when it's too long
|
||||
pos = self._cursor_pos * Font.mono_w
|
||||
if pos > (self._r.w - self._offset):
|
||||
self._offset = -(pos - self._r.w + 6)
|
||||
elif pos < (6 - self._offset):
|
||||
self._offset = 6 - pos
|
229
sgc/widgets/label.py
Normal file
229
sgc/widgets/label.py
Normal file
|
@ -0,0 +1,229 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2012 Sam Bull, Michael Rochester
|
||||
|
||||
"""
|
||||
Label to display information to the user.
|
||||
|
||||
"""
|
||||
|
||||
import pygame.mouse
|
||||
from pygame.locals import *
|
||||
|
||||
from _locals import *
|
||||
from base_widget import Simple
|
||||
|
||||
class Label(Simple):
|
||||
|
||||
"""
|
||||
Label
|
||||
|
||||
Attributes:
|
||||
text: ``str`` displayed in label. Can be assigned as a shortcut for
|
||||
``config(label=)`` with no second paramenter.
|
||||
"""
|
||||
|
||||
_settings_default = {"text": "", "col": Font.col, "font": Font["widget"],
|
||||
"col_selection": (118, 45, 215)}
|
||||
|
||||
_over = False
|
||||
_select = None # Starting point of selection
|
||||
__cursor_pos = 0
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
text: Either ``str`` containing text to be displayed or
|
||||
``tuple`` containing two strings. First string is text to
|
||||
be displayed, second string is rect attribute to be used
|
||||
for position. Defaults to 'topleft' if not passing a tuple.
|
||||
col: ``tuple`` (r,g,b) Text colour.
|
||||
font: Font object the label will render with.
|
||||
selectable: ``bool`` True if the text should be selectable.
|
||||
col_selection: ``tuple`` (r,g,b) Colour of selection rectangle.
|
||||
|
||||
"""
|
||||
if "init" in kwargs:
|
||||
strings = pygame.cursors.textmarker_strings
|
||||
cursor = pygame.cursors.compile(strings)
|
||||
size = (len(strings[0]), len(strings))
|
||||
hotspot = (size[0]/2, size[1]/2)
|
||||
self._cursor = (size, hotspot) + cursor
|
||||
if "text" in kwargs:
|
||||
if isinstance(kwargs["text"], str):
|
||||
self._settings["text"] = kwargs["text"]
|
||||
else:
|
||||
self._settings["text"] = kwargs["text"][0]
|
||||
self._temp_pos = kwargs["text"][1]
|
||||
if "col" in kwargs:
|
||||
self._settings["col"] = kwargs["col"]
|
||||
if "font" in kwargs:
|
||||
self._settings["font"] = kwargs["font"]
|
||||
if "selectable" in kwargs:
|
||||
self._can_focus = kwargs["selectable"]
|
||||
if "col_selection" in kwargs:
|
||||
self._settings["col_selection"] = kwargs["col_selection"]
|
||||
|
||||
def _draw(self, draw):
|
||||
if hasattr(self, "_temp_pos"):
|
||||
pos = getattr(self.rect, self._temp_pos)
|
||||
|
||||
# Split into lines
|
||||
text = []
|
||||
for line in self._settings["text"].split("\n"):
|
||||
text.append(self._settings["font"].render(line, True,
|
||||
self._settings["col"]))
|
||||
|
||||
# Dynamically set size
|
||||
h = 0
|
||||
for line in text:
|
||||
h += line.get_size()[1]
|
||||
w = max(text, key=lambda x: x.get_size()[0])
|
||||
self._create_base_images((w.get_size()[0], h))
|
||||
|
||||
# Blit each line
|
||||
y = 0
|
||||
for line in text:
|
||||
self._images["image"].blit(line, (0,y))
|
||||
y += line.get_size()[1]
|
||||
|
||||
# Copy position attribute over
|
||||
if hasattr(self, "_temp_pos"):
|
||||
setattr(self.rect, self._temp_pos, pos)
|
||||
del self._temp_pos
|
||||
|
||||
self.image = self._images["image"].copy()
|
||||
|
||||
# Store as tuple of (pos, width) tuples.
|
||||
if self._can_focus:
|
||||
chars = []
|
||||
p = 0
|
||||
for c in range(1, len(self._settings["text"])+1):
|
||||
char = self._settings["font"].render(self._settings["text"][:c],
|
||||
True, (0,0,0))
|
||||
chars.append((p, char.get_size()[0] - p))
|
||||
p = char.get_size()[0]
|
||||
chars.append((p, 0))
|
||||
self._chars = tuple(chars)
|
||||
|
||||
def _event(self, event):
|
||||
if event.type == MOUSEBUTTONDOWN and event.button == 1:
|
||||
# Begin drawing selection
|
||||
if pygame.key.get_mods() & KMOD_SHIFT and self._select is None:
|
||||
self._select = self._cursor_pos
|
||||
self._cursor_pos = self._mouse_cursor(event.pos)
|
||||
if not pygame.key.get_mods() & KMOD_SHIFT:
|
||||
self._select = self._cursor_pos
|
||||
elif event.type == MOUSEMOTION and event.buttons[0]:
|
||||
# Continue drawing selection while mouse held down
|
||||
self._cursor_pos = self._mouse_cursor(event.pos)
|
||||
elif event.type == MOUSEBUTTONUP:
|
||||
# Set cursor position with mouse click
|
||||
self._cursor_pos = self._mouse_cursor(event.pos)
|
||||
if self._select == self._cursor_pos:
|
||||
self._select = None
|
||||
elif event.type == KEYDOWN:
|
||||
if event.key == K_ESCAPE:
|
||||
self._select = None
|
||||
elif event.key == K_LEFT:
|
||||
if not event.mod & KMOD_SHIFT:
|
||||
self._select = None # Break selection
|
||||
elif self._select is None:
|
||||
# Reset selection if not selecting
|
||||
self._select = self._cursor_pos
|
||||
self._cursor_pos -= 1
|
||||
# Remove selection when cursor is at same position
|
||||
if self._select == self._cursor_pos:
|
||||
self._select = None
|
||||
elif event.key == K_RIGHT:
|
||||
if not event.mod & KMOD_SHIFT:
|
||||
self._select = None # Break selection
|
||||
elif self._select is None:
|
||||
self._select = self._cursor_pos
|
||||
self._cursor_pos += 1
|
||||
if self._select == self._cursor_pos:
|
||||
self._select = None
|
||||
elif event.mod & KMOD_CTRL:
|
||||
if event.key == K_a: # Select all
|
||||
self._select = 0
|
||||
self._cursor_pos = len(self._settings["text"])
|
||||
elif event.key == K_c and self._select is not None: # Copy
|
||||
select = self._select_fix()
|
||||
string = "".join(
|
||||
self._settings["text"][select[0]:select[1]])
|
||||
try:
|
||||
pygame.scrap.put(SCRAP_TEXT, string)
|
||||
except pygame.error:
|
||||
print "Please run 'pygame.scrap.init()'" \
|
||||
" to use the clipboard."
|
||||
|
||||
def update(self, time):
|
||||
if self._can_focus:
|
||||
# Change cursor when mouse not held down
|
||||
if not pygame.mouse.get_pressed()[0]:
|
||||
if not self._over and \
|
||||
self.rect_abs.collidepoint(pygame.mouse.get_pos()):
|
||||
self._over = True
|
||||
self._set_cursor(*self._cursor)
|
||||
elif self._over and \
|
||||
not self.rect_abs.collidepoint(pygame.mouse.get_pos()):
|
||||
self._over = False
|
||||
self._remove_cursor()
|
||||
if self.has_focus():
|
||||
self.image = self._images["image"].copy()
|
||||
draw = self.get_draw()
|
||||
if self._select is None:
|
||||
# Draw cursor in box
|
||||
x = self._chars[self._cursor_pos][0] - 1
|
||||
draw.line(self.image, (0,0,1), (x, 2), (x, self.rect.h-2))
|
||||
else:
|
||||
select = self._select_fix()
|
||||
# Semi-transparent selection rectangle
|
||||
w = (self._chars[select[1]][0] - self._chars[select[0]][0])
|
||||
selection = Simple((w, self.rect.h - 2))
|
||||
selection.pos = (self._chars[select[0]][0], 1)
|
||||
selection.image.fill(self._settings["col_selection"])
|
||||
selection.image.set_alpha(100)
|
||||
# Border around selection rectangle
|
||||
selection_b = Simple((selection.rect.w+2,
|
||||
selection.rect.h+2))
|
||||
draw.rect(selection_b.image,
|
||||
self._settings["col_selection"],
|
||||
selection_b.rect, 1)
|
||||
self.image.blit(selection.image, selection.pos)
|
||||
self.image.blit(selection_b.image, (selection.rect.x-1,
|
||||
selection.rect.y-1))
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._settings["text"]
|
||||
@text.setter
|
||||
def text(self, value):
|
||||
self._settings["text"] = value
|
||||
self._draw()
|
||||
|
||||
def _mouse_cursor(self, mouse_pos):
|
||||
"""Return the text cursor position of the mouse."""
|
||||
pos = mouse_pos[0] - self.rect_abs.x
|
||||
for index, (p,w) in enumerate(self._chars):
|
||||
if pos <= p + w/2:
|
||||
break
|
||||
return index
|
||||
|
||||
def _select_fix(self):
|
||||
"""If selection is right-to-left then reverse positions."""
|
||||
if self._select > self._cursor_pos:
|
||||
return (self._cursor_pos, self._select)
|
||||
else:
|
||||
return (self._select, self._cursor_pos)
|
||||
|
||||
def _focus_exit(self):
|
||||
"""Cancel any selection when focus is lost."""
|
||||
self.image = self._images["image"].copy()
|
||||
|
||||
@property
|
||||
def _cursor_pos(self):
|
||||
return self.__cursor_pos
|
||||
@_cursor_pos.setter
|
||||
def _cursor_pos(self, value):
|
||||
# Keep cursor position within text
|
||||
self.__cursor_pos = min(max(value, 0), len(self._settings["text"]))
|
217
sgc/widgets/menu.py
Normal file
217
sgc/widgets/menu.py
Normal file
|
@ -0,0 +1,217 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2012 Sam Bull
|
||||
|
||||
"""
|
||||
Menu widget. Creates a menu for a game.
|
||||
|
||||
"""
|
||||
|
||||
from _locals import *
|
||||
from boxes import VBox
|
||||
from scroll_box import ScrollBox
|
||||
from . import *
|
||||
|
||||
class Menu(Simple):
|
||||
|
||||
"""
|
||||
Menu
|
||||
|
||||
Can be indexed to access widgets by name.
|
||||
|
||||
Attributes:
|
||||
func_dict: Assign a lambda to return a dictionary of functions for
|
||||
config file to utilise.
|
||||
|
||||
"""
|
||||
|
||||
_modal = True
|
||||
_layered = True
|
||||
_settings_default = {"offset": (100, 50), "col": (0,0,1), "apply": False}
|
||||
|
||||
_menus = []
|
||||
_dict = {} # Dict for indexing widgets
|
||||
_old_menu = None
|
||||
_curr_menu = 0
|
||||
func_dict = lambda self: {}
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
menu: Either tuple containing menu data, to be documented.
|
||||
Or file object to read config data from, in the same format.
|
||||
apply: TODO ``bool`` True if an apply button should be added.
|
||||
col: ``tuple`` (r,g,b), Colour used for the background.
|
||||
offset: ``tuple`` (x,y) Contains position of menu widgets. y is
|
||||
added to bottom of title.
|
||||
|
||||
"""
|
||||
if "apply" in kwargs:
|
||||
self._settings["apply"] = kwargs["apply"]
|
||||
if "col" in kwargs:
|
||||
self._settings["col"] = kwargs["col"]
|
||||
if "offset" in kwargs:
|
||||
self._settings["offset"] = kwargs["offset"]
|
||||
if "menu" in kwargs:
|
||||
self._menus = [] # Remove previous menus
|
||||
self._dict = {}
|
||||
# Grab function dictionary
|
||||
self._funcs = self.func_dict()
|
||||
|
||||
# Set size to screen size
|
||||
if not hasattr(self, "image"):
|
||||
self._create_base_images(get_screen().rect.size)
|
||||
|
||||
menu = kwargs["menu"]
|
||||
# If file, read config tuple
|
||||
if isinstance(menu, file):
|
||||
menu_data = "".join(menu.readlines())
|
||||
menu = eval(menu_data)
|
||||
assert isinstance(menu, tuple)
|
||||
|
||||
menu_data = [(menu, None)] # (data, parent)
|
||||
# Create each submenu, by iterating through the menu data
|
||||
while menu_data:
|
||||
self._menus.append(_SubMenu(self.rect.size,
|
||||
col=self._settings["col"]))
|
||||
self._config_menu(menu_data, self._menus[-1])
|
||||
|
||||
def _config_menu(self, data_queue, menu):
|
||||
"""
|
||||
Configure the passed in menu, using the information from the first
|
||||
item in data_queue.
|
||||
|
||||
New sub-menus discovered in the data will be appended to the data_queue
|
||||
for later processing.
|
||||
|
||||
"""
|
||||
data, parent = data_queue.pop(0)
|
||||
widgets = []
|
||||
# Parse menu data
|
||||
for item in data:
|
||||
# Title
|
||||
if isinstance(item, str):
|
||||
menu._title = Simple(Font["title"].render(item[2:], True,
|
||||
Font.col))
|
||||
menu._title.rect.midtop = (self.rect.centerx, 40)
|
||||
# Sub-menu
|
||||
elif item[0].startswith("m:"):
|
||||
data_queue.append((item, len(self._menus)-1))
|
||||
surf = Font["widget"].render(item[0][2:], True, Font.col)
|
||||
widgets.append(Button(surf))
|
||||
# Change menu on button activate
|
||||
num = len(self._menus)-1 + len(data_queue)
|
||||
widgets[-1].activate = lambda n=num: self.change_menu(n)
|
||||
# Category/divider
|
||||
elif item[0].startswith("c:"):
|
||||
div = Simple(Font["widget"].render(item[0][2:], True,
|
||||
Font.col))
|
||||
self.get_draw().line(div.image, Font.col, (0, div.rect.h-1),
|
||||
(div.rect.w, div.rect.h-1))
|
||||
widgets.append(div)
|
||||
# Widget
|
||||
elif item[0].startswith("w:"):
|
||||
args = self._get_args(item[1:])
|
||||
name = args.pop("name")
|
||||
f = args.pop("func") if ("func" in args) else None
|
||||
if item[0].endswith("input_box"):
|
||||
widget = input_box.InputBox(**args)
|
||||
elif item[0].endswith("button"):
|
||||
for key in args:
|
||||
if key == "surf":
|
||||
args[key] = eval(args[key])
|
||||
widget = button.Button(**args)
|
||||
elif item[0].endswith("label"):
|
||||
widget = label.Label(**args)
|
||||
self._dict[name] = widget
|
||||
if f: widget.activate = self._funcs[f]
|
||||
widgets.append(widget)
|
||||
# Function
|
||||
elif item[0].startswith("f:"):
|
||||
surf = Font["widget"].render(item[1], True, Font.col)
|
||||
widgets.append(button.Button(surf=surf))
|
||||
widgets[-1].activate = self._funcs[item[0][2:]]
|
||||
|
||||
# Draw a back menu item
|
||||
if parent is not None:
|
||||
surf = Font["widget"].render("Back", True, Font.col)
|
||||
widgets.append(button.Button(surf=surf))
|
||||
widgets[-1].activate = lambda n=parent: self.change_menu(n)
|
||||
|
||||
menu._widgets = tuple(widgets)
|
||||
|
||||
def _draw(self, draw):
|
||||
for menu in self._menus:
|
||||
# Pack all widgets into a VBox
|
||||
box = VBox(widgets=menu._widgets, spacing=15)
|
||||
pos = (self._settings["offset"][0],
|
||||
self._settings["offset"][1] + menu._title.rect.bottom)
|
||||
box = ScrollBox((self.rect.w - pos[0], self.rect.h - pos[1]),
|
||||
widget=box, pos=pos)
|
||||
menu.config(col=self._settings["col"], menu=box)
|
||||
|
||||
def change_menu(self, menu_num):
|
||||
"""
|
||||
Change the currently displayed menu.
|
||||
|
||||
Args:
|
||||
menu_num: ``int`` The number representing the menu.
|
||||
|
||||
"""
|
||||
self._old_menu = self._curr_menu
|
||||
self._curr_menu = menu_num
|
||||
self._menus[self._curr_menu]._fade = 0
|
||||
|
||||
def update(self, time):
|
||||
menu = self._menus[self._curr_menu]
|
||||
menu.update(time)
|
||||
if self._old_menu is not None:
|
||||
self.image.blit(self._menus[self._old_menu].image, (0,0))
|
||||
menu.image.set_alpha(menu._fade)
|
||||
menu._fade += time / 3.
|
||||
if menu._fade >= 255:
|
||||
menu._fade = None
|
||||
self._old_menu = None
|
||||
menu.image.set_alpha(255)
|
||||
self.image.blit(menu.image, (0,0))
|
||||
|
||||
def _event(self, event):
|
||||
self._menus[self._curr_menu]._event(event)
|
||||
|
||||
def _get_args(self, args):
|
||||
"""Get the arguments passed in, saving them into a dictionary."""
|
||||
return {arg.split("=")[0]: arg.split("=")[1] for arg in args}
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Return widgets by name."""
|
||||
return self._dict[key]
|
||||
|
||||
class _SubMenu(Simple):
|
||||
|
||||
"""
|
||||
A single menu object to be created and managed by the Menu class.
|
||||
|
||||
"""
|
||||
_settings_default = {"col": (0,0,1), "menu": None}
|
||||
|
||||
_title = None
|
||||
_widgets = ()
|
||||
|
||||
def _config(self, **kwargs):
|
||||
if "col" in kwargs:
|
||||
self._settings["col"] = kwargs["col"]
|
||||
if "menu" in kwargs:
|
||||
self._settings["menu"] = kwargs["menu"]
|
||||
|
||||
def _draw(self, draw):
|
||||
self._images["image"].fill(self._settings["col"])
|
||||
if self._title:
|
||||
self._images["image"].blit(self._title.image, self._title.pos)
|
||||
|
||||
def update(self, time):
|
||||
self.image = self._images["image"].copy()
|
||||
self._settings["menu"].update(time)
|
||||
self.image.blit(self._settings["menu"].image,self._settings["menu"].pos)
|
||||
|
||||
def _event(self, event):
|
||||
"""Send events to container."""
|
||||
self._settings["menu"]._event(event)
|
255
sgc/widgets/opengl.py
Normal file
255
sgc/widgets/opengl.py
Normal file
|
@ -0,0 +1,255 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2011 Sam Bull
|
||||
|
||||
"""
|
||||
Base widget, all widgets inherit from this.
|
||||
|
||||
"""
|
||||
|
||||
from pygame.locals import Rect
|
||||
from OpenGL.GL import *
|
||||
import FTGL
|
||||
|
||||
from ..surface import SurfaceBase
|
||||
|
||||
|
||||
class OpenGLImage(SurfaceBase):
|
||||
"""
|
||||
Class used to emulate the interface of Surface for OpenGL drawing.
|
||||
|
||||
Functions should be used in the same manner as pygame.Surface.
|
||||
Differences are shown in documentation, otherwise assume it to
|
||||
function the same as the equivalent pygame.Surface function.
|
||||
|
||||
"""
|
||||
def __init__(self, surf, parent=None, **kwargs):
|
||||
self._a = None
|
||||
self._lock = False
|
||||
self.display_list = []
|
||||
self._children = []
|
||||
if parent:
|
||||
self._parent = parent # Parent surface used for _abs
|
||||
else:
|
||||
self._parent = self._default_screen
|
||||
|
||||
self._rect = Rect((0,0), (0,0))
|
||||
if isinstance(surf, (tuple,list)):
|
||||
self._rect.size = surf
|
||||
elif isinstance(surf, OpenGLFont):
|
||||
self._rect.size = (surf.size)
|
||||
self.display_list.extend(surf.display_list)
|
||||
elif isinstance(surf, OpenGLImage):
|
||||
self._rect = surf._rect
|
||||
self._a = surf._a
|
||||
self.display_list = surf.display_list
|
||||
self._children = surf._children
|
||||
|
||||
def blit(self, surf, pos=None):
|
||||
assert isinstance(surf, OpenGLImage)
|
||||
if surf not in self._children:
|
||||
if pos is not None: surf.pos = pos
|
||||
surf._parent = self
|
||||
self._children.append(surf)
|
||||
|
||||
def draw(self):
|
||||
glLoadIdentity()
|
||||
if self.rect.w <= self._parent.rect.w and \
|
||||
self.rect.h <= self._parent.rect.h:
|
||||
# Mask the area, so nothing is drawn outside surface area.
|
||||
bottom = self._default_screen.h - self.rect_abs.bottom
|
||||
glScissor(self.rect_abs.x, bottom,
|
||||
self.rect.w+1, self.rect.h+1)
|
||||
glTranslatef(self.pos_abs[0], self.pos_abs[1], 0)
|
||||
for dl,col in self.display_list:
|
||||
if col is not None: glColor(col[0], col[1], col[2], self.a)
|
||||
glCallList(dl)
|
||||
for child in self._children:
|
||||
child.draw()
|
||||
|
||||
def fill(self, col=0, rect=None, special_flags=0):
|
||||
"""If col == 0 and rect is None, clears image with no fill."""
|
||||
if rect is None:
|
||||
# Clear the surface
|
||||
self.display_list = []
|
||||
self._children = []
|
||||
rect = Rect((0,0), self.size)
|
||||
if col != 0:
|
||||
col = [c/255. for c in col]
|
||||
dl = glGenLists(1)
|
||||
glNewList(dl, GL_COMPILE)
|
||||
glRectfv(rect.topleft, rect.bottomright)
|
||||
glEndList()
|
||||
self.display_list.append((dl, col))
|
||||
|
||||
def copy(self):
|
||||
return OpenGLImage(self)
|
||||
|
||||
def get_size(self):
|
||||
return self.size
|
||||
|
||||
def set_alpha(self, alpha):
|
||||
self._a = alpha/255.
|
||||
|
||||
def set_at(self, vertex, col):
|
||||
col = [c/255. for c in col]
|
||||
|
||||
if self._lock:
|
||||
glColor(*col)
|
||||
else:
|
||||
dl = glGenLists(1)
|
||||
glNewList(dl, GL_COMPILE)
|
||||
glBegin(GL_POINTS)
|
||||
|
||||
glVertex(vertex)
|
||||
|
||||
if not self._lock:
|
||||
glEnd()
|
||||
glEndList()
|
||||
surf.display_list.append((dl, col))
|
||||
|
||||
def lock(self):
|
||||
"""
|
||||
Should only be used for multiple set_at() calls.
|
||||
Should not be used in conjuction with the draw functions.
|
||||
|
||||
"""
|
||||
self._lock = True
|
||||
self._lock_dl = glGenLists(1)
|
||||
glNewList(self._lock_dl, GL_COMPILE)
|
||||
glBegin(GL_POINTS)
|
||||
|
||||
def unlock(self):
|
||||
glEnd()
|
||||
glEndList()
|
||||
self.display_list.append((self._lock_dl, None))
|
||||
del self._lock_dl
|
||||
self._lock = False
|
||||
|
||||
def replace(self, surf, **kwargs):
|
||||
self._rect.size = surf.size
|
||||
self.display_list = surf.display_list[:]
|
||||
self._children = surf._children[:]
|
||||
|
||||
@property
|
||||
def a(self):
|
||||
if self._a is not None:
|
||||
return self._a
|
||||
else:
|
||||
return self._parent.a
|
||||
|
||||
# --- Dummy methods. Ignore. ---
|
||||
|
||||
def __call__(self):
|
||||
return self
|
||||
|
||||
def set_colorkey(self, col):
|
||||
pass
|
||||
|
||||
class OpenGLFont():
|
||||
"""
|
||||
Wraps the FTGL.TextureFont to allow it to be added
|
||||
to an OpenGLImage object in the same manner as pygame.Font.
|
||||
|
||||
"""
|
||||
def __init__(self, font, size):
|
||||
self._children = []
|
||||
self.font = FTGL.TextureFont(font)
|
||||
self.font.FaceSize(16)
|
||||
self.y_offset = self.font.line_height * .75
|
||||
|
||||
def render(self, text, antialias, color, background=None):
|
||||
text = text.encode()
|
||||
col = [c/255. for c in color]
|
||||
dl = glGenLists(1)
|
||||
self.size = (self.font.Advance(text), self.font.line_height)
|
||||
glNewList(dl, GL_COMPILE)
|
||||
glPushMatrix()
|
||||
# Flip text right way up
|
||||
glMultMatrixf((1, 0, 0, 0,
|
||||
0,-1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, self.y_offset, 0, 1))
|
||||
self.font.Render(text)
|
||||
glPopMatrix()
|
||||
glEndList()
|
||||
self.display_list = [(dl, col)]
|
||||
return self
|
||||
|
||||
class Draw():
|
||||
"""
|
||||
Class to emulate the pygame.draw module.
|
||||
Functions should work in the same manner.
|
||||
|
||||
"""
|
||||
def rect(self, surf, col, rect, width=0):
|
||||
col = [c/255. for c in col]
|
||||
dl = glGenLists(1)
|
||||
glNewList(dl, GL_COMPILE)
|
||||
if not width:
|
||||
glRectfv(rect.topleft, rect.bottomright)
|
||||
else:
|
||||
if width > 1: width -= 1
|
||||
hw = width/2.
|
||||
glLineWidth(width)
|
||||
glBegin(GL_LINES)
|
||||
glVertex(rect.x, rect.y + hw)
|
||||
glVertex(rect.right - width, rect.y + hw)
|
||||
# (hw%1) fixes line rendering off by a pixel
|
||||
glVertex(rect.right - hw, rect.y + (hw%1))
|
||||
glVertex(rect.right - hw, rect.bottom - width + (hw%1))
|
||||
glVertex(rect.right, rect.bottom - hw)
|
||||
glVertex(rect.x + width, rect.bottom - hw)
|
||||
glVertex(rect.x + hw, rect.bottom)
|
||||
glVertex(rect.x + hw, rect.y + width)
|
||||
glEnd()
|
||||
glEndList()
|
||||
surf.display_list.append((dl, col))
|
||||
|
||||
def polygon(self, surf, col, pointlist, width=0):
|
||||
"""
|
||||
With width == 0, can only draw convex polygons. Be careful of
|
||||
the order of vertices if it draws differently than pygame.
|
||||
|
||||
"""
|
||||
col = [c/255. for c in col]
|
||||
dl = glGenLists(1)
|
||||
glNewList(dl, GL_COMPILE)
|
||||
if not width:
|
||||
glBegin(GL_POLYGON)
|
||||
for v in pointlist:
|
||||
glVertex(v)
|
||||
glEnd()
|
||||
glEndList()
|
||||
surf.display_list.append((dl, col))
|
||||
|
||||
def circle(self, surf, col, pos, radius, width=0):
|
||||
col = [c/255. for c in col]
|
||||
dl = glGenLists(1)
|
||||
glNewList(dl, GL_COMPILE)
|
||||
if not width:
|
||||
glEnable(GL_POINT_SMOOTH)
|
||||
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST)
|
||||
glPointSize(radius*2)
|
||||
glBegin(GL_POINTS)
|
||||
glVertex(pos)
|
||||
glEnd()
|
||||
glDisable(GL_POINT_SMOOTH)
|
||||
glPointSize()
|
||||
glEndList()
|
||||
surf.display_list.append((dl, col))
|
||||
|
||||
def line(self, surf, col, start_pos, end_pos, width=1):
|
||||
col = [c/255. for c in col]
|
||||
dl = glGenLists(1)
|
||||
glNewList(dl, GL_COMPILE)
|
||||
glLineWidth(width)
|
||||
glBegin(GL_LINES)
|
||||
glVertex(start_pos)
|
||||
glVertex(end_pos)
|
||||
glEnd()
|
||||
glEndList()
|
||||
surf.display_list.append((dl, col))
|
||||
|
||||
# Export Draw functions
|
||||
draw = Draw()
|
180
sgc/widgets/radio_button.py
Normal file
180
sgc/widgets/radio_button.py
Normal file
|
@ -0,0 +1,180 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2012 Michael Rochester, Sam Bull
|
||||
|
||||
"""
|
||||
Radio Button, allows the user to select a single option from a group.
|
||||
|
||||
"""
|
||||
|
||||
import pygame.mouse
|
||||
from pygame.locals import *
|
||||
|
||||
from _locals import *
|
||||
from _locals import focus
|
||||
from base_widget import Simple
|
||||
|
||||
class Radio(Simple):
|
||||
|
||||
"""
|
||||
A selectable radio button.
|
||||
|
||||
Attributes:
|
||||
groups: A dictionary containing the active radio button or ``None`` for
|
||||
each radio group. Key is ``str`` containing the name of the group.
|
||||
selected: True if widget is the currently selected radio button in
|
||||
it's group.
|
||||
|
||||
Images:
|
||||
'image': The default, inactive button state.
|
||||
'over': The image used when the cursor is hovering over the button.
|
||||
'active': The image used for the active button in a group
|
||||
(if applicable).
|
||||
|
||||
"""
|
||||
|
||||
_can_focus = True
|
||||
_available_images = ("over", "active")
|
||||
_settings_default = {"group": None, "label": "", "col": (118, 45, 215),
|
||||
"label_col": Font.col, "radius": 7}
|
||||
|
||||
_over_state = False
|
||||
_draw_rect = False
|
||||
|
||||
groups = {}
|
||||
_order = {}
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
group: ``str`` Name of the group for widget to be added to.
|
||||
label: ``str`` Text to be displayed to the right of the widget.
|
||||
active: ``True`` Makes this the active radio button for it's group.
|
||||
col: ``tuple`` (r,g,b) The colour to be used for the 'over' image
|
||||
if not using a custom image.
|
||||
label_col: ``tuple`` (r,g,b) The colour for the label text.
|
||||
radius: ``int`` Radius of the button if not using a custom image.
|
||||
|
||||
"""
|
||||
if "group" in kwargs:
|
||||
if kwargs["group"] not in self.groups:
|
||||
self.groups[kwargs["group"]] = None
|
||||
self._order[kwargs["group"]] = []
|
||||
self._settings["group"] = kwargs["group"]
|
||||
self._order[self._settings["group"]].append(self)
|
||||
if "label" in kwargs:
|
||||
self._settings["label"] = kwargs["label"]
|
||||
if "col" in kwargs:
|
||||
self._settings["col"] = kwargs["col"]
|
||||
if "label_col" in kwargs:
|
||||
self._settings["label_col"] = kwargs["label_col"]
|
||||
if "radius" in kwargs:
|
||||
self._settings["radius"] = kwargs["radius"]
|
||||
assert self._settings["group"] is not None
|
||||
if "active" in kwargs:
|
||||
self._draw()
|
||||
self._activate()
|
||||
|
||||
def _draw(self, draw):
|
||||
r = self._settings["radius"]
|
||||
# Render text
|
||||
label = Simple(Font["widget"].render(self._settings["label"], True,
|
||||
self._settings["label_col"]))
|
||||
if not hasattr(self, "image"):
|
||||
self._create_base_images((r*2 + 10 + label.rect.w,
|
||||
max(label.rect.height, r*2)))
|
||||
|
||||
pos = (r, self.rect.h/2)
|
||||
# Background circles
|
||||
draw.circle(self._images["image"], (255,255,255), pos, r)
|
||||
draw.circle(self._images["over"], self._settings["col"], pos, r)
|
||||
# Border circles
|
||||
draw.circle(self._images["image"], (0,0,1), pos, r, 1)
|
||||
draw.circle(self._images["over"], (0,0,1), pos, r, 1)
|
||||
# Central dot for 'active' state
|
||||
draw.circle(self._images["active"],(0,0,1), pos, int(r/1.5))
|
||||
|
||||
label.rect.midleft = (r*2 + 10, pos[1])
|
||||
self._images["image"].blit(label.image, label.pos)
|
||||
self._images["over"].blit(label.image, label.pos)
|
||||
self._draw_button()
|
||||
|
||||
def update(self, time):
|
||||
"""Update the radio button each frame."""
|
||||
if self.rect_abs.collidepoint(pygame.mouse.get_pos()):
|
||||
if not self._over_state:
|
||||
# Draw over state
|
||||
self._over_state = True
|
||||
self._draw_button()
|
||||
elif self._over_state:
|
||||
# Draw normal state
|
||||
self._over_state = False
|
||||
self._draw_button()
|
||||
|
||||
def _event(self, event):
|
||||
if event.type == MOUSEBUTTONUP and event.button == 1:
|
||||
if self.rect_abs.collidepoint(event.pos):
|
||||
self._activate()
|
||||
elif event.type == KEYDOWN:
|
||||
def focus_change(diff):
|
||||
next_widget = order[order.index(widget) + diff]
|
||||
next_widget._activate()
|
||||
if self._parent:
|
||||
self._parent._focus.add(1, next_widget)
|
||||
else:
|
||||
focus.add(1, next_widget)
|
||||
order = self._order[self._settings["group"]]
|
||||
widget = self.groups[self._settings["group"]]
|
||||
if event.key == K_UP and order.index(widget) > 0:
|
||||
focus_change(-1)
|
||||
elif event.key == K_DOWN and order.index(widget) < len(order)-1:
|
||||
focus_change(1)
|
||||
elif event.type == KEYUP:
|
||||
if event.key in (K_SPACE, K_RETURN):
|
||||
self._activate()
|
||||
|
||||
def _focus_enter(self, focus):
|
||||
"""Draw rectangle when focus is gained from keyboard."""
|
||||
if focus == 1:
|
||||
self._draw_rect = True
|
||||
self._draw_button()
|
||||
|
||||
def _focus_exit(self):
|
||||
"""Stop drawing rectangle when focus is lost."""
|
||||
self._draw_rect = False
|
||||
self._draw_button()
|
||||
|
||||
def _draw_button(self):
|
||||
"""Draw the button."""
|
||||
if not self._over_state:
|
||||
self.image = self._images["image"].copy()
|
||||
else:
|
||||
self.image = self._images["over"].copy()
|
||||
if self.groups[self._settings["group"]] is self:
|
||||
self.image.blit(self._images["active"], (0,0))
|
||||
# Draw dotted rectangle to show keyboard focus
|
||||
if self._draw_rect:
|
||||
self._dotted_rect()
|
||||
|
||||
def _activate(self):
|
||||
"""Reset drawing of new and previous widget."""
|
||||
old = self.groups[self._settings["group"]]
|
||||
self.groups[self._settings["group"]] = self
|
||||
if old is not None: old._draw_button()
|
||||
self._draw_button()
|
||||
|
||||
def clear(self, group=None):
|
||||
"""
|
||||
Clear a group so no radio button is selected.
|
||||
|
||||
Args:
|
||||
group: ``str`` Group name to clear. Clear this widget's group if None.
|
||||
|
||||
"""
|
||||
if group is None: group = self._settings["group"]
|
||||
old = self.groups[group]
|
||||
self.groups[group] = None
|
||||
if old is not None: old._draw_button()
|
||||
|
||||
@property
|
||||
def selected(self):
|
||||
return self is self.groups[self._settings["group"]]
|
245
sgc/widgets/scroll_box.py
Normal file
245
sgc/widgets/scroll_box.py
Normal file
|
@ -0,0 +1,245 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2011-2012 Sam Bull
|
||||
|
||||
"""
|
||||
Scroll box. A container widget that provides scroll bars to be able to
|
||||
view a larger widget.
|
||||
|
||||
BUG: Scrolling gives focus. Can't scroll without focus.
|
||||
BUG: Scroll bar pops up over a modal widget.
|
||||
BUG: Scroll bars don't work in a modal widget.
|
||||
|
||||
"""
|
||||
|
||||
import pygame.mouse
|
||||
from pygame.locals import *
|
||||
|
||||
from _locals import *
|
||||
from base_widget import Simple
|
||||
|
||||
class ScrollBox(Simple):
|
||||
|
||||
"""
|
||||
Scroll Box
|
||||
|
||||
"""
|
||||
|
||||
_can_focus = True
|
||||
_default_size = (300, 200)
|
||||
_settings_default = {"widget": None, "col": (118, 45, 215)}
|
||||
|
||||
_scroll_x = _scroll_y = None
|
||||
_handle_x = _handle_y = None
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
widget: Widget that should be displayed in scroll box.
|
||||
col: ``tuple`` (r,g,b) Colour used for scroll bars and handles.
|
||||
|
||||
"""
|
||||
if "widget" in kwargs:
|
||||
self._settings["widget"] = kwargs["widget"]
|
||||
self._settings["widget"]._parent = self
|
||||
self._settings["widget"].pos = (0,0)
|
||||
if "col" in kwargs:
|
||||
self._settings["col"] = kwargs["col"]
|
||||
|
||||
def _draw(self, draw):
|
||||
# Create scroll bars and handles
|
||||
if self._settings["widget"].rect.w > self.rect.w:
|
||||
ratio = float(self.rect.w) / self._settings["widget"].rect.w
|
||||
self._scroll_x = Simple((self.rect.w * ratio, 3))
|
||||
self._scroll_x._parent = self
|
||||
self._scroll_x.image.fill(self._settings["col"])
|
||||
self._scroll_x.pos = (0, self.rect.h - 3)
|
||||
self._handle_x = _ScrollHandleH(widget=self)
|
||||
if self._settings["widget"].rect.h > self.rect.h:
|
||||
ratio = float(self.rect.h) / self._settings["widget"].rect.h
|
||||
self._scroll_y = Simple((3, self.rect.h * ratio))
|
||||
self._scroll_y._parent = self
|
||||
self._scroll_y.image.fill(self._settings["col"])
|
||||
self._scroll_y.pos = (self.rect.w - 3, 0)
|
||||
self._handle_y = _ScrollHandleV(widget=self)
|
||||
|
||||
def update(self, time):
|
||||
"""Update scroll box each frame."""
|
||||
self._settings["widget"].update(time)
|
||||
|
||||
self.image.fill(0)
|
||||
self.image.blit(self._settings["widget"].image,
|
||||
self._settings["widget"].pos)
|
||||
|
||||
pos = pygame.mouse.get_pos()
|
||||
if self._scroll_y is not None:
|
||||
self.image.blit(self._scroll_y.image, self._scroll_y.pos)
|
||||
r = self._scroll_y.rect_abs
|
||||
# Add scroll handles when cursor moves near scroll bar
|
||||
if not self._handle_y.active() and \
|
||||
r.inflate(20, 5).collidepoint(pos):
|
||||
# Position to left if handle would be off-screen.
|
||||
edge = (r.right + self._handle_y.rect.w)
|
||||
if edge < get_screen().rect.w:
|
||||
self._handle_y.rect.x = r.right
|
||||
else:
|
||||
self._handle_y.rect.right = r.left
|
||||
self._handle_y.update_pos(pos[1])
|
||||
self._handle_y.add()
|
||||
|
||||
if self._scroll_x is not None:
|
||||
self.image.blit(self._scroll_x.image, self._scroll_x.pos)
|
||||
r = self._scroll_x.rect_abs
|
||||
if not self._handle_x.active() and \
|
||||
r.inflate(5, 20).collidepoint(pos):
|
||||
edge = (r.bottom + self._handle_x.rect.h)
|
||||
if edge < get_screen().rect.h:
|
||||
self._handle_x.rect.y = r.bottom
|
||||
else:
|
||||
self._handle_x.rect.bottom = r.top
|
||||
self._handle_x.update_pos(pos[0])
|
||||
self._handle_x.add()
|
||||
|
||||
def _event(self, event):
|
||||
"""Respond to events."""
|
||||
self._settings["widget"]._event(event)
|
||||
if event.type == MOUSEBUTTONDOWN:
|
||||
if event.button == 4: # Scroll up
|
||||
self.scroll(y=-10)
|
||||
elif event.button == 5: # Scroll down
|
||||
self.scroll(y=10)
|
||||
elif event.button == 6: # Scroll left
|
||||
self.scroll(x=-10)
|
||||
elif event.button == 7: # Scroll right
|
||||
self.scroll(x=10)
|
||||
|
||||
def scroll(self, x=None, y=None):
|
||||
"""Scroll by x and y coordinates."""
|
||||
if x is not None and self._scroll_x is not None:
|
||||
# Set scroll bar position
|
||||
r = self._scroll_x.rect
|
||||
r.x = max(min(r.x + x, self.rect.w - r.w), 0)
|
||||
# Set widget's position
|
||||
ratio = r.x / float(self.rect.w - r.w)
|
||||
max_w = self._settings["widget"].rect.w - self.rect.w
|
||||
self._settings["widget"].rect.x = -max_w * ratio
|
||||
if y is not None and self._scroll_y is not None:
|
||||
r = self._scroll_y.rect
|
||||
r.y = max(min(r.y + y, self.rect.h - r.h), 0)
|
||||
ratio = r.y / float(self.rect.h - r.h)
|
||||
max_h = self._settings["widget"].rect.h - self.rect.h
|
||||
self._settings["widget"].rect.y = -max_h * ratio
|
||||
|
||||
def _change_focus(self, forward=True):
|
||||
return self._settings["widget"]._change_focus(forward)
|
||||
|
||||
def _focus_exit(self):
|
||||
self._settings["widget"]._focus_exit()
|
||||
|
||||
|
||||
|
||||
class _ScrollHandle(Simple):
|
||||
|
||||
"""
|
||||
Scroll bar to manipulate scroll box.
|
||||
|
||||
To be inherited from by _ScrollHandle[V/H], not to be used directly.
|
||||
|
||||
Uses lots of getattr() and other tricks to provide inheritable functions.
|
||||
|
||||
"""
|
||||
|
||||
_can_focus = True
|
||||
_layered = True
|
||||
_settings_default = {"widget": None}
|
||||
|
||||
_drag = None
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
widget: Scroll box that this handle should be synced to.
|
||||
|
||||
"""
|
||||
if "init" in kwargs:
|
||||
self._rect2 = self.rect_abs.inflate(20, 20)
|
||||
if "widget" in kwargs:
|
||||
self._settings["widget"] = kwargs["widget"]
|
||||
|
||||
def _draw(self, draw):
|
||||
self.image.fill(self._settings["widget"]._settings["col"])
|
||||
self.image.fill((200,200,200), self.rect.inflate(-4, -4))
|
||||
# Draw line in center
|
||||
r = self.rect
|
||||
start_pos = (3, r.centery) if self.xy == "y" else (r.centerx, 3)
|
||||
end_pos = (r.w-4, r.centery) if self.xy == "y" else (r.centerx, r.h-4)
|
||||
draw.line(self.image, (100,100,100), start_pos, end_pos)
|
||||
# Draw arrows
|
||||
if self.xy == "y":
|
||||
points1 = ((3, r.h/4), (r.centerx, r.h/5-1), (r.w-3, r.h/4))
|
||||
points2 = ((3, r.h*.75), (r.centerx, r.h*.8), (r.w-3, r.h*.75))
|
||||
else:
|
||||
points1 = ((r.w/4, 3), (r.w/5-1, r.centery), (r.w/4, r.h-3))
|
||||
points2 = ((r.w*.75, 3), (r.w*.8, r.centery), (r.w*.75, r.h-3))
|
||||
draw.polygon(self.image, (50,50,50), points1)
|
||||
draw.polygon(self.image, (50,50,50), points2)
|
||||
|
||||
def update_pos(self, xy):
|
||||
"""
|
||||
Change position of scroll handle.
|
||||
|
||||
Args:
|
||||
xy: Integer to move the scroll handle to, along the correct axis.
|
||||
|
||||
"""
|
||||
scroll_bar = getattr(self._settings["widget"], "_scroll_%s" % self.xy)
|
||||
if scroll_bar is not None:
|
||||
r = scroll_bar.rect_abs
|
||||
a,b = (r.bottom, r.top) if self.xy == "y" else (r.right, r.left)
|
||||
xy = min(a, max(xy, b))
|
||||
setattr(self.rect, "center%s" % self.xy, xy)
|
||||
self._rect2.center = self.rect.center
|
||||
|
||||
def update(self, time):
|
||||
# Move handle to cursor when cursor not hovering over.
|
||||
if not self.rect.collidepoint(pygame.mouse.get_pos()):
|
||||
self.update_pos(pygame.mouse.get_pos()[0 if self.xy == "x" else 1])
|
||||
# Hide handle when cursor moves too far.
|
||||
if self._drag is None and \
|
||||
not self._rect2.collidepoint(pygame.mouse.get_pos()):
|
||||
self.remove()
|
||||
|
||||
def _event(self, event):
|
||||
index = 1 if self.xy == "y" else 0
|
||||
if event.type == MOUSEBUTTONDOWN and event.button == 1 and \
|
||||
self.rect.collidepoint(event.pos):
|
||||
# Initialise drag
|
||||
center = getattr(self.rect_abs, "center%s" % self.xy)
|
||||
self._offset = event.pos[index] - center
|
||||
self._drag = event.pos[index]
|
||||
elif self._drag is not None:
|
||||
if event.type == MOUSEMOTION:
|
||||
# Move scroll handle and bar
|
||||
self.update_pos(event.pos[index] - self._offset)
|
||||
kwarg = {self.xy: event.rel[index]}
|
||||
self._settings["widget"].scroll(**kwarg)
|
||||
elif event.type == MOUSEBUTTONUP and event.button == 1:
|
||||
# Move scroll box up when clicked
|
||||
if -5 < (self._drag - event.pos[index]) < 5:
|
||||
center = getattr(self.rect_abs, "center%s" % self.xy)
|
||||
if event.pos[index] < center:
|
||||
kwarg = {self.xy: -40}
|
||||
self._settings["widget"].scroll(**kwarg)
|
||||
else:
|
||||
kwarg = {self.xy: 40}
|
||||
self._settings["widget"].scroll(**kwarg)
|
||||
# Or stop moving and set final position after drag
|
||||
else:
|
||||
self.update_pos(event.pos[index] - self._offset)
|
||||
self._drag = None
|
||||
|
||||
class _ScrollHandleV(_ScrollHandle):
|
||||
_default_size = (12,50)
|
||||
xy = "y"
|
||||
|
||||
class _ScrollHandleH(_ScrollHandle):
|
||||
_default_size = (50,12)
|
||||
xy = "x"
|
131
sgc/widgets/settings.py
Normal file
131
sgc/widgets/settings.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010 Sam Bull
|
||||
|
||||
"""
|
||||
Settings for games, these include:
|
||||
CONTROLS
|
||||
Keymap
|
||||
Mouse Sensitivity (speed) TODO
|
||||
DISPLAY
|
||||
Resolution (width, height) TODO
|
||||
Fullscreen (bool) TODO
|
||||
|
||||
"""
|
||||
|
||||
import pygame
|
||||
from pygame.locals import *
|
||||
|
||||
from ..locals import *
|
||||
from _locals import *
|
||||
from base_widget import Simple
|
||||
|
||||
|
||||
class Keys(Simple):
|
||||
|
||||
_can_focus = True # Override Simple
|
||||
|
||||
"""
|
||||
Screen used to change keymap settings.
|
||||
|
||||
Keys is a special widget that will fill the screen and take
|
||||
over the game loop for maximum effiencency.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, keymap_file, parent=None, **kwargs):
|
||||
"""
|
||||
Extend Simple and prepare the key order.
|
||||
|
||||
keymap_file -- String containing filename containing keymap.
|
||||
Key order should be on second line.
|
||||
parent,kwargs -- Pass through to Simple
|
||||
|
||||
"""
|
||||
size = self._default_screen.size
|
||||
Simple.__init__(self, size, parent, **kwargs)
|
||||
# Load key order
|
||||
with open(keymap_file) as f:
|
||||
f.readline()
|
||||
self._key_order = eval(f.readline())
|
||||
assert isinstance(self._key_order, list)
|
||||
|
||||
def add(self):
|
||||
"""
|
||||
Display the settings for the keymap to the player.
|
||||
|
||||
"""
|
||||
# Display title
|
||||
message = Surface(font_title.render("Keymap Settings",
|
||||
True, font_col))
|
||||
message.y = 30
|
||||
message.x = (self._parent.w - message.w)/2
|
||||
self._parent().blit(message(), message.pos)
|
||||
# Display settings
|
||||
positions = {}
|
||||
temp_y = 100
|
||||
row = 0
|
||||
for key in self._key_order:
|
||||
if temp_y > (self._parent.h - message.h*2):
|
||||
row += 1
|
||||
temp_y = 100
|
||||
# Render name
|
||||
message = Surface(font_widget.render(key.title(),
|
||||
True, font_col))
|
||||
message.y = temp_y
|
||||
message.x = 30 + ((self._parent.w-30)/3) * row
|
||||
self._parent().blit(message(), message.pos)
|
||||
# Render keymap
|
||||
message = Surface(font_widget.render(
|
||||
pygame.key.name(keymap[key]),
|
||||
True, font_col))
|
||||
message.y = temp_y
|
||||
message.x = ((((self._parent.w-30)/3) * (row+1) - 30) -
|
||||
message.w/2)
|
||||
self._parent().blit(message(), message.pos)
|
||||
positions[key] = message
|
||||
temp_y += message.h*2
|
||||
|
||||
# Event loop
|
||||
keypress_wait = False
|
||||
while True:
|
||||
event = pygame.event.wait()
|
||||
if event.type == QUIT:
|
||||
exit() #TODO exit to menu
|
||||
|
||||
elif event.type == MOUSEBUTTONDOWN and not keypress_wait:
|
||||
# If clicking a key, then prepare to change keymap
|
||||
for key in positions:
|
||||
if pygame.mouse.get_pos()[0] >= positions[key].x and \
|
||||
pygame.mouse.get_pos()[1] >= positions[key].y and \
|
||||
pygame.mouse.get_pos()[0] <= positions[key].x + \
|
||||
positions[key].w and \
|
||||
pygame.mouse.get_pos()[1] <= positions[key].y + \
|
||||
positions[key].h:
|
||||
|
||||
# Replace key with 'press key...' message
|
||||
self._parent().fill((0,0,0), positions[key].rect)
|
||||
message = Surface(font_widget.render(
|
||||
"press key...", True, font_col))
|
||||
message.y = positions[key].y
|
||||
message.x = positions[key].x - \
|
||||
(message.w - positions[key].w)/2
|
||||
positions[key] = message
|
||||
self._parent().blit(message(), message.pos)
|
||||
keypress_wait = key
|
||||
|
||||
elif event.type == KEYDOWN and keypress_wait:
|
||||
# When waiting for new key, replace text with new key
|
||||
if event.key != K_ESCAPE:
|
||||
keymap[keypress_wait] = event.key
|
||||
self._parent().fill((0,0,0), positions[keypress_wait].rect)
|
||||
message = Surface(font_widget.render(
|
||||
pygame.key.name(keymap[keypress_wait]),
|
||||
True, font_col))
|
||||
message.y = positions[keypress_wait].y
|
||||
message.x = positions[keypress_wait].x - \
|
||||
(message.w - positions[keypress_wait].w)/2
|
||||
positions[keypress_wait] = message
|
||||
self._parent().blit(message(), message.pos)
|
||||
keypress_wait = None
|
||||
pygame.display.update()
|
183
sgc/widgets/toggle.py
Normal file
183
sgc/widgets/toggle.py
Normal file
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2012 Michael Rochester, Sam Bull
|
||||
|
||||
"""
|
||||
Toggle button, allows the user to change a boolean setting.
|
||||
|
||||
"""
|
||||
|
||||
import pygame
|
||||
from pygame.locals import *
|
||||
|
||||
from _locals import *
|
||||
from _locals import focus
|
||||
from base_widget import Simple
|
||||
|
||||
class Toggle(Simple):
|
||||
|
||||
"""
|
||||
A toggle button, allowing the user to select between two states.
|
||||
|
||||
Attributes:
|
||||
state: True if toggle button is switched on.
|
||||
|
||||
Images:
|
||||
'image': The background when the button is set to off.
|
||||
'active': The background when the button is set to on.
|
||||
'handle': The image used for the slider.
|
||||
|
||||
"""
|
||||
|
||||
_can_focus = True
|
||||
_default_size = (130,30)
|
||||
_available_images = ("active", "handle")
|
||||
_settings_default = {"state": False, "label": "", "label_col": Font.col,
|
||||
"on_col": (88, 158, 232), "off_col": (191, 191, 186),
|
||||
"on_label_col": (255,255,255),
|
||||
"off_label_col": (93,82,80)}
|
||||
|
||||
_draw_rect = False
|
||||
_drag = None
|
||||
_handle_rect = None
|
||||
|
||||
def _config(self, **kwargs):
|
||||
"""
|
||||
state: ``bool`` Sets the state of the button (False by default).
|
||||
label: ``str`` Text to be displayed next to the button.
|
||||
label_col: ``tuple`` (r,g,b) The colour for the label.
|
||||
on_col: ``tuple`` (r,g,b) The background colour when the button is
|
||||
set to the 'on' state.
|
||||
off_col: ``tuple`` (r,g,b) The background colour when the button is
|
||||
set to the 'off' state.
|
||||
on_label_col: ``tuple`` (r,g,b) The on/off text colour when the
|
||||
button is set to the 'on' state.
|
||||
off_label_col: ``tuple`` (r,g,b) The on/off text colour when the
|
||||
button is set to the 'off' state.
|
||||
|
||||
"""
|
||||
if "init" in kwargs:
|
||||
self._handle_rect = Rect(0,0,0,0)
|
||||
if "state" in kwargs:
|
||||
self._settings["state"] = kwargs["state"]
|
||||
if "label" in kwargs:
|
||||
self._settings["label"] = kwargs["label"]
|
||||
if "label_col" in kwargs:
|
||||
self._settings["label_col"] = kwargs["label_col"]
|
||||
if "on_col" in kwargs:
|
||||
self._settings["on_col"] = kwargs["on_col"]
|
||||
if "off_col" in kwargs:
|
||||
self._settings["off_col"] = kwargs["off_col"]
|
||||
if "on_label_col" in kwargs:
|
||||
self._settings["on_label_col"] = kwargs["on_label_col"]
|
||||
if "off_label_col" in kwargs:
|
||||
self._settings["off_label_col"] = kwargs["off_label_col"]
|
||||
|
||||
def _draw(self, draw):
|
||||
label = Simple(Font["widget"].render(self._settings["label"], True,
|
||||
self._settings["label_col"]))
|
||||
label.rect.centery = self.rect.h/2
|
||||
|
||||
# Calculate widget and handle rects
|
||||
self.box = Rect((label.rect.w + 10, 0),
|
||||
(self.rect.w - label.rect.w - 10, self.rect.h))
|
||||
self._handle_rect.size = ((self.box.w/2) - 4, self.rect.h - 4)
|
||||
|
||||
# Draw handle
|
||||
draw.rect(self._images["handle"], (245,245,244),
|
||||
((0,2), self._handle_rect.size))
|
||||
for x in range(2,5): # Grips
|
||||
draw.line(self._images["handle"], (232,232,229),
|
||||
((self._handle_rect.w/6)*x, 10),
|
||||
((self._handle_rect.w/6)*x, self.rect.h-10), 3)
|
||||
|
||||
# Draw main images
|
||||
for img in ("off", "on"):
|
||||
image = "image" if (img == "off") else "active"
|
||||
draw.rect(self._images[image], self._settings[img+"_col"], self.box)
|
||||
|
||||
# Render the labels
|
||||
col = self._settings[img+"_label_col"]
|
||||
on = Simple(Font["widget"].render("ON", True, col))
|
||||
off = Simple(Font["widget"].render("OFF", True, col))
|
||||
on.rect.center = (label.rect.w + 10 + self.box.w*.25 - 1,
|
||||
self.rect.h/2)
|
||||
off.rect.center = (label.rect.w + 10 + self.box.w*.75 + 1,
|
||||
self.rect.h/2)
|
||||
|
||||
# Blit all text
|
||||
self._images[image].blit(label.image, label.pos)
|
||||
self._images[image].blit(on.image, on.pos)
|
||||
self._images[image].blit(off.image, off.pos)
|
||||
|
||||
self._draw_button()
|
||||
|
||||
def _event(self, event):
|
||||
if event.type == MOUSEBUTTONDOWN and event.button == 1:
|
||||
# If clicking handle
|
||||
if self._handle_rect.collidepoint(
|
||||
(event.pos[0]-self.pos_abs[0],
|
||||
event.pos[1]-self.pos_abs[1])):
|
||||
self._drag = (event.pos[0], event.pos[0] - self._handle_rect.x)
|
||||
elif event.type == MOUSEMOTION and event.buttons[0]:
|
||||
if self._drag is not None:
|
||||
# Move handle
|
||||
self._handle_rect.x = max(min(self.box.centerx + 3,
|
||||
event.pos[0] - self._drag[1]),
|
||||
self.box.x + 2)
|
||||
self._draw_button()
|
||||
elif event.type == MOUSEBUTTONUP and event.button == 1:
|
||||
if self._drag is not None:
|
||||
if abs(self._drag[0] - event.pos[0]) < 5: # Clicked
|
||||
self._settings["state"] = not self._settings["state"]
|
||||
else: # Dragged
|
||||
# Determine if dropped in on/off position
|
||||
if self._handle_rect.centerx < self.box.centerx:
|
||||
self._settings["state"] = False
|
||||
else:
|
||||
self._settings["state"] = True
|
||||
self._drag = None
|
||||
self._draw_button()
|
||||
elif self.rect_abs.collidepoint(event.pos):
|
||||
# Clicked outside of handle
|
||||
self._settings["state"] = not self._settings["state"]
|
||||
self._draw_button()
|
||||
elif event.type == KEYUP:
|
||||
if event.key in (K_RETURN, K_SPACE):
|
||||
self._settings["state"] = not self._settings["state"]
|
||||
self._draw_button()
|
||||
|
||||
def _focus_enter(self, focus):
|
||||
"""Draw dotted rect when focus is gained from keyboard."""
|
||||
if focus == 1:
|
||||
self._draw_rect = True
|
||||
self._draw_button()
|
||||
|
||||
def _focus_exit(self):
|
||||
"""Stop drawing dotted rect when focus is lost."""
|
||||
self._draw_rect = False
|
||||
self._draw_button()
|
||||
|
||||
def _draw_button(self):
|
||||
"""Render the widget and blit the handle in the correct place."""
|
||||
if self._settings["state"] is False:
|
||||
self.image = self._images["image"].copy()
|
||||
else:
|
||||
self.image = self._images["active"].copy()
|
||||
|
||||
if self._drag is None:
|
||||
# Fix handle in place when not dragging
|
||||
if self._settings["state"] is False:
|
||||
self._handle_rect.x = self.box.x + 2
|
||||
else:
|
||||
self._handle_rect.x = self.box.centerx + 3
|
||||
|
||||
self.image.blit(self._images["handle"], self._handle_rect)
|
||||
|
||||
# Draw dotted rectangle to show keyboard focus
|
||||
if self._draw_rect:
|
||||
self._dotted_rect()
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._settings["state"]
|
Loading…
Reference in New Issue
Block a user