#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  menu.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__ = ["BasicMenu","Menu"]

import weakref
import inspect

from .layer import Layer
from .util import ActionDispatcher

class BasicMenu(ActionDispatcher):
    """
    Menu base class without layer support.

    Each menu is separated from the other menus and can be switched between at any time.

    Actions supported by default:

    ``enter`` is triggered everytime the :py:meth:`on_enter()` method has been called.

    ``exit`` is triggered everytime the :py:meth:`on_exit()` method has been called.

    .. seealso::

       See :py:class:`Menu()` for more information.

    """

    def __init__(self,name,window,peng):
        self.name = name
        self.window = window
        self.peng = peng

        self.eventHandlers = {}

        self.worlds = []
    def draw(self):
        """
        This method is called if it is time to render the menu.

        Override this method in subclasses to customize behavior and actually draw stuff.
        """
        pass

    def addWorld(self,world):
        """
        Adds the given world to the internal list.

        Worlds that are registered via this method will get all events that are given to this menu passed through.

        This mechanic is mainly used to implement actor controllers.
        """
        self.worlds.append(world)

    # Event handlers
    def handleEvent(self,event_type,args):
        if event_type in self.eventHandlers:
            for whandler in self.eventHandlers[event_type]:
                # This allows for proper collection of deleted handler methods by using weak references
                handler = whandler()
                if handler is None:
                    del self.eventHandlers[event_type][self.eventHandlers[event_type].index(whandler)]
                handler(*args)
        for world in self.worlds:
            world.handle_event(event_type,args,self.window)
    handleEvent.__noautodoc__ = True
    def registerEventHandler(self,event_type,handler):
        if event_type not in self.eventHandlers:
            self.eventHandlers[event_type]=[]
        # Only a weak reference is kept
        if inspect.ismethod(handler):
            handler = weakref.WeakMethod(handler)
        else:
            handler = weakref.ref(handler)
        self.eventHandlers[event_type].append(handler)

    def on_enter(self,old):
        """
        This fake event handler will be called every time this menu is entered via the :py:meth:`PengWindow.changeMenu()` method.

        This handler will not be called if this menu is already active.
        """
        pass # Custom fake event handler for entering the menu
    def on_exit(self,new):
        """
        This fake event handler will be called every time this menu is exited via the :py:meth:`PengWindow.changeMenu()` method.

        This handler will not be called if this menu is the same as the new menu.
        """
        pass # Custom fake event handler for leaving the menu

class Menu(BasicMenu):
    """
    Subclass of :py:class:`BasicMenu` adding support for the :py:class:`Layer` class.

    This subclass overrides the draw and __init__ method, so be sure to call them if you override them.
    """

    def __init__(self,name,window,peng):
        super(Menu,self).__init__(name,window,peng)
        self.layers = []
    def addLayer(self,layer,z=-1):
        """
        Adds a new layer to the stack, optionally at the specified z-value.

        ``layer`` must be an instance of Layer or subclasses.

        ``z`` can be used to override the index of the layer in the stack. Defaults to ``-1`` for appending.
        """
        # Adds a new layer to the stack, optionally at the specified z-value
        # The z-value is the index this layer should be inserted in, or -1 for appending
        if not isinstance(layer,Layer):
            raise TypeError("layer must be an instance of Layer!")
        if z==-1:
            self.layers.append(layer)
        else:
            self.layers.insert(z,layer)
    def draw(self):
        """
        Draws the layers in the appropriate order.

        Layers that have their ``enabled`` attribute set to ``False`` are skipped.
        """
        # Draws each layer in the given order
        for layer in self.layers:
            if layer.enabled:
                layer._draw()

    def on_enter(self,old):
        """
        Same as :py:meth:`BasicMenu.on_enter()`\ , but also calls :py:meth:`Layer.on_menu_enter()` on every layer.
        """
        for layer in self.layers:
            layer.on_menu_enter(old)
    def on_exit(self,new):
        """
        Same as :py:meth:`BasicMenu.on_exit()`\ , but also calls :py:meth:`Layer.on_menu_exit()` on every layer.
        """
        for layer in self.layers:
            layer.on_menu_exit(new)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  pyglet_patch.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.
#
#
"""
These patches are used to fix specific problems with pyglet.

Note that these are not likely to stay, since I will have to verify that they still work with every Pyglet Release.
"""

__all__ = ["patch_float2int"]

import sys
import ctypes

import pyglet
import pyglet.graphics
from pyglet.graphics.vertexbuffer import MappableVertexBufferObject
from pyglet.graphics import vertexbuffer

from pyglet.gl import *

### patch_float2int

def _get_region(self, buffer, start, count):
        '''Map a buffer region using this attribute as an accessor.

        The returned region can be modified as if the buffer was a contiguous
        array of this attribute (though it may actually be interleaved or
        otherwise non-contiguous).

        The returned region consists of a contiguous array of component
        data elements.  For example, if this attribute uses 3 floats per
        vertex, and the `count` parameter is 4, the number of floats mapped
        will be ``3 * 4 = 12``.

        :Parameters:
            `buffer` : `AbstractMappable`
                The buffer to map.
            `start` : int
                Offset of the first vertex to map.
            `count` : int
                Number of vertices to map

        :rtype: `AbstractBufferRegion`
        '''
        byte_start = self.stride * start
        byte_size = self.stride * count
        array_count = self.count * count
        if self.stride == self.size or not array_count:
            # non-interleaved
            ptr_type = ctypes.POINTER(self.c_type * array_count)
            return buffer.get_region(byte_start, byte_size, ptr_type)
        else:
            # interleaved
            byte_start += self.offset
            byte_size -= self.offset
            elem_stride = self.stride // ctypes.sizeof(self.c_type)
            elem_offset = self.offset // ctypes.sizeof(self.c_type)
            ptr_type = ctypes.POINTER(
                self.c_type * int((count * elem_stride - elem_offset)))
            region = buffer.get_region(byte_start, byte_size, ptr_type)
            return vertexbuffer.IndirectArrayRegion(
                region, array_count, self.count, elem_stride)

def _bind(self):
        # Commit pending data
        super(MappableVertexBufferObject, self).bind()
        size = self._dirty_max - self._dirty_min
        if size > 0:
            if size == self.size:
                glBufferData(self.target, self.size, self.data, self.usage)
            else:
                glBufferSubData(self.target, self._dirty_min, int(size),
                    self.data_ptr + self._dirty_min)
            self._dirty_min = sys.maxsize
            self._dirty_max = 0

