Source code for peng3d.gui

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  __init__.py
#
#  Copyright 2016 notna <notna@apparat.org>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#
#

__all__ = ["GUIMenu", "SubMenu", "GUILayer", "FakeWidget"]

# from pprint import pprint
# import gc
# import weakref
# import sys
import collections
import warnings

from typing import TYPE_CHECKING, Optional, Dict, List, Any

if TYPE_CHECKING:
    import peng3d.window


try:
    import pyglet
    from pyglet.gl import *
except ImportError:
    pass  # Headless mode

from ..menu import Menu
from ..layer import Layer, Layer2D
from .widgets import *
from .button import *
from .slider import *
from .text import *
from .container import *
from .layered import *
from .layout import *
from .. import util
from ..util.types import *
from .style import Style


class FakeWidget(object):
    def __init__(self, parent):
        self.peng = parent.peng
        self.parent = parent
        self.submenu = parent

        self.pressed, self.is_hovering, self.enabled = False, False, True

    @property
    def pos(self):
        return [0, 0]  # As property to prevent bug with accidental manipulation

    @property
    def size(self):
        return self.parent.size


[docs]class GUIMenu(Menu): """ :py:class:`peng3d.menu.Menu` subclass adding 2D GUI Support. Note that widgets are not managed directly by this class, but rather by each :py:class:`SubMenu`\\ . """ def __init__( self, name: str, window: "peng3d.window.PengWindow", peng: Any = None, font: Optional[str] = None, font_size: Optional[float] = None, font_color: Optional[ColorRGBA] = None, borderstyle: Optional[BorderStyle] = None, style: Optional[Dict[str, StyleValue]] = None, ): super(GUIMenu, self).__init__(name, window, peng) pyglet.clock.schedule_interval(lambda dt: None, 1.0 / 30) self.submenus: Dict[str, "SubMenu"] = {} self.activeSubMenu: Optional[str] = None self.style: Style = Style(parent=self.peng.style, overrides=style) self.style.override_if_not_none("font", font) self.style.override_if_not_none("font_size", font_size) self.style.override_if_not_none("font_color", font_color) self.style.override_if_not_none("borderstyle", borderstyle) self.batch2d: pyglet.graphics.Batch = pyglet.graphics.Batch() self.bg: BackgroundType = [242, 241, 240, 255] self.bg_vlist = pyglet.graphics.vertex_list( 4, "v2f", "c4B", ) # For compatibility with Background classes self.pressed: bool = False self.is_hovering: bool = False self.enabled: bool = True self.peng.keybinds.add( "enter", f"peng3d:menu.[{self.name}].send_form", self._on_send_form, False ) self.peng.registerEventHandler("on_resize", self.on_resize) self.on_resize(*self.size)
[docs] def addSubMenu(self, submenu: "SubMenu") -> None: """ Adds a :py:class:`SubMenu` to this Menu. Note that nothing will be displayed unless a submenu is activated. .. deprecated:: 1.12.0 This method is no longer needed in most cases, since submenus now register themselves. """ if submenu.name in self.submenus: assert ( submenu is self.submenus[submenu.name] ), "Tried to register two submenus with the same name" return # Ignore duplicate registration attempts self.submenus[submenu.name] = submenu
[docs] def changeSubMenu(self, submenu: str) -> None: """ Changes the submenu that is displayed. :raises ValueError: if the name was not previously registered """ if submenu not in self.submenus: raise ValueError("Submenu %s does not exist!" % submenu) elif submenu == self.activeSubMenu: return # Ignore double submenu activation to prevent bugs in submenu initializer old = self.activeSubMenu self.activeSubMenu = submenu if old is not None: self.submenus[old].on_exit(submenu) self.submenus[old].doAction("exit") self.submenu.on_enter(old) self.submenu.doAction("enter")
[docs] def draw(self) -> None: """ Draws each menu layer and the active submenu. Note that the layers are drawn first and may be overridden by the submenu and widgets. """ super(GUIMenu, self).draw() # TODO: find a way to re-raise these exceptions bypassing pyglet try: self.submenu.draw() except (AttributeError, TypeError): import traceback traceback.print_exc()
def draw_bg(self) -> None: # Draws the background if isinstance(self.bg, Layer): self.bg._draw() elif hasattr(self.bg, "draw") and callable(self.bg.draw): self.bg.draw() elif isinstance(self.bg, list) or isinstance(self.bg, tuple): self.bg_vlist.draw(GL_QUADS) elif callable(self.bg): self.bg() elif isinstance(self.bg, Background): # The background will be drawn via the batch if not self.bg.initialized: self.bg.init_bg() self.bg.redraw_bg() self.bg.initialized = True elif self.bg == "blank": pass elif self.bg is None: raise RuntimeError("Cannot set Menu background to None") else: raise TypeError("Unknown/Unsupported background type") self.batch2d.draw() def setBackground(self, bg: BackgroundType): self.bg = bg if isinstance(bg, list) or isinstance(bg, tuple): if len(bg) == 3 and isinstance(bg, list): bg.append(255) self.bg_vlist.colors = bg * 4 elif bg in ["flat", "gradient", "oldshadow", "material"]: self.bg = ContainerButtonBackground(self, borderstyle=bg) self.on_resize(self.window.width, self.window.height) @property def submenu(self) -> "SubMenu": """ Property containing the :py:class:`SubMenu` instance that is currently active. """ return self.submenus[self.activeSubMenu] # The following properties are needed for compatibility with Background classes @property def pos(self) -> List[int]: return [0, 0] # As property to prevent bug with accidental manipulation @property def size(self) -> List[int]: return self.window.get_size() font = util.default_property("style") font_size = util.default_property("style") font_color = util.default_property("style") borderstyle = util.default_property("style") def on_resize(self, width, height): sx, sy = width, height self.bg_vlist.vertices = [0, 0, sx, 0, sx, sy, 0, sy] if isinstance(self.bg, Background): if not self.bg.initialized: self.bg.init_bg() self.bg.initialized = True self.bg.redraw_bg()
[docs] def on_enter(self, old): super().on_enter(old) self.submenu.redraw()
def _on_send_form(self, symbol, modifiers, release): # TODO: support context here if release: return self.submenu.send_form()
[docs]class GUILayer(GUIMenu, Layer2D): """ Hybrid of :py:class:`GUIMenu` and :py:class:`peng3d.layer.Layer2D`\\ . This class allows you to create Head-Up Displays and other overlays easily. """ # TODO: add safety recursion breaker if this is added as a layer to itself def __init__( self, name: str, menu: Menu, window: Optional["peng3d.window.PengWindow"] = None, peng: Optional["peng3d.Peng"] = None, ): if window is not None: warnings.warn( "Passing window to a GUILayer is no longer necessary; the window parameter will be removed in peng3d 2.0", DeprecationWarning, 3, # Needs to be rather high, since we are usually called a bit down the inheritance tree ) if peng is not None: warnings.warn( "Passing peng to a GUILayer is no longer necessary; the peng parameter will be removed in peng3d 2.0", DeprecationWarning, 3, ) Layer2D.__init__(self, menu, menu.window, menu.peng) GUIMenu.__init__(self, name, menu.window, menu.peng)
[docs] def draw(self) -> None: """ Draws the Menu. """ self.predraw() GUIMenu.draw(self) self.postdraw()
# Hack to allow Container to use the drawing method of SubMenu for itself from ..gui import container container.SubMenu = SubMenu # To allow menus to subclass SubMenu from .menus import *