#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  text.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__ = [
    "Label",
    "TextInput","TextInputBackground"
    ]

import time

import pyglet
from pyglet.gl import *
from pyglet.window import key

try:
    import pyperclip
    HAVE_PYPERCLIP = True
except ImportError:
    HAVE_PYPERCLIP = False

from .widgets import Background,Widget,mouse_aabb
from .button import ButtonBackground

class Label(Widget):
    """
    Simple widget that can display any single-line non-formatted string.

    This widget does not use any background by default.

    The default font color is chosen to work on the default background color and may need to be changed if the background color is changed.
    """
    def __init__(self,name,submenu,window,peng,
                 pos=None,size=None,
                 bg=None,
                 label="Label",
                 font_size=16,font="Arial",
                 font_color=[62,67,73,255],
                 multiline=False,
                 label_cls=pyglet.text.Label,
                ):
        super(Label,self).__init__(name,submenu,window,peng,pos,size,bg)

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

        if label_cls == pyglet.text.HTMLLabel:
            self._label = label_cls(str(label),
                    # Font is changed later
                    x=0,y=0,
                    batch=self.submenu.batch2d,
                    anchor_x="center", anchor_y="center",
                    group=pyglet.graphics.OrderedGroup(1),
                    width=self.size[0],height=self.size[1],
                    multiline=multiline,
                    )
            self._label.font_name = font
            self._label.font_size = font_size
            self._label.font_color = font_color
        else:
            self._label = label_cls(str(label),
                    font_name=font,
                    font_size=font_size,
                    color=font_color,
                    x=0,y=0,
                    batch=self.submenu.batch2d,
                    anchor_x="center", anchor_y="center",
                    group=pyglet.graphics.OrderedGroup(1),
                    width=self.size[0],height=self.size[1],
                    multiline=multiline,
                    )
        if getattr(label,"_dynamic",False):
            def f():
                self.label = str(label)
            self.peng.i18n.addAction("setlang",f)

        self.redraw()

    def on_redraw(self,dt=None):
        super(Label,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 label.
        """
        # Convenience variables
        sx,sy = self.size
        x,y = self.pos

        # Label position
        self._label.font_name = self.font_name
        self._label.font_size = self.font_size
        self._label.font_color = self.font_color

        self._label.x = int(x+sx/2.)
        self._label.y = int(y+sy/2.)
        self._label.width = self.size[0]
        self._label.height = self.size[1]
        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 = str(label)

class TextInputBackground(ButtonBackground):
    """
    Background for the :py:class:`TextInput` Widget.

    This background uses the button drawing routines and adds a cursor.
    """
    def __init__(self,*args,**kwargs):
        self.stime = 0
        super(TextInputBackground,self).__init__(*args,**kwargs)
    def init_bg(self):
        super(TextInputBackground,self).init_bg()
        self.vlist_cursor = self.submenu.batch2d.add(2,GL_LINES,pyglet.graphics.OrderedGroup(10),
            "v2f",
            "c3B",
            )
        self.reg_vlist(self.vlist_cursor)
    def redraw_bg(self):
        super(TextInputBackground,self).redraw_bg()

        sx,sy,x,y,bx,by = self.getPosSize()

        # TODO: make this less hacky
        otext = self.widget._text.text
        self.widget._text.text = self.widget._text.text[:self.widget.cursor_pos]
        tw = self.widget._text.content_width+2 if len(self.widget._text.text)!=0 else 0
        self.widget._text.text = otext

        v = x+tw+bx,y+by, x+tw+bx,y+sy-by
        self.vlist_cursor.vertices = v

        bg,o,i,s,h = self.getColors()
        # TODO: add compat with arbitrary backgrounds, will not work on material
        c = s*2 if (self.stime-time.time())%1>.5 or not self.widget.focussed else [0,0,0]*2
        self.vlist_cursor.colors = c

    @property
    def pressed(self):
        return self.change_on_press and self.widget.focussed

class TextInput(Widget):
    """
    Basic Textual Input widget.

    By default, this widget uses :py:class:`TextInputBackground` as its Background class.

    The optional default text will only be displayed if the text is empty.

    The ``allow_overflow`` flag determines if the text entered can be longer than the size of the :py:class:`TextInput`\ .

    The ``allow_copypaste`` flag controls whether or not the user can copy and paste the contents of the text box.
    By default, copying and pasting is allowed. This flag can also be set to ``"force"`` to force a crash
    with an appropriate error message if the :py:mod:`pyperclip` module is not available.
    Currently, only copying, pasting and cutting the whole text box is supported, as there is no mechanism for text selection yet.

    The key combinations used by this widget can be configured in the config via the :confval:`controls.keybinds.common.*` config values.
    """
    def __init__(self,name,submenu,window,peng,
                 pos=None,size=None,
                 bg=None,
                 text="",default="",
                 border=[4,4],borderstyle="flat",
                 font_size=16,font="Arial",
                 font_color=[62,67,73,255],
                 font_color_default=[62,67,73,200],
                 allow_overflow=False,
                 allow_copypaste=True,
                 min_size=None,
                 ):

        if allow_copypaste == "force" and not HAVE_PYPERCLIP:
            raise ValueError("%s with name %s requires Clipboard support, but Pyperclip is not installed"%(self.__class__.__name__,name))

        if bg is None:
            bg = TextInputBackground(self,border,borderstyle)
        super(TextInput,self).__init__(name,submenu,window,peng,pos,size,bg,min_size)

        self.cursor_pos = 0
        self.focussed = False
        self.allow_overflow = allow_overflow
        self.allow_copypaste = True if allow_copypaste is True or allow_copypaste=="force" else False
        self.force_copypaste = True if allow_copypaste=="force" else False

        self._text = pyglet.text.Label(str(text),
                font_name=font,
                font_size=font_size,
                color=font_color,
                x=0,y=0,
                batch=None,#self.submenu.batch2d,
                anchor_x="left", anchor_y="center",
                width=self.size[0],height=self.size[1]
                )
        self._default = pyglet.text.Label(str(default),
                font_name=font,
                font_size=font_size,
                color=font_color_default,
                x=0,y=0,
                batch=None,
                anchor_x="left", anchor_y="center",
                width=self.size[0],height=self.size[1]
                )
        self.cursor_pos = 0
        self.focussed = False

        if getattr(text,"_dynamic",False):
            def f():
                self.text = str(text)
            self.peng.i18n.addAction("setlang",f)
        if getattr(default,"_dynamic",False):
            def f():
                self.default = str(default)
            self.peng.i18n.addAction("setlang",f)

        self.peng.i18n.addAction("setlang",self.redraw) # for dynamic size

        self.peng.registerEventHandler("on_text",self.on_text)
        self.peng.registerEventHandler("on_text_motion",self.on_text_motion)
        if self.allow_copypaste:
            self.peng.keybinds.add(self.window.cfg["controls.keybinds.common.copy"],"peng3d:gui.widget.textinput.%s.copy"%self.name,self.on_copy)
            self.peng.keybinds.add(self.window.cfg["controls.keybinds.common.paste"],"peng3d:gui.widget.textinput.%s.paste"%self.name,self.on_paste)
            self.peng.keybinds.add(self.window.cfg["controls.keybinds.common.cut"],"peng3d:gui.widget.textinput.%s.cut"%self.name,self.on_cut)

        self.redraw()
        pyglet.clock.schedule_interval(lambda dt: self.redraw(),1./2.)

    def on_redraw(self):
        super(TextInput,self).on_redraw()
        self.redraw_label()
    def redraw_label(self):
        """
        Re-draws the label by calculating its position.

        Currently, the label will always be centered on the position of the label.
        """
        # Convenience variables
        sx,sy = self.size
        x,y = self.pos

        # Label position
        x = x+self.bg.border[0]
        y = y+sy/2.-self._text.font_size/4.
        w = self.size[0]
        h = self.size[1]

        self._text.x,self._text.y = x,y
        self._text.width,self._text.height=w,h

        self._default.x,self._default.y = x,y
        self._default.width,self._default.height=w,h

        self._text._update() # Needed to prevent the label from drifting to the top-left after resizing by odd amounts
        self._default._update()

    def draw(self):
        super(TextInput,self).draw()
        if self._text.text=="":
            self._default.draw()
        else:
            self._text.draw()
    draw.__noautodoc__ = True

    def on_text(self,text):
        if not (self.focussed and self.clickable):
            return

        otext = self.text
        t = self.text
        t = t[:self.cursor_pos]+text+t[self.cursor_pos:]
        self.text = t
        self.cursor_pos+=len(self.text)-len(otext)
        self.cursor_pos = min(self.cursor_pos,len(self.text))
        self.redraw()

    def on_text_motion(self,motion):
        if not (self.focussed and self.clickable):
            return

        if motion == key.MOTION_BACKSPACE:
            l = list(self.text)
            try:
                # Deletes the character in front of the cursor
                del l[self.cursor_pos-1]
            except Exception:
                return
            self.text = "".join(l)
            self.cursor_pos-=1
            self.cursor_pos = max(self.cursor_pos,0)
            self.redraw()
        elif motion == key.MOTION_DELETE:
            l = list(self.text)
            try:
                # Deletes the character after the cursor
                del l[self.cursor_pos]
            except Exception:
                return
            self.text = "".join(l)
            self.redraw()
        elif motion == key.MOTION_LEFT:
            # Moves the cursor to the left one character
            self.cursor_pos-=1
            self.cursor_pos = max(self.cursor_pos,0)
            self.redraw()
        elif motion == key.MOTION_RIGHT:
            # Moves the cursor to the right one character
            self.cursor_pos+=1
            self.cursor_pos = min(self.cursor_pos,len(self.text))
            self.redraw()
        elif motion == key.MOTION_BEGINNING_OF_LINE or motion == key.MOTION_BEGINNING_OF_FILE:
            # Moves the cursor to the beginning
            self.cursor_pos=0
            self.redraw()
        elif motion == key.MOTION_END_OF_LINE or motion == key.MOTION_END_OF_FILE:
            # Moves the cursor to the end
            self.cursor_pos = len(self.text)
            self.redraw()

    def on_mouse_press(self,x,y,button,modifiers):
        if not self.clickable:
            return
        elif mouse_aabb([x,y],self.size,self.pos):
            if button == pyglet.window.mouse.LEFT:
                self.doAction("press")
                self.pressed = True
                self.focussed = True
                self.bg.stime = time.time()
            elif button == pyglet.window.mouse.RIGHT:
                self.doAction("context")
            self.redraw()
        else:
            self.focussed = False
            self.redraw()

    def on_copy(self,symbol,modifiers,release):
        if release:
            return

        try:
            pyperclip.copy(self.text)
        except Exception:
            if self.force_copypaste:
                raise
    def on_paste(self,symbol,modifiers,release):
        if release:
            return

        try:
            t = pyperclip.paste()

            # Tries to copy as much as possible, until it does not fit
            if not self.allow_overflow:
                # TODO: implement more efficient algorithm
                self.text = ""
                for c in t:
                    self.text+=c
            else:
                self.text=t
        except Exception:
            if self.force_copypaste:
                raise
    def on_cut(self,symbol,modifiers,release):
        if release:
            return

        try:
            pyperclip.copy(self.text)
            self.text = ""
        except Exception:
            if self.force_copypaste:
                raise

    @property
    def text(self):
        """
        Property for accessing the text.
        """
        return self._text.text
    @text.setter
    def text(self,text):
        text = str(text)
        otext = self._text.text
        self._text.text = text
        self._text._update()
        # Reverts the change if
        # 1. the text length has NOT increased
        # 2. and allow_overflow is False
        # 3. and the text is wider than the widget size minus the border size
        # 4. and the text is not empty
        if len(otext)<len(text) and not self.allow_overflow and self.size[0]-self.bg.border[0]*2<=self._text.content_width if len(self.text)!=0 else 0:
            self._text.text=otext
            self._text._update()
        self.doAction("textchange")
        self.redraw() # necessary for size/pos that depends on label size

    def getContentSize(self):
        l = [max(self._text.content_width,self._default.content_width),max(self._text.content_height,self._default.content_height)]
        b = self.bg.border # TODO: make this work with borderless backgrounds
        return [l[0]+b[0]*2,l[1]+b[1]*2]

    @property
    def default(self):
        """
        Property for accessing the default text.
        """
        return self._default.text
    @default.setter
    def default(self,default):
        self._default.text = str(default)


#!/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"]

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

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 .. import util

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,window,peng):
        super(GUIMenu,self).__init__(name,window,peng)
        pyglet.clock.schedule_interval(lambda dt: None,1./30)
        self.submenus = {}
        self.activeSubMenu = None

    def addSubMenu(self,submenu):
        """
        Adds a :py:class:`SubMenu` to this Menu.

        Note that nothing will be displayed unless a submenu is activated.
        """
        self.submenus[submenu.name] = submenu
    def changeSubMenu(self,submenu):
        """
        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")

    def draw(self):
        """
        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()
        self.submenu.draw()

    @property
    def submenu(self):
        """
        Property containing the :py:class:`SubMenu` instance that is currently active.
        """
        return self.submenus[self.activeSubMenu]

class SubMenu(util.ActionDispatcher):
    """
    Sub Menu of the GUI system.

    Each instance must be registered with their menu to work properly, see :py:meth:`GUIMenu.addSubMenu()`\ .

    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.
    """
    def __init__(self,name,menu,window,peng):
        self.name = name
        self.menu = menu
        self.window = window
        self.peng = peng

        self.widgets = collections.OrderedDict()

        self.bg = [242,241,240,255]
        self.bg_vlist = pyglet.graphics.vertex_list(4,
            "v2f",
            "c4B",
            )
        self.peng.registerEventHandler("on_resize",self.on_resize)
        self.on_resize(*self.size)

        self.batch2d = pyglet.graphics.Batch()

    def draw(self):
        """
        Draws the submenu and its background.

        Note that this leaves the OpenGL state set to 2d drawing.
        """
        # Sets the OpenGL state for 2D-Drawing
        self.window.set2d()

        # 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
        else:
            raise TypeError("Unknown background type")

        # In case the background modified relevant state
        self.window.set2d()

        # Check that all widgets that need redrawing have been redrawn
        for widget in self.widgets.values():
            if widget.do_redraw:
                widget.on_redraw()
                widget.do_redraw = False

        # Actually draw the content
        self.batch2d.draw()

        # Call custom draw methods where needed
        for widget in self.widgets.values():
            widget.draw()

    def addWidget(self,widget):
        """
        Adds a widget to this submenu.
        """
        self.widgets[widget.name]=widget
    def getWidget(self,name):
        """
        Returns the widget with the given name.
        """
        return self.widgets[name]

    def delWidget(self,widget):
        """
        Deletes the widget by the given name.

        Note that this feature is currently experimental as there seems to be a memory leak with this method.
        """
        # TODO: fix memory leak upon widget deletion
        #print("*"*50)
        #print("Start delWidget")
        if isinstance(widget,BasicWidget):
            widget = widget.name

        if widget not in self.widgets:
            return

        w = self.widgets[widget]

        #print("refs: %s"%sys.getrefcount(w))

        w.delete()
        del self.widgets[widget]
        del widget

        #w_wref = weakref.ref(w)

        #print("GC: GARBAGE")
        #print(gc.garbage)
        #print("Widget Info")
        #print(w_wref())
        #import objgraph
        #print("Objgraph")
        #objgraph.show_refs([w], filename='./mem_widget.png')
        #print("refs: %s"%sys.getrefcount(w))
        #w_r = gc.get_referrers(w)
        #print("GC: REFS")
        #for w_ref in w_r:
        #    print(repr(w_ref)[:512])
        #print("GC: END")
        #print("len: %s"%len(w_r))
        #del w_ref,w_r
        #print("after del %s"%w_wref())
        #print("refs: %s"%sys.getrefcount(w))
        del w
        #print("Final WRef")
        #print(w_wref())

    def setBackground(self,bg):
        """
        Sets the background of the submenu.

        The background may be a RGB or RGBA color to fill the background with.

        Alternatively, a :py:class:`peng3d.layer.Layer` instance or other object with a ``.draw()`` method may be supplied.
        It is also possible to supply any other method or function that will get called.

        Also, the strings ``flat``\ , ``gradient``\ , ``oldshadow`` and ``material`` may be given, resulting in a background that looks similar to buttons.

        Lastly, the string ``"blank"`` may be passed to skip background drawing.
        """
        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 pos(self):
        return [0,0] # As property to prevent bug with accidental manipulation

    @property
    def size(self):
        return self.window.width,self.window.height

    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()

    def on_enter(self,old):
        pass
    def on_exit(self,new):
        pass

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,menu,window,peng):
        Layer2D.__init__(self,menu,window,peng)
        GUIMenu.__init__(self,menu.name,window,peng)
    def draw(self):
        """
        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 *


#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  button.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__ = [
    "Button","ButtonBackground",
    "ImageButton","ImageBackground",
    "FramedImageButton","FramedImageBackground",
    "ToggleButton",
    "Checkbox","CheckboxBackground"
]