def _iar__setitem__(self, index, value):
        count = self.count
        if not isinstance(index, slice):
            elem = index // count
            j = index % count
            self.region.array[elem * self.stride + j] = value
            return

        start = index.start or 0
        stop = index.stop
        step = index.step or 1
        if start < 0:
            start = self.size + start
        if stop is None:
            stop = self.size
        elif stop < 0:
            stop = self.size + stop

        assert step == 1 or step % count == 0, \
            'Step must be multiple of component count'

        data_start = (start // count) * self.stride + start % count
        data_stop = (stop // count) * self.stride + stop % count

        # ctypes does not support stepped slicing, so do the work in a list
        # and copy it back.
        data = self.region.array[:]
        if step == 1:
            data_step = self.stride
            value_step = count
            for i in range(count):
                data[data_start + i:int(data_stop) + i:data_step] = \
                    value[i::value_step]
        else:
            data_step = (step // count) * self.stride
            data[data_start:data_stop:data_step] = value
        self.region.array[:] = data

def _draw(self, mode, vertex_list=None):
        '''Draw vertices in the domain.

        If `vertex_list` is not specified, all vertices in the domain are
        drawn.  This is the most efficient way to render primitives.

        If `vertex_list` specifies a `VertexList`, only primitives in that
        list will be drawn.

        :Parameters:
            `mode` : int
                OpenGL drawing mode, e.g. ``GL_POINTS``, ``GL_LINES``, etc.
            `vertex_list` : `VertexList`
                Vertex list to draw, or ``None`` for all lists in this domain.

        '''
        glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT)
        for buffer, attributes in self.buffer_attributes:
            buffer.bind()
            for attribute in attributes:
                attribute.enable()
                attribute.set_pointer(attribute.buffer.ptr)
        if vertexbuffer._workaround_vbo_finish:
            glFinish()

        if vertex_list is not None:
            glDrawArrays(mode, vertex_list.start, vertex_list.count)
        else:
            starts, sizes = self.allocator.get_allocated_regions()
            primcount = len(starts)
            if primcount == 0:
                pass
            elif primcount == 1:
                # Common case
                glDrawArrays(mode, starts[0], int(sizes[0]))
            elif gl_info.have_version(1, 4):
                starts = (GLint * primcount)(*starts)
                sizes = (GLsizei * primcount)(*sizes)
                glMultiDrawArrays(mode, starts, sizes, primcount)
            else:
                for start, size in zip(starts, sizes):
                    glDrawArrays(mode, start, size)

        for buffer, _ in self.buffer_attributes:
            buffer.unbind()
        glPopClientAttrib()

def patch_float2int():
    """
    Patches the :py:mod:`pyglet.graphics.vertexattribute`\ , :py:mod:`pyglet.graphics.vertexbuffer` and :py:mod:`pyglet.graphics.vertexdomain` modules.

    This patch is only needed with Python 3.x and will be applied automatically when initializing :py:class:`Peng()`\ .

    The patches consist of simply converting some list indices, slices and other numbers to integers from floats with .0. These patches have not been tested thoroughly, but work with at least ``test.py`` and ``test_gui.py``\ .

    Can be enabled and disabled via :confval:`pyglet.patch.patch_float2int`\ .
    """
    pyglet.graphics.vertexattribute.AbstractAttribute.get_region = _get_region
    pyglet.graphics.vertexbuffer.MappableVertexBufferObject.bind = _bind
    pyglet.graphics.vertexbuffer.IndirectArrayRegion.__setitem__ = _iar__setitem__
    pyglet.graphics.vertexdomain.VertexDomain.draw = _draw

### End patch_float2int


#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  __init__.py
#
#  Copyright 2017 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__ = [
    "WatchingList",
    "register_pyglet_handler",
    "ActionDispatcher",
    "SmartRegistry",
    ]

import sys
import weakref
import threading

try:
    import bidict
except ImportError:
    HAVE_BIDICT = False
else:
    HAVE_BIDICT = True

from .gui import *

class WatchingList(list):
    """
    Subclass of :py:func:`list` implementing a watched list.

    A WatchingList will call the given callback with a reference to itself whenever it is modified.
    Internally, the callback is stored as a weak reference, meaning that the creator should keep a reference around.

    This class is used in :py:class:`peng3d.gui.widgets.BasicWidget()` to allow for modifying single coordinates of the pos and size properties.
    """
    def __init__(self,l,callback=None):
        if callback is not None:
            self.callback = weakref.WeakMethod(callback)
        else:
            self.callback = None
        super(WatchingList,self).__init__(l)
    def __setitem__(self,*args):
        super(WatchingList,self).__setitem__(*args)
        if self.callback is not None:
            c = self.callback()(self)

def register_pyglet_handler(peng,func,event,raiseErrors=False):
    """
    Registers the given pyglet-style event handler for the given pyglet event.

    This function allows pyglet-style event handlers to receive events bridged
    through the peng3d event system. Internally, this function creates a lambda
    function that decodes the arguments and then calls the pyglet-style event handler.

    The ``raiseErrors`` flag is passed through to the peng3d event system and will
    cause any errors raised by this handler to be ignored.

    .. seealso::
       See :py:meth:`~peng3d.peng.Peng.addEventListener()` for more information.
    """
    peng.addEventListener("pyglet:%s"%event,(lambda data:func(*data["args"])),raiseErrors)

class ActionDispatcher(object):
    """
    Helper Class to be used to enable action support.

    Actions are simple callbacks that are specific to the instance they are registered with.

    To be able to use actions, a class must be a subclass of :py:class:`ActionDispatcher()`\ .

    Creation of required data structures is handled automatically when the first action is added.

    Internally, this object uses the ``actions`` attribute to store a map of action names to
    a list of callbacks.
    """
    def addAction(self,action,func,*args,**kwargs):
        """
        Adds a callback to the specified action.

        All other positional and keyword arguments will be stored and passed to the function upon activation.
        """
        if not hasattr(self,"actions"):
            self.actions = {}
        if action not in self.actions:
            self.actions[action] = []
        self.actions[action].append((func,args,kwargs))
    def doAction(self,action):
        """
        Helper method that calls all callbacks registered for the given action.
        """
        if not hasattr(self,"actions"):
            return
        for f,args,kwargs in self.actions.get(action,[]):
            f(*args,**kwargs)

class SmartRegistry(object):
    """
    Smart registry allowing easy management of mappings from int to str and vice versa.

    Note that bidict is required to be able to use this class.

    ``data`` may be a dictionary to initialize the registry with. Only dictionaries
    gotten from the :py:attr:`data` property should be used.

    ``reuse_ids`` specifies whether or not the automatic ID generator should re-use
    old, now unused IDs. See :py:meth:`genNewID()` for more information.

    ``start_id`` is the lowest ID that will be generated by the automatic ID generator.

    ``max_id`` is the highest ID that will be generated by the automatic ID generator.
    Should this limit by reached, an :py:exc:`AssertionError` will be raised.

    ``default_reg`` may be a dictionary mapping IDs to names. It will only be used if
    ``data`` did not already contain a registry.

    It is possible to access the registry via the dict-style ``reg[key]`` notation.
    This will return the name of whatever object was used as the key.

    Registering is also possible in a similar manner, like ``reg[name]=id``\ .
    ``id`` may be ``None`` to automatically generate one.

    This class also supports the ``in`` operator, note that both IDs and names are checked.
    """
    def __init__(self,data=None,reuse_ids=False,start_id=0,max_id=float("inf"),default_reg=None):
        # TODO: fix max_id being a float by default
        assert HAVE_BIDICT

        self._data = data if data is not None else {}

        self.reuse_ids = reuse_ids
        # if true, new ids will be assigned from lowest available id
        # if false, an internal counter is used

        self.start_id = start_id
        self.max_id = max_id

        self.id_lock = threading.Lock()
        self.registry_lock = threading.Lock()

        if "reg" not in self._data:
            default_reg = default_reg if default_reg is not None else {}
            if not isinstance(default_reg,dict):
                raise TypeError("Default Registry must be a dictionary")
            # no reg yet, create a new one
            # ID->NAME mapping
            self._data["reg"] = default_reg
        for k in self._data["reg"].keys():
            if not isinstance(k,int):
                raise TypeError("All keys must be integers")
        for v in self._data["reg"].values():
            if not isinstance(v,str):
                raise TypeError("All values must be strings")
        self._data["reg"] = bidict.bidict(self._data["reg"])
        if not self.reuse_ids and "next_id" not in self._data:
            if self._data.get("reg",{}) != {}:
                # already data there, find highest id +1
                self._data["next_id"]=max(max(self._data["reg"].keys())+1,start_id)
            else:
                # no data there, use start_id
                self._data["next_id"]=self.start_id

    def genNewID(self):
        """
        Generates a new ID.

        If ``reuse_ids`` was false, the new ID will be read from an internal counter
        which is also automatically increased. This means that the newly generated ID is already reserved.

        If ``reuse_ids`` was true, this method starts counting up from ``start_id`` until it finds an
        ID that is not currently known. Note that the ID is not reserved, this means that
        calling this method simultaneously from multiple threads may cause the same ID to be returned twice.

        Additionally, if the ID is greater or equal to ``max_id``\ , an :py:exc:`AssertionError` is raised.
        """
        if self.reuse_ids:
            i = self.start_id
            while True:
                if i not in self._data["reg"]:
                    assert i<=self.max_id
                    return i # no need to change any variables
                i+=1
        else:
            with self.id_lock:
                # new id creation in lock, to avoid issues with multiple threads
                i = self._data["next_id"]
                assert i<=self.max_id
                self._data["next_id"]+=1
            return i

    def register(self,name,force_id=None):
        """
        Registers a name to the registry.

        ``name`` is the name of the object and must be a string.

        ``force_id`` can be optionally set to override the automatic ID generation
        and force a specific ID.

        Note that using ``force_id`` is discouraged, since it may cause problems when ``reuse_ids`` is false.
        """
        with self.registry_lock:
            if force_id is None:
                new_id = self.genNewID()
            else:
                new_id = force_id
            self._data["reg"][new_id]=name
            return new_id

    def normalizeID(self,in_id):
        """
        Takes in an object and normalizes it to its ID/integer representation.

        Currently, only integers and strings may be passed in, else a :py:exc:`TypeError`
        will be thrown.
        """
        if isinstance(in_id,int):
            assert in_id in self._data["reg"]
            return in_id
        elif isinstance(in_id,str):
            assert in_id in self._data["reg"].inv
            return self._data["reg"].inv[in_id]
        else:
            raise TypeError("Only int and str can be converted to IDs")
    def normalizeName(self,in_name):
        """
        Takes in an object and normalizes it to its name/string.

        Currently, only integers and strings may be passed in, else a :py:exc:`TypeError`
        will be thrown.
        """
        if isinstance(in_name,str):
            assert in_name in self._data["reg"].inv
            return in_name
        elif isinstance(in_name,int):
            assert in_name in self._data["reg"]
            return self._data["reg"][in_name]
        else:
            raise TypeError("Only int and str can be converted to names")

    @property
    def data(self):
        """
        Read-only property to access the internal data.

        This is a dictionary containing all information necessary to re-create the registry
        via the ``data`` argument.

        The returned object is fully JSON/YAML/MessagePack serializable, as it only contains
        basic python data types.
        """
        d = self._data
        d["reg"]=dict(d["reg"])
        return d

    def __getitem__(self,key):
        # to access registry as reg[obj] -> name
        return self.normalizeName(key)

    def __setitem(self,key,value):
        # None may be used as value for auto generation
        # to access registry as reg[name]=id
        self.register(key,value)

    def __contains__(self,value):
        return value in self._data["reg"] or value in self._data["reg"].inv


#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  layer.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__ = ["Layer", "Layer2D", "Layer3D","LayerGroup","LayerWorld"]

try:
    import pyglet
except ImportError:
    pass

from . import camera

class Layer(object):
    """
    Base layer class.

    A Layer can be used to separate background from foreground or the 3d world from a 2d HUD.

    This class by itself does nothing, you will need to subclass it and override the :py:meth:`draw()` method.
    """

    def __init__(self,menu,window,peng):
        self.window = window
        self.menu = menu
        self.peng = peng
        self.enabled = True

    # Subclass overrides
    def draw(self):
        """
        Called when this layer needs to be drawn.

        Override this method in subclasses to redefine behavior.
        """
        pass
    def predraw(self):
        """
        Called before the :py:meth:`draw()` method is called.

        This method is used in the :py:class:`Layer2D()` and :py:class:`Layer3D()` subclasses for setting OpenGL state.

        Override this method in subclasses to redefine behavior.
        """
        pass
    def postdraw(self):
        """
        Called after the :py:meth:`draw()` method is called.

        This method can be used to reset OpenGL state to avoid conflicts with other code.

        Override this method in subclasses to redefine behavior.
        """
        pass
    # End subclass overrides

    # Event handlers

    def on_menu_enter(self,old):
        """
        Custom fake event handler called by :py:meth:`Menu.on_enter()` for every layer.

        Useful for adding and removing event handlers per layer.
        """
        pass
    def on_menu_exit(self,new):
        """
        Custom fake event handler called by :py:meth:`Menu.on_exit()` for every layer.

        Useful for adding and removing event handlers per layer.
        """
        pass

    def _draw(self):
        if not self.enabled:
            return
        self.predraw()
        try:
            self.draw()
        except Exception:
            raise
        finally:
            self.postdraw()

class Layer2D(Layer):
    """
    2D Variant of :py:class:`Layer()` and a subclass of the former.

    This class makes use of the :py:meth:`predraw()` method to configure OpenGL to draw 2-Dimensionally.
    This class uses :py:meth:`PengWindow.set2d()` to set the 2D mode.

    When overriding the :py:meth:`predraw()` method, make sure to call the superclass.
    """
    def predraw(self):
        """
        Uses :py:meth:`PengWindow.set2d()` to enable a 2D OpenGL state.
        """
        self.window.set2d()

class Layer3D(Layer):
    """
    3D Variant of :py:class:`Layer()` and a subclass of the former.

    This class works the same as :py:class:`Layer2D()`\ , only for 3D drawing instead.
    This class uses :py:meth:`PengWindow.set3d()` to set the 3D mode.

    Also, the correct :py:func:`glTranslatef()` and :py:func:`glRotatef()` are applied to simplify drawing objects.
    When writing the :py:meth:`draw()` method of this class, you will only need to use world coordinates, not camera coordinates.
    This allows for easy building of Games using First-Person-Perspectives.
    """
    def __init__(self,menu,window,peng,cam):
        super(Layer3D,self).__init__(menu,window,peng)
        if not isinstance(cam,camera.Camera):
            raise TypeError("cam must be of type Camera!")
        self.cam = cam
    def predraw(self):
        """
        Uses :py:meth:`PengWindow.set3d()` to enable a 3D OpenGL state.
        """
        self.window.set3d(self.cam)

class LayerGroup(Layer):
    """
    Layer variant wrapping the supplied pyglet group.

    ``group`` may only be an instance of :py:class:`pyglet.graphics.Group`\ , else a :py:exc:`TypeError` will be raised.

    Also note that both the :py:meth:`predraw() <Layer.predraw()>` and :py:meth:`postdraw() <Layer.postdraw()>` methods are overwritten by this class.

    .. seealso::

       For more information about pyglet groups, see `the pyglet docs <http://pyglet.readthedocs.io/en/latest/programming_guide/graphics.html#setting-the-opengl-state>`_\ .

    """
    def __init__(self,menu,window,peng,group):
        super(LayerGroup,self).__init__(menu,window,peng)
        if not isinstance(group,pyglet.graphics.Group):
            raise TypeError("group must be an instance of pyglet.graphics.Group")
        self.group = group
    def predraw(self):
        """
        Sets the group state.
        """
        self.group.set_state()
    def postdraw(self):
        """
        Re-sets the previous state.
        """
        self.group.unset_state()

class LayerWorld(Layer3D):
    """
    Subclass of :py:class:`Layer3D()` implementing a 3D Layer showing a specific :py:class:`WorldView`\ .

    All arguments passed to the constructor should be self-explanatory.

    Note that you may not set any of the attributes directly, or crashes and bugs may appear indirectly within a certain during future re-drawing of the screen.
    """
    def __init__(self,menu,window,peng,world,viewname):
        super(LayerWorld,self).__init__(menu,window,peng,world.getView(viewname).cam)
        self.world = world
        self.viewname = viewname
        self.view = self.world.getView(self.viewname)
    def setView(self,name):
        """
        Sets the view used to the specified ``name``\ .

        The name must be known to the world or else a :py:exc:`ValueError` is raised.
        """
        if name not in self.world.views:
            raise ValueError("Invalid viewname for world!")
        self.viewname = viewname
        self.view = self.world.getView(self.viewname)
    def predraw(self):
        """
        Sets up the attributes used by :py:class:`Layer3D()` and calls :py:meth:`Layer3D.predraw()`\ .
        """
        self.cam = self.view.cam
        super(LayerWorld,self).predraw()
    def draw(self):
        """
        Draws the view using the :py:meth:`World.render3d()` method.
        """
        self.world.render3d(self.view)
    def on_menu_enter(self,old):
        """
        Passes the event through to the WorldView to allow for custom behavior.
        """
        return self.view.on_menu_enter(old)
    def on_menu_exit(self,new):
        """
        Same as :py:meth:`on_menu_enter()`\ .
        """
        return self.view.on_menu_exit(new)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  layered.py
#
#  Copyright 2017 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__ = [
    "LayeredWidget",
    "BasicWidgetLayer","WidgetLayer",
    "GroupWidgetLayer",
    "ImageWidgetLayer","DynImageWidgetLayer",
    "ImageButtonWidgetLayer",
    "LabelWidgetLayer",
    "FormattedLabelWidgetLayer","HTMLLabelWidgetLayer",
    "BaseBorderWidgetLayer","ButtonBorderWidgetLayer",
    ]

