diff --git a/sgc/LICENSE b/sgc/LICENSE deleted file mode 100644 index bc51544..0000000 --- a/sgc/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -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. diff --git a/sgc/__init__.py b/sgc/__init__.py deleted file mode 100644 index 7676f64..0000000 --- a/sgc/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Module Packages: - :py:mod:`widgets`: All the widgets available for use in this toolkit. - -Modules: - :py:mod:`locals`: Constants to be imported into the local namespace for convenience. - :py:mod:`surface`: Extended pygame.surface classes. - -""" - -import surface -import locals -import widgets -from widgets._locals import Font diff --git a/sgc/example/menu b/sgc/example/menu deleted file mode 100644 index 28c7972..0000000 --- a/sgc/example/menu +++ /dev/null @@ -1,16 +0,0 @@ -("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") -) diff --git a/sgc/example/test.py b/sgc/example/test.py deleted file mode 100644 index e01f297..0000000 --- a/sgc/example/test.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/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() diff --git a/sgc/locals.py b/sgc/locals.py deleted file mode 100644 index 8b870f5..0000000 --- a/sgc/locals.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/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 diff --git a/sgc/surface.py b/sgc/surface.py deleted file mode 100644 index dbc986c..0000000 --- a/sgc/surface.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/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) diff --git a/sgc/widgets/__init__.py b/sgc/widgets/__init__.py deleted file mode 100644 index 1858568..0000000 --- a/sgc/widgets/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -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`: Simple widget that does nothing. May be useful for images etc. - :py:class:`Button`: Clickable button. - :py:class:`FPSCounter`: FPS counter. - :py:class:`InputBox`: Input box. - :py:class:`Label`: Label. - :py:class:`Menu`: Game menu. - :py:class:`Radio`: Radio button. - :py:class:`settings`: TODO (Stay away). Common user settings (keymap etc.) - :py:class:`Toggle`: Toggle button. - -Container widgets: - :py:class:`Container`: Basic container, holds a group of other widgets and handles - focus between them. - :py:class:`VBox`: Automatically aligns widgets into a vertical column. - :py:class:`HBox`: Automatically aligns widgets into a horizontal row. - :py:class:`Dialog`: Dialog window. - :py:class:`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 diff --git a/sgc/widgets/_locals.py b/sgc/widgets/_locals.py deleted file mode 100644 index 29e0f60..0000000 --- a/sgc/widgets/_locals.py +++ /dev/null @@ -1,368 +0,0 @@ -#!/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[:] diff --git a/sgc/widgets/base_widget.py b/sgc/widgets/base_widget.py deleted file mode 100644 index c1b8ad0..0000000 --- a/sgc/widgets/base_widget.py +++ /dev/null @@ -1,299 +0,0 @@ -#!/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]) diff --git a/sgc/widgets/boxes.py b/sgc/widgets/boxes.py deleted file mode 100644 index 99187e3..0000000 --- a/sgc/widgets/boxes.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/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) diff --git a/sgc/widgets/button.py b/sgc/widgets/button.py deleted file mode 100644 index ef0e58a..0000000 --- a/sgc/widgets/button.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/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() diff --git a/sgc/widgets/container.py b/sgc/widgets/container.py deleted file mode 100644 index 1a679e0..0000000 --- a/sgc/widgets/container.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/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() diff --git a/sgc/widgets/dialog.py b/sgc/widgets/dialog.py deleted file mode 100644 index 85d9083..0000000 --- a/sgc/widgets/dialog.py +++ /dev/null @@ -1,125 +0,0 @@ -#!/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) diff --git a/sgc/widgets/fps_counter.py b/sgc/widgets/fps_counter.py deleted file mode 100644 index 7492ab0..0000000 --- a/sgc/widgets/fps_counter.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/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) diff --git a/sgc/widgets/input_box.py b/sgc/widgets/input_box.py deleted file mode 100644 index 624de6f..0000000 --- a/sgc/widgets/input_box.py +++ /dev/null @@ -1,325 +0,0 @@ -#!/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 diff --git a/sgc/widgets/label.py b/sgc/widgets/label.py deleted file mode 100644 index a7a4863..0000000 --- a/sgc/widgets/label.py +++ /dev/null @@ -1,229 +0,0 @@ -#!/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"])) diff --git a/sgc/widgets/menu.py b/sgc/widgets/menu.py deleted file mode 100644 index 608f4b2..0000000 --- a/sgc/widgets/menu.py +++ /dev/null @@ -1,217 +0,0 @@ -#!/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) diff --git a/sgc/widgets/opengl.py b/sgc/widgets/opengl.py deleted file mode 100644 index c5ae17e..0000000 --- a/sgc/widgets/opengl.py +++ /dev/null @@ -1,255 +0,0 @@ -#!/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() diff --git a/sgc/widgets/radio_button.py b/sgc/widgets/radio_button.py deleted file mode 100644 index ed56db9..0000000 --- a/sgc/widgets/radio_button.py +++ /dev/null @@ -1,180 +0,0 @@ -#!/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"]] diff --git a/sgc/widgets/scroll_box.py b/sgc/widgets/scroll_box.py deleted file mode 100644 index e28ef51..0000000 --- a/sgc/widgets/scroll_box.py +++ /dev/null @@ -1,245 +0,0 @@ -#!/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" diff --git a/sgc/widgets/settings.py b/sgc/widgets/settings.py deleted file mode 100644 index 7e4ee3a..0000000 --- a/sgc/widgets/settings.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/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() diff --git a/sgc/widgets/toggle.py b/sgc/widgets/toggle.py deleted file mode 100644 index 3a77549..0000000 --- a/sgc/widgets/toggle.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/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"]