import pyglet
from pyglet.gl import *

from .widgets import Background,Widget,mouse_aabb

LABEL_FONT_SIZE = 16

class ButtonBackground(Background):
    """
    Background for the :py:class:`Button` Widget.

    This background renders the button and its border, but not the label.
    """
    n_vertices = 20
    change_on_press = True
    vlist_layer = 0 # used as the first argument to OrderedGroup

    def __init__(self,widget,border=[4,4],borderstyle="flat",
                 batch=None,change_on_press=None):

        super(ButtonBackground,self).__init__(widget)

        self.border = border
        self.borderstyle = borderstyle

        self.batch = batch

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

        self.borderstyles = {}
        self.addBorderstyle("flat",self.bs_flat)
        self.addBorderstyle("gradient",self.bs_gradient)
        self.addBorderstyle("oldshadow",self.bs_oldshadow)
        self.addBorderstyle("material",self.bs_material)
    def init_bg(self):
        # Can only be initialized here due to order of initialization
        self.batch = self.batch if self.batch is not None else self.submenu.batch2d

        self.vlist = self.batch.add(self.n_vertices,GL_QUADS,pyglet.graphics.OrderedGroup(self.vlist_layer),
            "v2f",
            "c3B",
            )
        self.reg_vlist(self.vlist)
    def redraw_bg(self):
        # Convenience variables
        sx,sy,x,y,bx,by = self.getPosSize()

        # Button background

        # Outer vertices
        #    x          y
        v1 = x,         y+sy
        v2 = x+sx,      y+sy
        v3 = x,         y
        v4 = x+sx,      y

        # Inner vertices
        #    x          y
        v5 = x+bx,      y+sy-by
        v6 = x+sx-bx,   y+sy-by
        v7 = x+bx,      y+by
        v8 = x+sx-bx,   y+by

        # 5 Quads, for edges and the center
        qb1 = v5+v6+v2+v1
        qb2 = v8+v4+v2+v6
        qb3 = v3+v4+v8+v7
        qb4 = v3+v7+v5+v1
        qc  = v7+v8+v6+v5

        v = qb1+qb2+qb3+qb4+qc
        self.vlist.vertices = v

        if self.borderstyle not in self.borderstyles:
            raise ValueError("Invalid Border style")
        c = self.borderstyles[self.borderstyle](*self.getColors())
        self.vlist.colors = c

    def getPosSize(self):
        """
        Helper function converting the actual widget position and size into a usable and offsetted form.

        This function should return a 6-tuple of ``(sx,sy,x,y,bx,by)`` where sx and sy are the size, x and y the position and bx and by are the border size.

        All values should be in pixels and already include all offsets, as they are used directly for generation of vertex data.

        This method can also be overridden to limit the background to a specific part of its widget.
        """
        sx,sy = self.widget.size
        x,y = self.widget.pos
        bx,by = self.border
        return sx,sy,x,y,bx,by
    def getColors(self):
        """
        Overrideable function that generates the colors to be used by various borderstyles.

        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.
        """
        bg = self.submenu.bg[:3] if isinstance(self.submenu.bg,list) or isinstance(self.submenu.bg,tuple) else [242,241,240]
        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)]
        # Outer,Inner,Shadow,Highlight
        return bg,o,i,s,h

    def addBorderstyle(self,name,func):
        """
        Adds a borderstyle to the background object.

        Note that borderstyles must be registered seperately for each background object.

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

        ``func`` will be called with its arguments as ``(bg,o,i,s,h)``\ , see :py:meth:`getColors()` for more information.
        """
        self.borderstyles[name]=func

    @property
    def pressed(self):
        """
        Read-only helper property to be used by borderstyles for determining if the widget 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 borderstyles for determining if the widget 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 bs_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
    bs_flat.__noautodoc__ = True
    def bs_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
    bs_gradient.__noautodoc__ = True
    def bs_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
    bs_oldshadow.__noautodoc__ = True
    def bs_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
    bs_material.__noautodoc__ = True

class Button(Widget):
    """
    Button Widget allowing the user to trigger specific actions.

    By default, this Widget uses :py:class:`ButtonBackground` as its Background class.

    The border given is in pixels from the left/right and top/bottom, respectively.

    The borderstyle may be either ``flat``\ , which has no border at all,
    ``gradient``\ , which fades from the inner color to the background color,
    ``oldshadow``\ , which uses a simple fake shadow with the light from the top-left corner and
    ``material``\ , which imitates Google Material Design shadows.

    Also, the label of the button may only be a single line of text, anything else may produce undocumented behavior.

    If necessary, the font size of the Label may be changed via the global Constant :py:data:`LABEL_FONT_SIZE`\ , changes will only apply to Buttons created after change.
    The text color used is ``[62,67,73,255]`` in RGBA and the font used is Arial, which should be available on most systems.
    """
    def __init__(self,name,submenu,window,peng,
                 pos=None, size=[100,24],bg=None,
                 border=[4,4], borderstyle="flat",
                 label="Button",min_size=None):
        if bg is None:
            bg = ButtonBackground(self,border,borderstyle)
        super(Button,self).__init__(name,submenu,window,peng,pos,size,bg,min_size)
        self._label = pyglet.text.Label(str(label),
                font_name="Arial",
                font_size=LABEL_FONT_SIZE,
                color=[62,67,73,255],
                x=0,y=0,
                batch=self.submenu.batch2d,
                group=pyglet.graphics.OrderedGroup(1),
                anchor_x="center", anchor_y="center"
                )
        if getattr(label,"_dynamic",False):
            def f():
                self.label = str(label)
            self.peng.i18n.addAction("setlang",f)
        self.peng.i18n.addAction("setlang",self.redraw) # for dynamic size
        self.redraw()

        # Redraws the button every 2 seconds to prevent glitched graphics
        pyglet.clock.schedule_interval(lambda dt:self.redraw(),2)

    def on_redraw(self):
        super(Button,self).on_redraw()
        self.redraw_label()
    def redraw_label(self):
        """
        Re-draws the label by calculating its position.

        Currently, the label will always be centered on the Button.
        """
        # Convenience variables
        sx,sy = self.size
        x,y = self.pos

        # Label position
        self._label.x = x+sx/2.
        self._label.y = y+sy/2.
        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 label of this Button.
        """
        return self._label.text
    @label.setter
    def label(self,label):
        # TODO: make this work with changing languages if previous label was not dynamic
        self._label.text = str(label)
        self.redraw() # necessary for size/pos that depends on label size

    def getContentSize(self):
        l = [self._label.content_width,self._label.content_height]
        b = self.bg.border # TODO: make this work with borderless backgrounds
        return [l[0]+b[0]*2,l[1]+b[1]*2]

    def delete(self):
        self._label.delete()
        del self._label
        super(Button,self).delete()
    delete.__noautodoc__ = True