import warnings

import pyglet
from pyglet.gl import *

from .widgets import Background, Widget
from .. import util

class LayeredWidget(Widget):
    """
    Layered Widget allowing for easy creation of custom widgets.

    A Layered Widget consists of (nearly) any amount of layers in a specific order.

    All Layers should be subclasses of :py:class:`BasicWidgetLayer` or :py:class:`WidgetLayer`\ .

    ``layers`` must be a list of 2-tuples of ``(layer,z_index)``\ .
    """
    def __init__(self,name,submenu,window,peng,
                pos=None,size=None,
                bg=None,layers=[],
                ):
        super(LayeredWidget,self).__init__(name,submenu,window,peng,pos,size,bg)

        self.layers = []
        self._layers = {}
        for l,z in layers:
            # Makes sure that all layers are sorted
            # Does not use sort() to keep original order
            self.addLayer(l,z)
    def addLayer(self,layer,z_index=None):
        """
        Adds the given layer at the given Z Index.

        If ``z_index`` is not given, the Z Index specified by the layer will be used.
        """
        if z_index is None:
            z_index = layer.z_index
        i = 0
        for l,z in self.layers:
            if z>z_index:
                break
            i+=1
        self._layers[layer.name]=layer
        self.layers.insert(i,[layer,z_index])
    def getLayer(self,name):
        """
        Returns the layer corresponding to the given name.

        :raises KeyError: If there is no Layer with the given name.
        """
        return self._layers[name]
    def on_redraw(self):
        super(LayeredWidget,self).on_redraw()
        for layer,_ in self.layers:
            layer.on_redraw()
    def redraw_layer(self,name):
        """
        Redraws the given layer.

        :raises ValueError: If there is no Layer with the given name.
        """
        if name not in self._layers:
            raise ValueError("Layer %s not part of widget, cannot redraw")
        self._layers[name].on_redraw()
    def draw(self):
        """
        Draws all layers of this LayeredWidget.

        This should normally be unneccessary, since it is recommended that layers use Vertex Lists instead of OpenGL Immediate Mode.
        """
        super(LayeredWidget,self).draw()
        for layer,_ in self.layers:
            layer._draw()
    def delete(self):
        """
        Deletes all layers within this LayeredWidget before deleting itself.

        Recommended to call if you are removing the widget, but not yet exiting the interpreter.
        """
        for layer,_ in self.layers:
            layer.delete()
        self.layers = []
        self._layers = {}
        super(LayeredWidget,self).delete()