class _FakeTexture(object):
    def __init__(self,target,texid,texcoords):
        self.target = target
        self.id = texid
        self.tex_coords = texcoords
        self.anchor_x = 0
        self.anchor_y = 0

class ImageBackground(Background):
    """
    Background for the :py:class:`ImageButton` Widget.

    This background renders a image given based on whether the widget is pressed, hovered over or disabled.

    It should also be possible to use this class as a background for most other Widgets.
    """
    def __init__(self,widget,bg_idle=[GL_TEXTURE_2D,GL_TEXTURE1,[0]*12],bg_hover=None,bg_disabled=None,bg_pressed=None):
        bg = bg_idle
        self.bg_texinfo = bg
        if bg_hover is None:
            assert bg_hover[1]==self.bg_texinfo[1] # see init_bg()
            self.bg_hover=bg
        else:
            self.bg_hover=bg_hover
        if bg_disabled is None:
            assert bg_disabled[1]==self.bg_texinfo[1] # see init_bg()
            self.bg_disabled=bg
        else:
            self.bg_disabled=bg_disabled
        if bg_pressed is None:
            assert bg_pressed[1]==self.bg_texinfo[1] # see init_bg()
            self.bg_pressed=bg
        else:
            self.bg_pressed=bg_pressed
        super(ImageBackground,self).__init__(widget)
    def init_bg(self):
        # TODO: add seperate groups per active texture, in case the different images are on different textures
        self.bg_group = pyglet.graphics.TextureGroup(_FakeTexture(*self.bg_texinfo))
        self.vlist_bg = self.submenu.batch2d.add(4,GL_QUADS,self.bg_group,
            "v2f",
            ("t3f",self.bg_texinfo[2]),
            )
        self.reg_vlist(self.vlist_bg)
    def redraw_bg(self):
        # Convenience variables
        sx,sy = self.widget.size
        x,y = self.widget.pos

        # Button background

        # Outer vertices
        #    x          y
        v1 = x,         y+sy
        v2 = x+sx,      y+sy
        v3 = x,         y
        v4 = x+sx,      y

        q = v3+v4+v2+v1

        self.vlist_bg.vertices = q

        # Textures

        if not self.widget.enabled:
            self.vlist_bg.tex_coords = self.bg_disabled[2]
        elif self.widget.pressed:
            self.vlist_bg.tex_coords = self.bg_pressed[2]
        elif self.widget.is_hovering:
            self.vlist_bg.tex_coords = self.bg_hover[2]
        else:
            self.vlist_bg.tex_coords = self.bg_texinfo[2]