class BasicWidgetLayer(object):
    """
    Base class for all Layers to be used with :py:class:`LayeredWidget()`\ .

    Not to be confused with :py:class:`peng3d.layer.Layer()`\ , these classes are not compatible.

    It is recommended to use :py:class:`WidgetLayer()` instead, since functionality is limited in this basic class.

    Note that the ``z_index`` will default to a reasonable value for most subclasses and thus is not required to be given explicitly.
    The ``z_index`` for this Layer defaults to ``0``\ .
    """
    z_index = 0
    def __init__(self,name,widget,
                z_index=None,
                ):
        self.name = name
        self.widget = widget

        if z_index is not None:
            self.z_index = z_index
        self.group = pyglet.graphics.OrderedGroup(self.z_index)

        self._vlists = []

    def on_redraw(self):
        """
        Called by the parent widget if this Layer should be redrawn.

        Note that it is recommended to call the Baseclass Variant of this Method first when overwriting it.
        See :py:meth:`WidgetLayer.on_redraw` for more information.
        """
        pass
    def _draw(self):
        self.predraw()
        self.draw()
        self.postdraw()

    def predraw(self):
        """
        Called before calling the :py:meth:`draw()` Method.

        Useful for setting up OpenGL state.
        """
        pass
    def draw(self):
        """
        Called to draw the layer.

        Note that using this function is discouraged, use Pyglet Vertex Lists instead.

        If you want to call this method manually, call :py:meth:`_draw()` instead.
        This will make sure that :py:meth:`predraw()` and :py:meth:`postdraw()` are called.
        """
        pass
    def postdraw(self):
        """
        Called after calling the :py:meth:`draw()` Method.

        Useful for unsetting OpenGL state.
        """
        pass

    def regVList(self,vlist):
        """
        Registers a vertex list for proper deletion once this Layer gets destroyed.

        This prevents visual artifacts from forming during deletion of a layer.
        """
        self._vlists.append(vlist)
    def delete(self):
        """
        Deletes this Layer.

        Currently only deletes VertexLists registered with :py:meth:`regVList()`\ .
        """
        for vlist in self._vlists:
            vlist.delete()
        self._vlists = []