class ImageButton(Button):
    """
    Subclass of :py:class:`Button` using an image as a background instead.

    By default, this Widget uses :py:class:`ImageBackground` as its Background class.

    There are no changes to any other mechanics of the Button, only visually.
    """
    def __init__(self,name,submenu,window,peng,
                 pos=None, size=[100,24],bg=None,
                 label="Button",
                 bg_idle=[GL_TEXTURE_2D,GL_TEXTURE1,[0]*12],
                 bg_hover=None,
                 bg_disabled=None,
                 bg_pressed=None,
                 ):
        if bg is None:
            bg = ImageBackground(self,bg_idle,bg_hover,bg_disabled,bg_pressed)
        super(ImageButton,self).__init__(name,submenu,window,peng,pos,size,bg,label=label)

class FramedImageBackground(ImageBackground):
    """
    Background for the :py:class:`FramedImageButton` Widget.

    This background is similar to :py:class:`ImageBackground`\ , but it attempts to scale smarter with less artifacts.

    Note that this feature is currently not working properly, and will thus output a warning on the console if tried to use.
    """
    def __init__(self,widget,bg_idle=[GL_TEXTURE_2D,GL_TEXTURE1,[0]*12],bg_hover=None,bg_disabled=None,bg_pressed=None,frame_size=[[2,10,2],[2,10,2]]):
        print("Use FramedImageBackground with care, may produce graphical glitches and crashes")
        # TODO: fix this
        self.frame_size = frame_size
        self.repeat_edge=True
        self.repeat_center=True
        super(FramedImageBackground,self).__init__(widget,bg_idle,bg_hover,bg_disabled,bg_pressed)
    def init_bg(self):
        self.bg_group = pyglet.graphics.TextureGroup(_FakeTexture(*self.bg_texinfo))
        self.vlist_bg = self.submenu.batch2d.add(36,GL_QUADS,self.bg_group,
            "v2f",
            "t3f",
            )
        self.reg_vlist(self.vlist_bg)
    def redraw_bg(self):
        # Convenience variables
        sx,sy = self.widget.size
        x,y = self.widget.pos
        fx,fy = self.frame_size
        osx,osy = sum(fx),sum(fy)
        bsx = (osx/osy)*sy
        bsy = (osx/osy)*sy
        # frame-axis-left/right/center/up/down
        fxl,fxc,fxr = fx
        fyu,fyc,fyd = fy

        fxl,fxc,fxr = (fxl/sum(fx))*bsx, (fxc/sum(fx))*bsx, (fxr/sum(fx))*bsx
        fyu,fyc,fyd = (fyu/sum(fy))*bsy, (fyc/sum(fy))*bsy, (fyd/sum(fy))*bsy

        #assert fxl+fxc+fxr==sx
        #assert fyu+fyc+fyd==sy
        # To avoid confusion when supplied invalid numbers

        # 16 Vertices according to button_scheme.xcf

        v1  = x,         y+sy
        v2  = x+fxl,     y+sy
        v3  = x+sx-fxr,  y+sy
        v4  = x+sx,      y+sy
        v5  = x+sx,      y+sy-fyu
        v6  = x+sx,      y+fyd
        v7  = x+sx,      y
        v8  = x+sx-fxr,  y
        v9  = x+fxl,     y
        v10 = x,         y
        v11 = x,         y+fyd
        v12 = x,         y+sy-fyu
        v13 = x+fxl,     y+sy-fyu
        v14 = x+sx-fxr,  y+sy-fyu
        v15 = x+sx-fxr,  y+fyd
        v16 = x+fxl,     y+fyd

        bv1  = x,         y+bsy
        bv2  = x+fxl,     y+bsy
        bv3  = x+bsx-fxr,  y+bsy
        bv4  = x+bsx,      y+bsy
        bv5  = x+bsx,      y+bsy-fyu
        bv6  = x+bsx,      y+fyd
        bv7  = x+bsx,      y
        bv8  = x+bsx-fxr,  y
        bv9  = x+fxl,     y
        bv10 = x,         y
        bv11 = x,         y+fyd
        bv12 = x,         y+bsy-fyu
        bv13 = x+fxl,     y+bsy-fyu
        bv14 = x+bsx-fxr,  y+bsy-fyu
        bv15 = x+bsx-fxr,  y+fyd
        bv16 = x+fxl,     y+fyd

        qc1 = v10+v9 +v16+v11
        qc2 = v12+v13+v2 +v1
        qc3 = v14+v5 +v4 +v3
        qc4 = v8 +v7 +v6 +v15

        #qe1 = bv9 +bv8 +bv15+bv16
        #qe2 = bv11+bv16+bv13+bv12
        #qe3 = bv13+bv14+bv3 +bv2
        #qe4 = bv15+bv6 +bv5 +bv14
        qe1 = v9 +v8 +v15+v16
        qe2 = v11+v16+v13+v12
        qe3 = v13+v14+v3 +v2
        qe4 = v15+v6 +v5 +v14

        qc = v16+v15+v14+v13

        v = qc1+qc2+qc3+qc4+qe1+qe2+qe3+qe4+qc

        # Texture Coords
        if not self.widget.enabled:
            texcoords = self.bg_disabled[2]
        elif self.widget.pressed:
            texcoords = self.bg_pressed[2]
        elif self.widget.is_hovering:
            texcoords = self.bg_hover[2]
        else:
            texcoords = self.bg_texinfo[2]
        print("###")
        print(self.widget.enabled,self.widget.pressed,self.widget.is_hovering)
        print(texcoords)
        x,y,_,sx,_,_,_,sy,_,_,_,_ = texcoords
        sx,sy=sx-x,sy-y
        asx,asy = min(sx,(osx/osy)*sx),min(sy,(osx/osy)*sx)
        fxl,fxc,fxr=(fx[0]/sum(fx))*sx, (fx[1]/sum(fx))*sx, (fx[2]/sum(fx))*sx
        fyu,fyc,fyd=(fy[0]/sum(fy))*sy, (fy[1]/sum(fy))*sy, (fy[2]/sum(fy))*sy

        print(x,y,sx,sy)
        print(fxl,fxc,fxr)
        print(fyu,fyc,fyd)
        print("###")

        assert fxl+fxc+fxr==sx
        assert fyu+fyc+fyd==sy

        t1  = x,         y+sy,      0
        t2  = x+fxl,     y+sy,      0
        t3  = x+sx-fxr,  y+sy,      0
        t4  = x+sx,      y+sy,      0
        t5  = x+sx,      y+sy-fyu,  0
        t6  = x+sx,      y+fyd,     0
        t7  = x+sx,      y,         0
        t8  = x+sx-fxr,  y,         0
        t9  = x+fxl,     y,         0
        t10 = x,         y,         0
        t11 = x,         y+fyd,     0
        t12 = x,         y+sy-fyu,  0
        t13 = x+fxl,     y+sy-fyu,  0
        t14 = x+sx-fxr,  y+sy-fyu,  0
        t15 = x+sx-fxr,  y+fyd,     0
        t16 = x+fxl,     y+fyd,     0

        at1  = x,         y+asy,      0
        at2  = x+fxl,     y+asy,      0
        at3  = x+asx-fxr,  y+asy,      0
        at4  = x+asx,      y+asy,      0
        at5  = x+asx,      y+asy-fyu,  0
        at6  = x+asx,      y+fyd,     0
        at7  = x+asx,      y,         0
        at8  = x+asx-fxr,  y,         0
        at9  = x+fxl,     y,         0
        at10 = x,         y,         0
        at11 = x,         y+fyd,     0
        at12 = x,         y+asy-fyu,  0
        at13 = x+fxl,     y+asy-fyu,  0
        at14 = x+asx-fxr,  y+asy-fyu,  0
        at15 = x+asx-fxr,  y+fyd,     0
        at16 = x+fxl,     y+fyd,     0

        tqc1 = t10+t9 +t16+t11
        tqc2 = t12+t13+t2 +t1
        tqc3 = t14+t5 +t4 +t3
        tqc4 = t8 +t7 +t6 +t15

        # size of center x size adjusted
        #fxcr = (fx[1]/sum(fx))*bsx
        # rel x pos of v3 in P
        #xv3 = self.widget.size[0]-(self.widget.size[0]*((fx[2]/sum(fx))))
        xv3 = v3[0]-self.widget.pos[0]
        xv2 = v2[0]-self.widget.pos[0]
        #xv2 = self.widget.size[0]*(fx[0]/sum(fx))
        #txr = (fxcr/self.widget.size[0])*(self.widget.size[0]+((fx[2]/sum(fx))*bsx))
        #  P   Ratio of (center-x-size adjusted) to x-size applied to (x-size plus (right-x-size adjusted))
        #txr = fxl+(self.widget.size[0]*((fx[2]/sum(fx))))
        #  T   Left-x-size not adjusted plus x-size times (ratio of right-x-size to total)
        #txr = sx-fxr # Default
        #  T   x-size minus right-x-size not adjusted
        #txr = (max((xv3-xv2),fx[1])/self.widget.size[0])*sx
        print(fx[1]/sum(fx))
        txr = (fx[1]/sum(fx))*asx
        #  P  ratio of (xv3 rel to xv2) to x-size

        txr = x+txr

        xt2  = x+fxl,     y+asy,      0
        xt13 = x+fxl,     y+asy-fyu,  0
        xt16 = x+fxl,     y+fyd,     0
        xt9  = x+fxl,     y,         0

        xt8  = txr,  y,         0
        xt15 = txr,  y+fyd,     0
        xt14 = txr,  y+asy-fyu,  0
        xt3  = txr,  y+asy,      0

        yt6  = x+asx,      y+fyd,     0
        yt15 = x+asx-fxr,  y+fyd,     0
        yt11 = x,         y+fyd,     0
        yt16 = x+fxl,     y+fyd,     0

        yt12 = x,         y+asy-fyu,  0
        yt13 = x+fxl,     y+asy-fyu,  0
        yt5  = x+asx,      y+asy-fyu,  0
        yt14 = x+asx-fxr,  y+asy-fyu,  0

        tqe1 = xt9 +xt8 +xt15+xt16
        tqe2 = yt11+yt16+yt13+yt12
        tqe3 = xt13+xt14+xt3 +xt2
        tqe4 = yt15+yt6 +yt5 +yt14

        #tqe1 = at9 +at8 +at15+at16
        #tqe2 = at11+at16+at13+at12
        #tqe3 = at13+at14+at3 +at2
        #tqe4 = at15+at6 +at5 +at14

        #tqe1 = t9 +t8 +t15+t16
        #tqe2 = t11+t16+t13+t12
        #tqe3 = t13+t14+t3 +t2
        #tqe4 = t15+t6 +t5 +t14

        tqc = t16+t15+t14+t13

        t = tqc1+tqc2+tqc3+tqc4+tqe1+tqe2+tqe3+tqe4+tqc
        #print(t)
        #print(v)
        self.vlist_bg.vertices=v
        self.vlist_bg.tex_coords=t