class WidgetLayer(BasicWidgetLayer):
    """
    Subclass of :py:class:`WidgetLayer()` adding commonly used utility features.

    This subclass adds a border and offset system.

    The ``border`` is a 2-tuple of ``(x_border,y_border)``\ . The border is applied to all sides, resulting in the size being decreased by two pixel per pixel border width.

    ``offset`` is relative to the bottom left corner of the screen.
    """
    def __init__(self,name,widget,
                z_index=None,
                border=[0,0],offset=[0,0],
                ):
        super(WidgetLayer,self).__init__(name,widget,z_index)

        self._initialized = False
        self._border = border
        self._offset = offset
    def on_redraw(self):
        """
        Called when the Layer should be redrawn.

        If a subclass uses the :py:meth:`initialize()` Method, it is very important to also call the Super Class Method to prevent crashes.
        """
        super(WidgetLayer,self).on_redraw()
        if not self._initialized:
            self.initialize()
            self._initialized = True
    def initialize(self):
        """
        Called just before :py:meth:`on_redraw()` is called the first time.
        """
        pass

    @property
    def border(self):
        """
        Property to be used for setting and getting the border of the layer.

        Note that setting this property causes an immediate redraw.
        """
        if callable(self._border):
            return util.WatchingList(self._border(*(self.widget.pos+self.widget.size)),self._wlredraw_border)
        else:
            return util.WatchingList(self._border,self._wlredraw_border)
    @border.setter
    def border(self,value):
        self._border = value
        self.on_redraw()

    @property
    def offset(self):
        """
        Property to be used for setting and getting the offset of the layer.

        Note that setting this property causes an immediate redraw.
        """
        if callable(self._offset):
            return util.WatchingList(self._offset(*(self.widget.pos+self.widget.size)),self._wlredraw_offset)
        else:
            return util.WatchingList(self._offset,self._wlredraw_offset)
    @offset.setter
    def offset(self,value):
        self._offset = value
        self.on_redraw()

    def _wlredraw_border(self,wl):
        self.border = wl[:]
    def _wlredraw_offset(self,wl):
        self.offset = wl[:]

    def getPos(self):
        """
        Returns the absolute position and size of the layer.

        This method is intended for use in vertex position calculation, as the border and offset have already been applied.

        The returned value is a 4-tuple of ``(sx,sy,ex,ey)``\ .
        The two values starting with an s are the "start" position, or the lower-left corner.
        The second pair of values signify the "end" position, or upper-right corner.
        """
        # Returns sx,sy,ex,ey
        # sx,sy are bottom-left/lowest
        # ex,ey are top-right/highest
        sx,sy = self.widget.pos[0]+self.border[0]+self.offset[0],                       self.widget.pos[1]+self.border[1]+self.offset[1]
        ex,ey = self.widget.pos[0]+self.widget.size[0]-self.border[0]+self.offset[0],   self.widget.pos[1]+self.widget.size[1]-self.border[1]+self.offset[1]
        return sx,sy,ex,ey
    def getSize(self):
        """
        Returns the size of the layer, with the border size already subtracted.
        """
        return self.widget.size[0]-self.border[0]*2,self.widget.size[1]-self.border[1]*2

class GroupWidgetLayer(WidgetLayer):
    """
    Subclass of :py:class:`WidgetLayer()` allowing for using a pyglet group to manage OpenGL state.

    If no pyglet group is given, :py:class:`pyglet.graphics.NullGroup()` will be used.
    """
    def __init__(self,name,widget,
                group=None,z_index=None,
                border=[0,0],offset=[0,0],
                ):
        super(GroupWidgetLayer,self).__init__(name,widget,z_index,border,offset)

        self.group = group if group is not None else pyglet.graphics.NullGroup()
    def predraw(self):
        self.group.set_state()
    predraw.__noautodoc__ = True
    def postdraw(self):
        self.group.unset_state()
    postdraw.__noautodoc__ = True

class ImageWidgetLayer(WidgetLayer):
    """
    Subclass of :py:class:`WidgetLayer()` implementing a simple static image view.

    This layer can display any resource representable by the :py:class:`ResourceManager()`\ .

    ``img`` is a 2-tuple of ``(resource_name,category)``\ .

    The ``z_index`` for this Layer defaults to ``1``\ .
    """
    z_index = 1
    def __init__(self,name,widget,
                z_index=None,
                border=[0,0],offset=[0,0],
                img=[None,None],
                ):
        super(ImageWidgetLayer,self).__init__(name,widget,z_index,border,offset)

        self.img = widget.peng.resourceMgr.getTex(*img)
        self.img_group = util.ResourceGroup(self.img,self.group)

    def initialize(self):
        self.img_vlist = self.widget.submenu.batch2d.add(4,GL_QUADS,self.img_group,
                        "v2f",
                        ("t3f",self.img[2]),
                        )
        self.regVList(self.img_vlist)
    initialize.__noautodoc__ = True
    def on_redraw(self):
        super(ImageWidgetLayer,self).on_redraw()
        sx,sy, ex,ey = self.getPos()

        # A simple rectangle
        self.img_vlist.vertices =  sx,sy, ex,sy, ex,ey, sx,ey

class _DynImageGroup(pyglet.graphics.Group):
    def __init__(self,layer,parent=None):
        super(_DynImageGroup,self).__init__(parent)
        self.layer = layer
    def set_state(self):
        tex_info = self.layer.imgs[self.layer.cur_img]
        glEnable(tex_info[0])
        glBindTexture(tex_info[0],tex_info[1])
    def unset_state(self):
        tex_info = self.layer.imgs[self.layer.cur_img]
        glDisable(tex_info[0])