class FramedImageButton(ImageButton):
    """
    Subclass of :py:class:`ImageButton` adding smart scaling to the background.

    By default, this Widget uses :py:class:`FramedImageBackground` as its Background class.

    Note that this feature is currently not working properly, and will thus output a warning on the console if tried to use.
    """
    def __init__(self,name,submenu,window,peng,
                 pos=None, size=[100,24],bg=None,
                 label="Button",
                 bg_idle=[GL_TEXTURE_2D,GL_TEXTURE1,[0]*12],
                 bg_hover=None,
                 bg_disabled=None,
                 bg_pressed=None,
                 frame_size=[[2,10,2],[2,10,2]],
                 ):
        if bg is None:
            bg = FramedImageBackground(self,bg_idle,bg_hover,bg_disabled,bg_pressed,frame_size)
        super(FramedImageButton,self).__init__(name,submenu,window,peng,pos,size,bg,label=label)

class ToggleButton(Button):
    """
    Variant of :py:class:`Button` that stays pressed until clicked again.

    This widgets adds the following actions:

    - ``press_down`` is called upon depressing the button
    - ``press_up`` is called upon releasing the button
    - ``click`` is changed to be called on every click on the button, e.g. like ``press_down`` and ``press_up`` combined
    """
    def on_mouse_press(self,x,y,button,modifiers):
        if not self.clickable:
            return
        elif mouse_aabb([x,y],self.size,self.pos):
            if button == pyglet.window.mouse.LEFT:
                self.doAction("click")
                self.pressed = not self.pressed
                if self.pressed:
                    self.doAction("press_down")
                else:
                    self.doAction("press_up")
            elif button == pyglet.window.mouse.RIGHT:
                self.doAction("context")
            self.redraw()
    def on_mouse_release(self,x,y,button,modifiers):
        pass

class CheckboxBackground(ButtonBackground):
    """
    Background for the :py:class:`Checkbox` Widget.

    This background looks like a button, but adds a square in the middle if it is pressed.

    The color of the square defaults to a tone of orange commonly found in GTK GUIs on Ubuntu.
    """
    vlist_layer = 1

    def __init__(self,widget,borderstyle,checkcolor=[240,119,70],**kwargs):
        self.checkcolor = checkcolor
        super(CheckboxBackground,self).__init__(widget,[3,3],borderstyle,**kwargs)
    def init_bg(self):
        super(CheckboxBackground,self).init_bg()
        self.vlist_check = self.submenu.batch2d.add(4,GL_QUADS,pyglet.graphics.OrderedGroup(10),
            "v2f",
            "c3B",
            )
        self.reg_vlist(self.vlist_check)
    def redraw_bg(self):
        # Convenience variables
        sx,sy = self.widget.size
        x,y = self.widget.pos
        bx,by = self.border

        # Button background

        # Outer vertices
        #    x          y
        v1 = x,         y+sy
        v2 = x+sx,      y+sy
        v3 = x,         y
        v4 = x+sx,      y

        # Inner vertices
        #    x          y
        v5 = x+bx,      y+sy-by
        v6 = x+sx-bx,   y+sy-by
        v7 = x+bx,      y+by
        v8 = x+sx-bx,   y+by

        # 5 Quads, for edges and the center
        qb1 = v5+v6+v2+v1
        qb2 = v8+v4+v2+v6
        qb3 = v3+v4+v8+v7
        qb4 = v3+v7+v5+v1
        qc  = v7+v8+v6+v5

        v = qb1+qb2+qb3+qb4+qc

        self.vlist.vertices = v

        bg = self.submenu.bg[:3] if isinstance(self.submenu.bg,list) or isinstance(self.submenu.bg,tuple) else [242,241,240]
        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)]
        # Outer,Inner,Shadow,Highlight

        if self.borderstyle == "flat":
            if self.widget.pressed:
                i = s
            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
        elif self.borderstyle == "gradient":
            if self.widget.pressed:
                i = s
            elif self.widget.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
        elif self.borderstyle == "oldshadow":
            if self.widget.pressed:
                i = s
                s,h = h,s
            elif self.widget.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
        elif self.borderstyle == "material":
            if self.widget.pressed:
                i = [max(bg[0]-20,0),max(bg[1]-20,0),max(bg[2]-20,0)]
            elif self.widget.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
        else:
            raise ValueError("Invalid Border style")

        c = cb1+cb2+cb3+cb4+cc

        self.vlist.colors = c

        # Cross

        # Old method that displayed a tick
        """if not self.widget.pressed:
            self.vlist_cross.colors = 6*bg
        else:
            if self.borderstyle=="flat":
                c = [min(bg[0]+8,255),min(bg[1]+8,255),min(bg[2]+8,255)]
            elif self.borderstyle=="gradient":
                c = h
            elif self.borderstyle=="oldshadow":
                c = h
            elif self.borderstyle=="material":
                c = s
            self.vlist_cross.colors = 6*c

        # Convenience variables
        sx,sy = self.widget.size
        x,y = self.widget.pos
        bx,by = self.border

        v1 = x+bx,      y+(sy-by*2)/2+by
        v2 = x+sx/2,    y+(sy-by*2)/4+by
        v3 = v6
        v4 = x+sx,      y+sy
        v5 = x+sx/2,    y+by
        v6 = x+bx,      y+(sy-by*2)/4+by

        self.vlist_cross.vertices = v2+v1+v6+v5+v4+v3"""

        # TODO: add better visual indicator

        v1 = x+bx*1.5,    y+sy-by*1.5
        v2 = x+sx-bx*1.5, y+sy-by*1.5
        v3 = x+bx*1.5,    y+by*1.5
        v4 = x+sx-bx*1.5, y+by*1.5

        self.vlist_check.colors = self.checkcolor*4 if self.widget.pressed else i*4
        self.vlist_check.vertices = v3+v4+v2+v1

class Checkbox(ToggleButton):
    """
    Variant of :py:class:`ToggleButton` using a different visual indicator.

    By default, this Widget uses :py:class:`CheckboxBackground` as its Background class.

    Note that the position and size given are for the indicator, the label will be bigger than the given size.

    The label given will be displayed to the right of the Checkbox.
    """
    def __init__(self,name,submenu,window,peng,
                 pos=None, size=[100,24],bg=None,
                 borderstyle="flat",
                 label="Checkbox",
                 checkcolor=[240,119,70]):
        if bg is None:
            bg = CheckboxBackground(self,borderstyle,checkcolor)
        super(Checkbox,self).__init__(name,submenu,window,peng,pos,size,bg,borderstyle=borderstyle,label=label)
    def redraw_label(self):
        """
        Re-calculates the position of the Label.
        """
        # Convenience variables
        sx,sy = self.size
        x,y = self.pos

        # Label position
        self._label.anchor_x = "left"
        self._label.x = x+sx/2.+sx
        self._label.y = y+sy/2.+sy*.15
        self._label._update() # Needed to prevent the label from drifting to the top-left after resizing by odd amounts


#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  keybind.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.
#
#

import pyglet
from pyglet.window import key

import bidict

MOD_RELEASE = 1 << 15 # Additional fake modifier applied if on_key_release is called
"""
Fake modifier applied when a key is released instead of pressed.

This modifier internally has the value of ``1<<15`` and should thus be safe from any added modifiers in the future.

Note that this modifier is only applied within keybinds, not in regular ``on_key_down`` and ``on_key_up`` handlers.
"""

MODNAME2MODIFIER = bidict.OrderedBidict([
    ("ctrl",key.MOD_ACCEL), # For compat between MacOSX and everything else, equals to MOD_COMMAND on OSX and MOD_CTRL everywhere else
    ("alt",key.MOD_ALT),
    ("shift",key.MOD_SHIFT),
    #("control",key.MOD_ACCEL),
    # Currently disabled due to bidict not allowing duplicate values
    ("option",key.MOD_OPTION), # MacOSX only
    ("capslock",key.MOD_CAPSLOCK),
    ("numlock",key.MOD_NUMLOCK),
    ("scrolllock",key.MOD_SCROLLLOCK),
    #("release",MOD_RELEASE), # Deprecated, use the released flag instead
    ])
"""
Ordered Bidict that maps between user-friendly names and internal constants.

Note that since this is a bidict, you can query the reverse mapping by accessing :py:attr:`MODNAME2MODIFIER.inv`\ .
The non-inverse mapping maps from user-friendly name to internal constant.

This mapping is used by the Keybind system to convert the modifier constants to names.

The Mapping is as follows:

================ =============================== =======
Name             Pyglet constant                 Notes
================ =============================== =======
ctrl             :py:data:`key.MOD_ACCEL`
alt              :py:data:`key.MOD_ALT`          1
shift            :py:data:`key.MOD_SHIFT`
option           :py:data:`key.MOD_OPTION`
capslock         :py:data:`key.MOD_CAPSLOCK`
numlock          :py:data:`key.MOD_NUMLOCK`
scrollock        :py:data:`key.MOD_SCROLLOCK`
================ =============================== =======

1: automatically replaced by MOD_CTRL on Darwin/OSX
"""
if pyglet.compat_platform=="darwin":
    MODNAME2MODIFIER["alt"]=key.MOD_CTRL

OPTIONAL_MODNAMES = ["capslock","numlock","scrollock"]
"""
List of modifiers that are not substantial to a key combo.

If the :confval:`controls.keybinds.strict` option is disabled, every key combo is emitted with and without the modifiers in this list.
Else, only the combo with these modifiers is emitted.

This may cause no more combos to get through if numlock or capslock are activated.
"""