class DynImageWidgetLayer(WidgetLayer):
    """
    Subclass of :py:class:`WidgetLayer` allowing for dynamic images.

    ``imgs`` is a dictionary of names to 2-tuples of ``(resource_name,category)``\ .

    If no default image name is given, a semi-random one will be selected.

    The ``z_index`` for this Layer defaults to ``1``\ .
    """
    z_index = 1
    def __init__(self,name,widget,
                z_index=None,
                border=[0,0],offset=[0,0],
                imgs={},
                default=None,
                ):
        super(DynImageWidgetLayer,self).__init__(name,widget,z_index,border,offset)

        self.imgs = {}
        self.img_group = _DynImageGroup(self,self.group)
        for name,rsrc in imgs.items():
            self.addImage(name,rsrc)

        self.cur_img = None
        self.default_img = default

    def addImage(self,name,rsrc):
        """
        Adds an image to the internal registry.

        ``rsrc`` should be a 2-tuple of ``(resource_name,category)``\ .
        """
        self.imgs[name]=self.widget.peng.resourceMgr.getTex(*rsrc)
    def switchImage(self,name):
        """
        Switches the active image to the given name.

        :raises ValueError: If there is no such image
        """
        if name not in self.imgs:
            raise ValueError("No image of name '%s'"%name)
        elif self.cur_img==name:
            return

        self.cur_img = name
        self.on_redraw()

    def initialize(self):
        self.img_vlist = self.widget.submenu.batch2d.add(4,GL_QUADS,self.img_group,
                        "v2f",
                        "t3f",
                        )
        self.regVList(self.img_vlist)

        self.cur_img = self.cur_img if self.cur_img is not None else (self.default_img if self.default_img is not None else list(self.imgs.keys())[0])
    initialize.__noautodoc__ = True
    def on_redraw(self):
        super(DynImageWidgetLayer,self).on_redraw()
        sx,sy, ex,ey = self.getPos()

        # A simple rectangle
        self.img_vlist.vertices =  sx,sy, ex,sy, ex,ey, sx,ey

        self.img_vlist.tex_coords = self.imgs[self.cur_img][2]

class ImageButtonWidgetLayer(DynImageWidgetLayer):
    """
    Subclass of :py:class:`DynImageWidgetLayer()` that acts like an :py:class:`ImageButton()`\ .

    The ``img_*`` arguments are of the same format as in :py:class:`DynImageWidgetLayer()`\ .

    This class internally uses the :py:meth:`BasicWidget.getState()` method for getting the state of the widget.
    """
    def __init__(self,name,widget,
                z_index=None,
                border=[0,0],offset=[0,0],
                img_idle=None,img_pressed=None,img_hover=None,img_disabled=None,
                ):
        super(ImageButtonWidgetLayer,self).__init__(name,widget,z_index,border,
                    imgs={"idle":img_idle,
                          "pressed":img_pressed,
                          "hover":img_hover,
                          "disabled":img_disabled,
                          },
                    default="idle",
                    )
        self.widget.addAction("statechanged",lambda:self.switchImage(self.widget.getState()))

class LabelWidgetLayer(WidgetLayer):
    """
    Subclass of :py:class:`WidgetLayer()` displaying arbitrary plain text.

    Note that this method internally uses a pyglet Label that is centered on the Layer.

    The ``z_index`` for this Layer defaults to ``2``\ .
    """
    z_index = 2
    def __init__(self,name,widget,
                z_index=None,
                border=[0,0],offset=[0,0],
                label="",
                font_size=16,font="Arial",
                font_color=[62,67,73,255],
                multiline=False,
                ):
        super(LabelWidgetLayer,self).__init__(name,widget,z_index,border,offset)

        self._label = pyglet.text.Label(str(label),
                font_name=font,
                font_size=font_size,
                color=font_color,
                x=0,y=0,
                batch=self.widget.submenu.batch2d,
                group=self.group,
                anchor_x="center", anchor_y="center",
                width=self.getSize()[0],#height=self.getSize()[1],
                multiline=multiline,
                )
        if getattr(label,"_dynamic",False):
            def f():
                self.label = str(label)
            self.peng.i18n.addAction("setlang",f)

        self.on_redraw()

    def on_redraw(self,dt=None):
        super(LabelWidgetLayer,self).on_redraw()
        self.redraw_label()
    def redraw_label(self):
        """
        Re-draws the text by calculating its position.

        Currently, the text will always be centered on the position of the layer.
        """
        # Convenience variables
        x,y,_,_ = self.getPos()
        sx,sy = self.getSize()

        self._label.x = x+sx/2.
        self._label.y = y+sy/2.
        self._label.width = sx
        # Height is not set, would look weird otherwise
        #self._label.height = sx
        self._label._update() # Needed to prevent the label from drifting to the top-left after resizing by odd amounts

    @property
    def label(self):
        """
        Property for accessing the text of the label.
        """
        return self._label.text
    @label.setter
    def label(self,label):
        self._label.text = label

class FormattedLabelWidgetLayer(WidgetLayer):
    """
    Subclass of :py:class:`WidgetLayer()` serving as a base class for other formatted label layers.

    The Label Type can be set via the class attribute ``cls``\ , it should be set to any class that is compatible with :py:class:`pyglet.text.Label`\ .

    It is recommended to use one of the subclasses of this class instead of this class directly.

    The ``z_index`` for this Layer defaults to ``2``\ .
    """
    z_index = 2
    cls = pyglet.text.HTMLLabel
    def __init__(self,name,widget,
                z_index=None,
                border=[0,0],offset=[0,0],
                label="",
                font_size=None,font="Arial",
                font_color=None,
                multiline=False,
                ):
        super(FormattedLabelWidgetLayer,self).__init__(name,widget,z_index,border,offset)

        self.font_size = font_size
        self.font_name = font
        self.font_color = font_color

        self._label = self.cls(str(label),
                x=0,y=0,
                batch=self.widget.submenu.batch2d,
                group=self.group,
                anchor_x="center", anchor_y="center",
                width=self.getSize()[0],#height=self.getSize()[1],
                multiline=multiline,
                )
        if getattr(label,"_dynamic",False):
            def f():
                self.label = str(label)
            self.peng.i18n.addAction("setlang",f)

        self.on_redraw()

    def on_redraw(self,dt=None):
        super(FormattedLabelWidgetLayer,self).on_redraw()
        self.redraw_label()
    def redraw_label(self):
        """
        Re-draws the text by calculating its position.

        Currently, the text will always be centered on the position of the layer.
        """
        # Convenience variables
        x,y,_,_ = self.getPos()
        sx,sy = self.getSize()

        if self.font_name is not None:
            self._label.font_name = self.font_name
        if self.font_size is not None:
            self._label.font_size = self.font_size
        if self.font_color is not None:
            self._label.color = self.font_color

        self._label.x = x+sx/2.
        self._label.y = y+sy/2.
        self._label.width = sx
        # Height is not set, would look weird otherwise
        #self._label.height = sx
        self._label._update() # Needed to prevent the label from drifting to the top-left after resizing by odd amounts

    @property
    def label(self):
        """
        Property for accessing the text of the label.

        Note that depending on the type of format, this property may not exactly represent the original text as it is converted internally.
        """
        return self._label.text
    @label.setter
    def label(self,label):
        self._label.text = label

class HTMLLabelWidgetLayer(FormattedLabelWidgetLayer):
    """
    Subclass of :py:class:`FormattedLabelWidgetLayer` implementing a basic HTML Label.

    Note that not all tags are supported, see the docs for :py:class:`pyglet.text.HTMLLabel` for details.
    """
    cls = pyglet.text.HTMLLabel