class KeybindHandler(object):
    """
    Handler class that automatically converts incoming key events to key combo events.

    A keybinding always is of format ``[MOD1-[MOD2-]]KEY`` with potentially more modifiers.

    See :py:data:`MODNAME2MODIFIER` for more information about existing modifiers.

    Note that the order in which modifiers are listed also is the order of the above listing.

    Keybindings are matched exactly, and optionally a second time without the modifiers listed in :py:data:`OPTIONAL_MODNAMES` if :confval:`controls.keybinds.strict` is set to False.
    """
    def __init__(self,peng):
        self.peng = peng
        self.peng.registerEventHandler("on_key_press",self.on_key_press)
        self.peng.registerEventHandler("on_key_release",self.on_key_release)
        self.keybinds = {}
        self.keybinds_nm = {}
        self.kbname = bidict.bidict()
    def add(self,keybind,kbname,handler,mod=True):
        """
        Adds a keybind to the internal registry.

        Keybind names should be of the format ``namespace:category.subcategory.name``\ e.g. ``peng3d:actor.player.controls.forward`` for the forward key combo for the player actor.

        :param str keybind: Keybind string, as described above
        :param str kbname: Name of the keybind, may be used to later change the keybinding without re-registering
        :param function handler: Function or any other callable called with the positional arguments ``(symbol,modifiers,release)`` if the keybind is pressed or released
        :param int mod: If the keybind should respect modifiers
        """
        keybind = keybind.lower()
        if mod:
            if keybind not in self.keybinds:
                self.keybinds[keybind]=[]
            self.keybinds[keybind].append(kbname)
        else:
            if keybind not in self.keybinds_nm:
                self.keybinds_nm[keybind]=[]
            self.keybinds_nm[keybind].append(kbname)
        self.kbname[kbname]=handler
        self.peng.sendEvent("peng3d:keybind.add",{"peng":self.peng,"keybind":keybind,"kbname":kbname,"handler":handler,"mod":mod})
    def changeKeybind(self,kbname,combo):
        """
        Changes a keybind of a specific keybindname.

        :param str kbname: Same as kbname of :py:meth:`add()`
        :param str combo: New key combination
        """
        for key,value in self.keybinds.items():
            if kbname in value:
                del value[value.index(kbname)]
                break
        if combo not in self.keybinds:
            self.keybinds[combo]=[]
        self.keybinds[combo].append(kbname)
        self.peng.sendEvent("peng3d.keybind.change",{"peng":self.peng,"kbname":kbname,"combo":combo})
    def mod_is_held(self,modname,modifiers):
        """
        Helper method to simplify checking if a modifier is held.

        :param str modname: Name of the modifier, see :py:data:`MODNAME2MODIFIER`
        :param int modifiers: Bitmask to check in, same as the modifiers argument of the on_key_press etc. handlers
        """
        modifier = MODNAME2MODIFIER[modname.lower()]
        return modifiers&modifier
    def on_key_press(self,symbol,modifiers,release=False):
        # Format: "MOD1-MOD2-MOD3-KEY" in all lowercase
        modnames = []
        for modname in MODNAME2MODIFIER:
            if self.mod_is_held(modname,modifiers):
                modnames.append(modname)
        keyname = key.symbol_string(symbol)
        modnames.append(keyname)
        combo = "-".join(modnames).lower()
        self.handle_combo(combo,symbol,modifiers,release,True)
        self.handle_combo(keyname.lower(),symbol,modifiers,release,False)
        if not self.peng.cfg["controls.keybinds.strict"]:
            newmodnames = []
            for modname in modnames:
                if modname not in OPTIONAL_MODNAMES:
                    newmodnames.append(modname)
            if newmodnames==modnames:
                return
            combo = "-".join(newmodnames).lower()
            self.handle_combo(combo,symbol,modifiers,release,True)
    def handle_combo(self,combo,symbol,modifiers,release=False,mod=True):
        """
        Handles a key combination and dispatches associated events.

        First, all keybind handlers registered via :py:meth:`add` will be handled,
        then the pyglet event :peng3d:pgevent:`on_key_combo` with params ``(combo,symbol,modifiers,release,mod)`` is sent to the :py:class:`Peng()` instance.

        Also sends the events :peng3d:event:`peng3d:keybind.combo`\, :peng3d:event:`peng3d:keybind.combo.press` and :peng3d:event`peng3d:keybind.combo.release`\ .

        :params str combo: Key combination pressed
        :params int symbol: Key pressed, passed from the same argument within pyglet
        :params int modifiers: Modifiers held while the key was pressed
        :params bool release: If the combo was released
        :params bool mod: If the combo was sent without mods
        """
        if self.peng.cfg["controls.keybinds.debug"]:
            print("combo: nm=%s %s"%(mod,combo))
        if mod:
            for kbname in self.keybinds.get(combo,[]):
                self.kbname[kbname](symbol,modifiers,release)
        else:
            for kbname in self.keybinds_nm.get(combo,[]):
                self.kbname[kbname](symbol,modifiers,release)
        self.peng.sendPygletEvent("on_key_combo",(combo,symbol,modifiers,release,mod))
        self.peng.sendEvent("peng3d:keybind.combo",{"peng":self.peng,"combo":combo,"symbol":symbol,"modifiers":modifiers,"release":release,"mod":mod})
        if release:
            self.peng.sendEvent("peng3d:keybind.combo.release",{"peng":self.peng,"combo":combo,"symbol":symbol,"modifiers":modifiers,"release":release,"mod":mod})
        else:
            self.peng.sendEvent("peng3d:keybind.combo.press",{"peng":self.peng,"combo":combo,"symbol":symbol,"modifiers":modifiers,"release":release,"mod":mod})
    def on_key_release(self,symbol,modifiers):
        #self.on_key_press(symbol,modifiers|MOD_RELEASE)
        self.on_key_press(symbol,modifiers,release=True)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  player.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__ = ["BasicPlayer","FirstPersonPlayer","FourDirectionalMoveController","EgoMouseRotationalController","BasicFlightController"]

import math

try:
    import pyglet
except ImportError:
    pass

from . import Actor,RotatableActor,Controller

class FourDirectionalMoveController(Controller):
    """
    Controller allowing the user to control the actor with the keyboard.

    You can configure the used keybinds with the :confval:`controls.controls.forward` etc.
    The keybinds can also be changed with their ``keybindname``\ , e.g. ``peng3d:actor.<actor uuid>.player.controls.forward`` for forward.

    The movement speed may also be changed via the :py:attr:`movespeed` instance attribute, which defaults to :confval:`controls.controls.movespeed`\ .

    You may also access the currently held keys via :py:attr:`move`\ , which is a list with 2 items, forwards/backwards and left/right.
    """
    def __init__(self,*args,**kwargs):
        super(FourDirectionalMoveController,self).__init__(*args,**kwargs)
        self.move = [0,0]
        self.movespeed = self.peng.cfg["controls.controls.movespeed"]
    def registerEventHandlers(self):
        """
        Registers needed keybinds and schedules the :py:meth:`update` Method.

        You can control what keybinds are used via the :confval:`controls.controls.forward` etc. Configuration Values.
        """
        # Forward
        self.peng.keybinds.add(self.peng.cfg["controls.controls.forward"],"peng3d:actor.%s.player.controls.forward"%self.actor.uuid,self.on_fwd_down,False)
        # Backward
        self.peng.keybinds.add(self.peng.cfg["controls.controls.backward"],"peng3d:actor.%s.player.controls.backward"%self.actor.uuid,self.on_bwd_down,False)
        # Strafe Left
        self.peng.keybinds.add(self.peng.cfg["controls.controls.strafeleft"],"peng3d:actor.%s.player.controls.strafeleft"%self.actor.uuid,self.on_left_down,False)
        # Strafe Right
        self.peng.keybinds.add(self.peng.cfg["controls.controls.straferight"],"peng3d:actor.%s.player.controls.straferight"%self.actor.uuid,self.on_right_down,False)
        pyglet.clock.schedule_interval(self.update,1.0/60)
    def update(self,dt):
        """
        Should be called regularly to move the actor.

        This method does nothing if the :py:attr:`enabled` property is set to false.

        Note that this method is called automatically and should not be manually called.
        """
        if not self.enabled:
            return
        speed = self.movespeed
        d = dt * speed # distance covered this tick.
        dx, dy, dz = self.get_motion_vector()
        # New position in space, before accounting for gravity.
        dx, dy, dz = dx * d, dy * d, dz * d
        #dy+=self.vert*VERT_SPEED
        x,y,z = self.actor._pos
        newpos = dx+x, dy+y, dz+z
        self.actor.pos = newpos
    def get_motion_vector(self):
        """
        Returns the movement vector according to held buttons and the rotation.

        :return: 3-Tuple of ``(dx,dy,dz)``
        :rtype: tuple
        """
        if any(self.move):
            x, y = self.actor._rot
            strafe = math.degrees(math.atan2(*self.move))
            y_angle = math.radians(y)
            x_angle = math.radians(x + strafe)
            dy = 0.0
            dx = math.cos(x_angle)
            dz = math.sin(x_angle)
        else:
            dy = 0.0
            dx = 0.0
            dz = 0.0
        return (dx, dy, dz)

    # Event Handlers
    def on_fwd_down(self,symbol,modifiers,release):
        self.move[0]-=1 if not release else -1
    def on_bwd_down(self,symbol,modifiers,release):
        self.move[0]+=1 if not release else -1

    def on_left_down(self,symbol,modifiers,release):
        self.move[1]-=1 if not release else -1
    def on_right_down(self,symbol,modifiers,release):
        self.move[1]+=1 if not release else -1