# TODO: support other formats (Markdown, Pyglet-style Attributed Text, Maybe others?)

class BaseBorderWidgetLayer(WidgetLayer):
    """
    Subclass of :py:class:`WidgetLayer` that displays a basic border around the layer.

    Note that not all styles will look good with this class, see :py:class:`ButtonBorderWidgetLayer()` for more information.

    Note that the ``border`` and ``offset`` arguments have been renamed to ``base_border`` and ``base_offset`` to prevent naming conflicts.

    Subclasses may set the :py:attr:`n_vertices` value to change the number of
    vertices or :py:attr:`change_on_press` to change the default value for the
    argument of the same name.
    By default, 36 vertices are used and ``changed_on_press`` is set to ``True``\ .

    The ``z_index`` for this Layer defaults to ``0.5``\ .
    """
    z_index = 0.5 # between default and image layer

    n_vertices = 36
    change_on_press = True

    color_bg = None
    color_o = None
    color_i = None
    color_s = None
    color_h = None

    def __init__(self,name,widget,
                z_index=None,
                base_border=[0,0],base_offset=[0,0],
                border=[4,4, 4,4, 4,4, 4,4],
                style="flat",
                batch=None, change_on_press=None,
                ):
        super(BaseBorderWidgetLayer,self).__init__(name,widget,z_index,base_border,base_offset)

        self.bborder = border
        self.style = style

        if style=="material" and self.__class__==BaseBorderWidgetLayer:
            warnings.warn("Material style may have visual artifacts if used with BaseBorderWidgetLayer, use ButtonBorderWidgetLayer instead",stacklevel=2)
        elif style=="gradient" and self.__class__==BaseBorderWidgetLayer:
            warnings.warn("Gradient style may have visual artifacts if used with BaseBorderWidgetLayer, use ButtonBorderWidgetLayer instead",stacklevel=2)
        elif style=="oldshadow" and self.__class__==BaseBorderWidgetLayer:
            warnings.warn("Oldshadow style may have visual artifacts if used with BaseBorderWidgetLayer, use ButtonBorderWidgetLayer instead",stacklevel=2)

        self.batch = batch

        self.change_on_press = change_on_press if change_on_press is not None else self.change_on_press

        self.styles = {}
        self.addStyle("flat",self.s_flat)
        self.addStyle("gradient",self.s_gradient)
        self.addStyle("oldshadow",self.s_oldshadow)
        self.addStyle("material",self.s_material)

    def addStyle(self,name,func):
        """
        Adds a style to the layer.

        Note that styles must be registered seperately for each layer.

        ``name`` is the (string) name of the style.

        ``func`` will be called with its arguments as ``(bg,o,i,s,h)``\ , see :py:meth:`getColors()` for more information.
        """
        self.styles[name]=func
    def getColors(self):
        """
        Overrideable function that generates the colors to be used by various styles.

        Should return a 5-tuple of ``(bg,o,i,s,h)``\ .

        ``bg`` is the base color of the background.

        ``o`` is the outer color, it is usually the same as the background color.

        ``i`` is the inner color, it is usually lighter than the background color.

        ``s`` is the shadow color, it is usually quite a bit darker than the background.

        ``h`` is the highlight color, it is usually quite a bit lighter than the background.

        The returned values may also be statically overridden by setting the :py:attr:`color_<var>` attribute to anything but ``None``\ .
        """

        bg = self.widget.submenu.bg[:3] if isinstance(self.widget.submenu.bg,list) or isinstance(self.widget.submenu.bg,tuple) else [242,241,240]
        bg = bg if self.color_bg is None else self.color_bg

        o,i = bg, [min(bg[0]+8,255),min(bg[1]+8,255),min(bg[2]+8,255)]
        s,h = [max(bg[0]-40,0),max(bg[1]-40,0),max(bg[2]-40,0)], [min(bg[0]+12,255),min(bg[1]+12,255),min(bg[2]+12,255)]

        o = o if self.color_o is None else self.color_o
        i = i if self.color_i is None else self.color_i
        s = s if self.color_s is None else self.color_s
        h = h if self.color_h is None else self.color_h

        # Outer,Inner,Shadow,Highlight
        return bg,o,i,s,h

    def initialize(self):
        self.batch = self.batch if self.batch is not None else self.widget.submenu.batch2d

        self.vlist = self.batch.add(self.n_vertices,GL_QUADS,self.group,
            "v2f",
            "c3B",
            )
        self.regVList(self.vlist)
    initialize.__noautodoc__ = True
    def on_redraw(self):
        super(BaseBorderWidgetLayer,self).on_redraw()
        self.vlist.vertices = self.genVertices()

        if self.style not in self.styles:
            raise ValueError("Invalid Style")
        c = self.styles[self.style](*self.getColors())

        if len(c)==self.n_vertices*3: # one color per vertex
            # No need to change anything
            pass
        elif len(c)==20*3: # old button-style coloring
            c = self.stretchColors(c)
        else:
            raise ValueError("Style produced %s colors, but %s required"%(len(c)/3,self.n_vertices))

        self.vlist.colors = c
    def genVertices(self):
        """
        Called to generate the vertices used by this layer.

        The length of the output of this method should be three times the :py:attr:`n_vertices` attribute.

        See the source code of this method for more information about the order of the vertices.
        """
        sx,sy,ex,ey = self.getPos()
        b = self.bborder

        # Vertex Naming
        # Y
        # |1  2  3  4
        # |5  6  7  8
        # |9  10 11 12
        # |13 14 15 16
        # +------> X
        # Border order
        # 4 2-tuples
        # Each marks x,y offset from the respective corner
        # tuples are in order topleft,topright,bottomleft,bottomright
        # indices:
        # 0,1:topleft; 2,3:topright; 4,5:bottomleft; 6,7:bottomright
        # For a simple border that is even, just repeat the first tuple three more times

        v1 = sx,            ey
        v2 = sx+b[0],       ey
        v3 = ex-b[2],       ey
        v4 = ex,            ey

        v5 = sx,            ey-b[1]
        v6 = sx+b[0],       ey-b[1]
        v7 = ex-b[2],       ey-b[3]
        v8 = ex,            ey-b[3]

        v9 = sx,            sy+b[5]
        v10= sx+b[4],       sy+b[5]
        v11= ex-b[6],       sy+b[7]
        v12= ex,            sy+b[7]

        v13= sx,            sy
        v14= sx+b[4],       sy
        v15= ex-b[6],       sy
        v16= ex,            sy

        # Layer is separated into 9 sections, naming:
        # 1 2 3
        # 4 5 6
        # 7 8 9
        # Within each section, vertices are given counter-clockwise, starting with the bottom-left
        # 4 3
        # 1 2
        # This is important when assigning colors

        q1 = v5 +v6 +v2 +v1
        q2 = v6 +v7 +v3 +v2
        q3 = v7 +v8 +v4 +v3
        q4 = v9 +v10+v6 +v5
        q5 = v10+v11+v7 +v6
        q6 = v11+v12+v8 +v7
        q7 = v13+v14+v10+v9
        q8 = v14+v15+v11+v10
        q9 = v15+v16+v12+v11

        return q1+q2+q3+q4+q5+q6+q7+q8+q9
    def stretchColors(self,c):
        """
        Method that is called to stretch the colors.

        Note that this should be implemented by subclasses if plausible and reasonable.
        """
        # Subclasses should override this method to implement color stretching
        # Not possible here since the corners are implemented differently
        raise NotImplementedError("Color stretching is not supported on %s"%self.__class__.__name__)

    @property
    def pressed(self):
        """
        Read-only helper property to be used by styles for determining if the layer should be rendered as pressed or not.

        Note that this property may not represent the actual pressed state, it will always be False if ``change_on_press`` is disabled.
        """
        return self.change_on_press and self.widget.pressed
    @property
    def is_hovering(self):
        """
        Read-only helper property to be used by styles for determining if the layer should be rendered as hovered or not.

        Note that this property may not represent the actual hovering state, it will always be False if ``change_on_press`` is disabled.
        """
        return self.change_on_press and self.widget.is_hovering

    def s_flat(self,bg,o,i,s,h):
        # Flat style makes no difference between normal,hover and pressed
        return i*36
    s_flat.__noautodoc__ = True
    def s_gradient(self,bg,o,i,s,h):
        if self.pressed:
            i = s
        elif self.is_hovering:
            i = [min(i[0]+6,255),min(i[1]+6,255),min(i[2]+6,255)]

        return (
            o+i+o+o+
            i+i+o+o+
            i+o+o+o+
            o+i+i+o+
            i+i+i+i+
            i+o+o+i+
            o+o+i+o+
            o+o+i+i+
            o+o+o+i
            )
    s_gradient.__noautodoc__ = True
    def s_oldshadow(self,bg,o,i,s,h):
        if self.pressed:
            i = s
            s,h = h,s
        elif self.is_hovering:
            i = [min(i[0]+6,255),min(i[1]+6,255),min(i[2]+6,255)]
            s = [min(s[0]+6,255),min(s[1]+6,255),min(s[2]+6,255)]

        return (
            h+h+h+h+
            h+h+h+h+
            h+s+h+h+
            h+h+h+h+
            i+i+i+i+
            s+s+s+s+
            h+s+h+h+
            s+s+s+s+
            s+s+s+s
            )
    s_oldshadow.__noautodoc__ = True
    def s_material(self,bg,o,i,s,h):
        if self.pressed:
            i = [max(bg[0]-20,0),max(bg[1]-20,0),max(bg[2]-20,0)]
        elif self.is_hovering:
            i = [max(bg[0]-10,0),max(bg[1]-10,0),max(bg[2]-10,0)]

        return (
            o+s+o+o+
            s+s+o+o+
            s+o+o+o+
            o+s+s+o+
            i+i+i+i+
            s+o+o+s+
            o+o+s+o+
            o+o+s+s+
            o+o+o+s
            )
    s_material.__noautodoc__ = True