class EgoMouseRotationalController(Controller):
    """
    Controller allowing the user to rotate the actor with the mouse.
    """
    def __init__(self,*args,**kwargs):
        super(EgoMouseRotationalController,self).__init__(*args,**kwargs)
    def registerEventHandlers(self):
        """
        Registers the motion and drag handlers.

        Note that because of the way pyglet treats mouse dragging, there is also an handler registered to the on_mouse_drag event.
        """
        self.world.registerEventHandler("on_mouse_motion",self.on_mouse_motion)
        self.world.registerEventHandler("on_mouse_drag",self.on_mouse_drag)
    def on_mouse_motion(self, x, y, dx, dy):
        if self.enabled:
            m = self.peng.cfg["controls.mouse.sensitivity"]
            x, y = self.actor._rot
            x, y = x + dx * m, y + dy * m
            y = max(-90, min(90, y))
            x %= 360
            newrot = (x,y)
            self.actor.rot = newrot
    def on_mouse_drag(self,x,y,dx,dy,buttons,modifiers):
        self.on_mouse_motion(x,y,dx,dy)

class BasicFlightController(Controller):
    """
    Controller allowing the user to move up and down with the jump and crouch controls.

    The used keybinds may be configured via :confval:`controls.controls.crouch` and :confval:`controls.controls.jump`\ .

    The vertical speed used when flying may be configured via :confval:`controls.controls.verticalspeed` or the :py:attr:`speed` attribute.
    """
    def __init__(self,*args,**kwargs):
        super(BasicFlightController,self).__init__(*args,**kwargs)
        self.speed = self.peng.cfg["controls.controls.verticalspeed"]
        self.move = 0
    def registerEventHandlers(self):
        """
        Registers the up and down handlers.

        Also registers a scheduled function every 60th of a second, causing pyglet to redraw your window with 60fps.
        """
        # Crouch/fly down
        self.peng.keybinds.add(self.peng.cfg["controls.controls.crouch"],"peng3d:actor.%s.player.controls.crouch"%self.actor.uuid,self.on_crouch_down,False)
        # Jump/fly up
        self.peng.keybinds.add(self.peng.cfg["controls.controls.jump"],"peng3d:actor.%s.player.controls.jump"%self.actor.uuid,self.on_jump_down,False)
        pyglet.clock.schedule_interval(self.update,1.0/60)
    def update(self,dt):
        """
        Should be called regularly to move the actor.

        This method does nothing if the :py:attr:`enabled` property is set to False.

        This method is called automatically and should not be called manually.
        """
        if not self.enabled:
            return
        dy = self.speed * dt * self.move
        x,y,z = self.actor._pos
        newpos = x,dy+y,z
        self.actor.pos = newpos

    def on_crouch_down(self,symbol,modifiers,release):
        self.move -= 1 if not release else -1
    def on_jump_down(self,symbol,modifiers,release):
        self.move += 1 if not release else -1

class BasicPlayer(RotatableActor):
    """
    Basic Player class, subclass of :py:class:`RotatableActor()`\ .

    This class adds no features currently, it can be used to identify player actors via :py:func:`isinstance()`\ .
    """
    pass

class FirstPersonPlayer(BasicPlayer):
    """
    Old class allowing to create standard first-person players easily.

    :deprecated: See :py:class:`EgoMouseRotationalController()` and :py:class:`FourDirectionalMoveController()` instead
    """
    def __init__(self,peng,world,uuid=None,pos=[0,0,0],rot=[0,0]):
        super(FirstPersonPlayer,self).__init__(peng,world,uuid,pos,rot)
        self.move = [0,0]
        self.movespeed = self.peng.cfg["controls.controls.movespeed"]
        self.active = True

        # Event handler registration
        # Forward
        self.peng.keybinds.add(self.peng.cfg["controls.controls.forward"],"peng3d:actor.player.controls.forward",self.on_fwd_down)
        self.peng.keybinds.add("release-"+self.peng.cfg["controls.controls.forward"],"peng3d:actor.player.controls.forward.release",self.on_fwd_up)
        # Backward
        self.peng.keybinds.add(self.peng.cfg["controls.controls.backward"],"peng3d:actor.player.controls.backward",self.on_bwd_down)
        self.peng.keybinds.add("release-"+self.peng.cfg["controls.controls.backward"],"peng3d:actor.player.controls.backward.release",self.on_bwd_up)
        # Strafe Left
        self.peng.keybinds.add(self.peng.cfg["controls.controls.strafeleft"],"peng3d:actor.player.controls.strafeleft",self.on_left_down)
        self.peng.keybinds.add("release-"+self.peng.cfg["controls.controls.strafeleft"],"peng3d:actor.player.controls.strafeleft.release",self.on_left_up)
        # Strafe Right
        self.peng.keybinds.add(self.peng.cfg["controls.controls.straferight"],"peng3d:actor.player.controls.straferight",self.on_right_down)
        self.peng.keybinds.add("release-"+self.peng.cfg["controls.controls.straferight"],"peng3d:actor.player.controls.straferight.release",self.on_right_up)
        # Mouse
        self.world.registerEventHandler("on_mouse_motion",self.on_mouse_motion)
        self.world.registerEventHandler("on_mouse_drag",self.on_mouse_drag)
        pyglet.clock.schedule_interval(self.update,1.0/60)
    def update(self,dt):
        """
        Internal method used for moving the player.

        :param float dt: Time delta since the last call to this method
        """
        speed = self.movespeed
        d = dt * speed # distance covered this tick.
        dx, dy, dz = self.get_motion_vector()
        # New position in space, before accounting for gravity.
        dx, dy, dz = dx * d, dy * d, dz * d
        #dy+=self.vert*VERT_SPEED
        x,y,z = self._pos
        newpos = dx+x, dy+y, dz+z
        self.pos = newpos
    def get_motion_vector(self):
        """
        Returns the movement vector according to held buttons and the rotation.

        :return: 3-Tuple of ``(dx,dy,dz)``
        :rtype: tuple
        """
        if any(self.move):
            x, y = self._rot
            strafe = math.degrees(math.atan2(*self.move))
            y_angle = math.radians(y)
            x_angle = math.radians(x + strafe)
            dy = 0.0
            dx = math.cos(x_angle)
            dz = math.sin(x_angle)
        else:
            dy = 0.0
            dx = 0.0
            dz = 0.0
        return (dx, dy, dz)

    # Event Handlers
    def on_fwd_down(self,symbol,modifiers):
        self.move[0]-=1
    def on_fwd_up(self,symbol,modifiers):
        self.move[0]+=1

    def on_bwd_down(self,symbol,modifiers):
        self.move[0]+=1
    def on_bwd_up(self,symbol,modifiers):
        self.move[0]-=1

    def on_left_down(self,symbol,modifiers):
        self.move[1]-=1
    def on_left_up(self,symbol,modifiers):
        self.move[1]+=1

    def on_right_down(self,symbol,modifiers):
        self.move[1]+=1
    def on_right_up(self,symbol,modifiers):
        self.move[1]-=1

    def on_mouse_motion(self, x, y, dx, dy):
        if self.active:
            m = 0.15
            x, y = self._rot
            x, y = x + dx * m, y + dy * m
            y = max(-90, min(90, y))
            x %= 360
            newrot = (x,y)
            self.rot = newrot
    def on_mouse_drag(self,x,y,dx,dy,buttons,modifiers):
        self.on_mouse_motion(x,y,dx,dy)