class ButtonBorderWidgetLayer(BaseBorderWidgetLayer):
    """
    Subclass of :py:class:`BaseBorderWidgetLayer()` implementing Button-Style borders.

    This class is based on the :py:class:`ButtonBackground` class.
    This means that most styles are also available here and should look identical.

    Note that this class uses only 20 vertices and is thus not compatible with styles
    created for use with :py:class:`BaseBorderWidgetLayer`\ .

    Also note that the ``border`` argument also only receives two values instead of eight.
    """
    n_vertices = 20
    def __init__(self,name,widget,
                z_index=None,
                base_border=[0,0],base_offset=[0,0],
                border=[4,4],
                style="flat",
                batch=None, change_on_press=None,
                ):
        super(ButtonBorderWidgetLayer,self).__init__(name,widget,z_index,base_border,base_offset,border[0:2],style,batch,change_on_press)
    def genVertices(self):
        sx,sy, ex,ey = self.getPos()
        bx,by = self.bborder

        # Vertex Naming
        # Y
        # | 1       2
        # |  5     6
        # |  7     8
        # | 3       4
        # +-------> X
        # Quad order
        #    1
        # 4  5  2
        #    3
        # Within each quad, vertices are ordered from the bottom-left counter-clockwise

        v1 = sx,        ey
        v2 = ex,        ey
        v3 = sx,        sy
        v4 = ex,        sy

        v5 = sx+bx,     ey-by
        v6 = ex-bx,     ey-by
        v7 = sx+bx,     sy+by
        v8 = ex-bx,     sy+by

        q1 = v5+v6+v2+v1
        q2 = v8+v4+v2+v6
        q3 = v3+v4+v8+v7
        q4 = v3+v7+v5+v1
        q5 = v7+v8+v6+v5

        return q1+q2+q3+q4+q5
    genVertices.__noautodoc__ = True

    def s_flat(self,bg,o,i,s,h):
        # Flat style makes no difference between normal,hover and pressed
        cb1 = i+i+i+i
        cb2 = i+i+i+i
        cb3 = i+i+i+i
        cb4 = i+i+i+i
        cc  = i+i+i+i

        return cb1+cb2+cb3+cb4+cc
    s_flat.__noautodoc__ = True
    def s_gradient(self,bg,o,i,s,h):
        if self.pressed:
            i = s
        elif self.is_hovering:
            i = [min(i[0]+6,255),min(i[1]+6,255),min(i[2]+6,255)]
        cb1 = i+i+o+o
        cb2 = i+o+o+i
        cb3 = o+o+i+i
        cb4 = o+i+i+o
        cc  = i+i+i+i

        return cb1+cb2+cb3+cb4+cc
    s_gradient.__noautodoc__ = True
    def s_oldshadow(self,bg,o,i,s,h):
        if self.pressed:
            i = s
            s,h = h,s
        elif self.is_hovering:
            i = [min(i[0]+6,255),min(i[1]+6,255),min(i[2]+6,255)]
            s = [min(s[0]+6,255),min(s[1]+6,255),min(s[2]+6,255)]
        cb1 = h+h+h+h
        cb2 = s+s+s+s
        cb3 = s+s+s+s
        cb4 = h+h+h+h
        cc  = i+i+i+i

        return cb1+cb2+cb3+cb4+cc
    s_oldshadow.__noautodoc__ = True
    def s_material(self,bg,o,i,s,h):
        if self.pressed:
            i = [max(bg[0]-20,0),max(bg[1]-20,0),max(bg[2]-20,0)]
        elif self.is_hovering:
            i = [max(bg[0]-10,0),max(bg[1]-10,0),max(bg[2]-10,0)]
        cb1 = s+s+o+o
        cb2 = s+o+o+s
        cb3 = o+o+s+s
        cb4 = o+s+s+o
        cc  = i+i+i+i

        return cb1+cb2+cb3+cb4+cc
    s_material.__noautodoc__ = True

    def stretchColors(self,c):
        return c
    stretchColors.__noautodoc__ = True