예제 #1
0
파일: astro.py 프로젝트: 50833140/wcmj2020
    def run(self, rocket=None):
        """
        Execute the Planet (and Rocket) simulation.

        Optional parameters:
        
        * **rocket** Reference to a Rocket object - sets the initial view
        """
        if rocket:
            viewalt = rocket.altitude
            viewanom = rocket.tanomaly
        else:
            viewalt = 0
            viewanom = pi / 2
        self.viewaltitude = self.kwargs.get('viewalt',
                                            viewalt)  # how high to look
        self.viewanomaly = self.kwargs.get('viewanom',
                                           viewanom)  # where to look
        self.viewanomalyd = self.kwargs.get('viewanomd',
                                            degrees(self.viewanomaly))
        self._planetcircle = Circle((0, 0),
                                    self.radius,
                                    style=LineStyle(1, Color(self.color, 1)),
                                    color=Color(self.color, 0.5))
        r = self.radius + self.viewaltitude
        self.viewPosition = (r * cos(self.viewanomaly),
                             r * sin(self.viewanomaly))
        super().run()
예제 #2
0
파일: astro.py 프로젝트: tiggerntatie/ggame
 def __init__(self, **kwargs):
     scale = kwargs.get("viewscale", 10)  # 10 pixels per meter default
     self.radius = kwargs.get("radius", 6.371e6)  # Earth - meters
     self.mass = kwargs.get("planetmass", 5.9722e24)  # Earth - kg
     self.color = kwargs.get("color", 0x008040)  # greenish
     self.viewaltitude = kwargs.get("viewalt", 0)  # how high to look
     self.viewanomaly = kwargs.get("viewanom", pi / 2)  # where to look
     self.viewanomalyd = kwargs.get("viewanomd", degrees(self.viewanomaly))
     self._planetcircle = Circle(
         (0, 0),
         self.radius,
         style=LineStyle(1, Color(self.color, 1)),
         color=Color(self.color, 0.5),
     )
     super().__init__(scale)
예제 #3
0
파일: point.py 프로젝트: tiggerntatie/ggame
class Point(_Point):
    """
    Basic point object representing any point in a geometrical sense.
    An instantiated Point object is *callable* and will return a tuple
    with its logical position as an (x,y) pair.


    :param tuple(float,float) pos: Position in physical or logical units.

    :param \\**kwargs:
        See below

    :Optional Keyword Arguments:
        * **positioning** (*str*) One of 'logical' (default) or 'physical'
        * **size** (*int*) Radius of the point (in pixels)
        * **color** (*Color*) Valid :class:`~ggame.asset.Color` object
        * **style** (*LineStyle*) Valid :class:`~ggame.asset.LineStyle` object

    Example:

    .. literalinclude:: ../examples/pointpoint.py

    """

    _defaultsize = 5
    _defaultstyle = LineStyle(0, Color(0, 1))

    def __init__(self, *args, **kwargs):
        super().__init__(
            CircleAsset(self._defaultsize, self._defaultstyle,
                        self._defaultcolor), *args, **kwargs)

    def _buildAsset(self):
        return CircleAsset(self._stdinputs.size(), self._stdinputs.style(),
                           self._stdinputs.color())
예제 #4
0
파일: point.py 프로젝트: 50833140/wcmj2020
class Point(_Point):
    """
    Basic point object representing any point in a geometrical sense. 
    An instantiated Point object is *callable* and will return a tuple
    with its logical position as an (x,y) pair.
    
    
    :param tuple(float,float) pos: Position in physical or logical units.

    :param \**kwargs:
        See below

    :Optional Keyword Arguments:
        * **positioning** (*str*) One of 'logical' (default) or 'physical'
        * **size** (*int*) Radius of the point (in pixels)
        * **color** (*Color*) Valid :class:`~ggame.asset.Color` object
        * **style** (*LineStyle*) Valid :class:`~ggame.asset.LineStyle` object
            
    Example::
    
        from ggame.asset import Color
        from ggame.point import Point
        from ggame.mathapp import MathApp

        p1 = Point((0,1), color=Color(0xff8000, 1.0))
        p1.movable = True
        # An orange point that can be moved
        
        p2 = Point(lambda: (p1()[0], p1()[1]+1))
        # A point position based on P1
        p3 = Point((1,0))
        # A third, fixed point
        
        MathApp().run()
    
    """
    _defaultsize = 5
    _defaultstyle = LineStyle(0, Color(0, 1))


    def __init__(self, *args, **kwargs):
        super().__init__(CircleAsset(self._defaultsize, 
            self._defaultstyle, self._defaultcolor), *args, **kwargs)


    def _buildAsset(self):
        return CircleAsset(self._stdinputs.size(),
                            self._stdinputs.style(),
                            self._stdinputs.color())
예제 #5
0
파일: circle.py 프로젝트: 50833140/wcmj2020
class Circle(_MathVisual):
    """
    Create a circle on the screen. This is a subclass of 
    :class:`~ggame.sprite.Sprite` and 
    :class:`~ggame.mathapp._MathVisual` but most of the inherited members are of
    little use and are not shown in the documentation.

    :param \*args:
        See below
    :param \**kwargs:
        See below

    :Required Arguments:
        * **pos** (*tuple(float,float)*) Center point of the circle, which may
            be a literal tuple of floats, or a reference to any object or 
            function that returns or evaluates to a tuple of floats.
        * **radius** [float or Point] Radius of the circle (logical units)
            or a :class:`~ggame.point.Point` on the circle.
    
    
    :Optional Keyword Arguments:
        * **positioning** (*str*) One of 'logical' or 'physical'
        * **style** (*LineStyle*) Valid :class:`~ggame.asset.LineStyle` object
        * **color** (*Color*) Valid :class:`~ggame.color.Color` object`
        
    Example::
    
        from ggame.point import Point
        from ggame.circle import Circle
        from ggame.mathapp import MathApp
        
        p1 = Point((2,1))
        c = Circle(p1, 1.4)
    
        MathApp().run()
    """

    _posinputsdef = ['pos']
    _nonposinputsdef = ['radius']
    _defaultcolor = Color(0, 0)

    def __init__(self, *args, **kwargs):
        super().__init__(
            CircleAsset(0, self._defaultstyle, self._defaultcolor), *args,
            **kwargs)
        self._touchAsset()
        self.fxcenter = self.fycenter = 0.5

    def _buildAsset(self):
        pcenter = self._spposinputs.pos
        try:
            pradius = MathApp.distance(
                self._posinputs.pos(),
                self._nposinputs.radius()) * MathApp._scale
        except (AttributeError, TypeError):
            pradius = self._nposinputs.radius() * MathApp._scale
        style = self._stdinputs.style()
        fill = self._stdinputs.color()
        ymax = pcenter[1] + pradius
        ymin = pcenter[1] - pradius
        xmax = pcenter[0] + pradius
        xmin = pcenter[0] - pradius
        try:
            if ymin > MathApp.height or ymax < 0 or xmax < 0 or xmin > MathApp.width:
                return CircleAsset(pradius, style, fill)
            elif pradius > 2 * MathApp.width:
                # here begins unpleasant hack to overcome crappy circles
                poly = self._buildPolygon(pcenter, pradius)
                if len(poly):
                    passet = PolygonAsset(poly, style, fill)
                    return passet
        except AttributeError:
            return CircleAsset(pradius, style, fill)
        return CircleAsset(pradius, style, fill)

    def _buildPolygon(self, pcenter, pradius):
        """
        pcenter is in screen relative coordinates.
        returns a coordinate list in circle relative coordinates
        """
        xcepts = [
            self._findIntercepts(pcenter, pradius, 0, 0, 0, MathApp.height),
            self._findIntercepts(pcenter, pradius, 0, 0, MathApp.width, 0),
            self._findIntercepts(pcenter, pradius, MathApp.width, 0,
                                 MathApp.width, MathApp.height),
            self._findIntercepts(pcenter, pradius, 0, MathApp.height,
                                 MathApp.width, MathApp.height)
        ]
        ilist = []
        for x in xcepts:
            if x and len(x) < 2:
                ilist.extend(x)
        #ilist is a list of boundary intercepts that are screen-relative
        if len(ilist) > 1:
            xrange = ilist[-1][0] - ilist[0][0]
            yrange = ilist[-1][1] - ilist[0][1]
            numpoints = 20
            inx = 0
            for i in range(numpoints):
                icepts = self._findIntercepts(
                    pcenter, pradius, pcenter[0], pcenter[1],
                    ilist[0][0] + xrange * (i + 1) / (numpoints + 1),
                    ilist[0][1] + yrange * (i + 1) / (numpoints + 1))
                if len(icepts):
                    ilist.insert(inx + 1, icepts[0])
                    inx = inx + 1
            self._addBoundaryVertices(ilist, pcenter, pradius)
            ilist.append(ilist[0])
            ilist = [(i[0] - pcenter[0], i[1] - pcenter[1]) for i in ilist]
        return ilist

    def _addBoundaryVertices(self, plist, pcenter, pradius):
        """
        Sides 0=top, 1=right, 2=bottom, 3=left
        """
        #figure out rotation in point sequence
        cw = 0
        try:
            rtst = plist[0:3] + [plist[0]]
            for p in range(3):
                cw = cw + (rtst[p + 1][0] - rtst[p][0]) * (rtst[p + 1][1] +
                                                           rtst[p][1])
        except IndexError:
            #print(plist)
            return
        cw = self._sgn(cw)
        cw = 1 if cw < 0 else 0
        vertices = ((-100, -100), (MathApp.width + 100, -100),
                    (MathApp.width + 100,
                     MathApp.height + 100), (-100, MathApp.height + 100))
        nextvertex = [(vertices[0], vertices[1]), (vertices[1], vertices[2]),
                      (vertices[2], vertices[3]), (vertices[3], vertices[0])]
        nextsides = [(3, 1), (0, 2), (1, 3), (2, 0)]
        edges = ((None, 0), (MathApp.width, None), (None, MathApp.height),
                 (0, None))
        endside = startside = None
        for side in range(4):
            if endside is None and (edges[side][0] == round(plist[-1][0])
                                    or edges[side][1] == round(plist[-1][1])):
                endside = side
            if startside is None and (edges[side][0] == round(plist[0][0])
                                      or edges[side][1] == round(plist[0][1])):
                startside = side
        iterations = 0
        while startside != endside:
            iterations = iterations + 1
            if iterations > 20:
                break
            if endside != None and startside != None:  #  and endside != startside
                plist.append(nextvertex[endside][cw])
                endside = nextsides[endside][cw]

    def _sgn(self, x):
        return 1 if x >= 0 else -1

    def _findIntercepts(self, c, r, x1, y1, x2, y2):
        """
        c (center) and x and y values are physical, screen relative.
        function returns coordinates in screen relative format
        """
        x1n = x1 - c[0]
        x2n = x2 - c[0]
        y1n = y1 - c[1]
        y2n = y2 - c[1]
        dx = x2n - x1n
        dy = y2n - y1n
        dr = sqrt(dx * dx + dy * dy)
        D = x1n * y2n - x2n * y1n
        disc = r * r * dr * dr - D * D
        dr2 = dr * dr
        if disc <= 0:  # less than two solutions
            return []
        sdisc = sqrt(disc)
        x = [(D * dy + self._sgn(dy) * dx * sdisc) / dr2 + c[0],
             (D * dy - self._sgn(dy) * dx * sdisc) / dr2 + c[0]]
        y = [(-D * dx + abs(dy) * sdisc) / dr2 + c[1],
             (-D * dx - abs(dy) * sdisc) / dr2 + c[1]]
        getcoords = lambda x, y, c: [
            (x, y)
        ] if x >= 0 and x <= MathApp.width and y >= 0 and y <= MathApp.height else [
        ]
        res = getcoords(x[0], y[0], c)
        res.extend(getcoords(x[1], y[1], c))
        return res

    @property
    def center(self):
        return self._center()

    @center.setter
    def center(self, val):
        """
        An ordered pair (x,y) or :class:`~ggame.point.Point` that represents 
        the (logical) circle center. This attribute is set-able and get-able.
        """
        newval = self.Eval(val)
        if newval != self._center:
            self._center = newval
            self._touchAsset()

    @property
    def radius(self):
        return self._radius()

    @radius.setter
    def radius(self, val):
        newval = self.Eval(val)
        """
        A **float** that represents the radius of the circle. This attribugte
        is set-able and get-able.
        """
        if newval != self._radius:
            self._radius = newval
            self._touchAsset()

    def step(self):
        self._touchAsset()

    def physicalPointTouching(self, ppos):
        r = MathApp.distance(self._pcenter, ppos)
        inner = self._pradius - self.style.width / 2
        outer = self._pradius + self.style.width / 2
        return r <= outer and r >= inner

    def translate(self, pdisp):
        pass
예제 #6
0
 def _buildAsset(self):
     self._setThumb()
     return RectangleAsset(self._stdinputs.width(),
                           self._stdinputs.size(),
                           line=self._stdinputs.style(),
                           fill=Color(0, 0))
예제 #7
0
"""
Example of using the Label class.
"""

from ggame.asset import Color
from ggame.label import Label
from ggame.mathapp import MathApp

L = Label(
    (20, 80),  # physical location on screen
    "Initial Speed (m/s)",  # text to display
    size=15,  # text size (pixels)
    positioning="physical",  # use physical coordinates
    color=Color(0x202000, 1.0),
)  # text color

MathApp().run()
예제 #8
0
class _MathVisual(Sprite, _MathDynamic, metaclass=ABCMeta):
    """
    Abstract Base Class for all visual, potentially dynamic objects.
    
    :param Asset asset: A valid ggame asset object.
    
    :param list args: A list of required positional or non-positional arguments
        as named in the _posinputsdef and _nonposinputsdef lists overridden
        by child classes.
        
    :param \**kwargs:
        See below

    :Optional Keyword Arguments:
        * **positioning** (*string*) One of 'logical' or 'physical'
        * **size** (*int*) Size of the object (in pixels)
        * **width** (*int*) Width of the object (in pixels)
        * **color** (*Color*) Valid :class:`~ggame.asset.Color` object
        * **style** (*LineStyle*) Valid :class:`~ggame.asset.LineStyle` object
    """

    _posinputsdef = [
    ]  # a list of names (string) of required positional inputs
    _nonposinputsdef = [
    ]  # a list of names (string) of required non positional inputs
    _defaultsize = 15
    _defaultwidth = 200
    _defaultcolor = Color(0, 1)
    _defaultstyle = LineStyle(1, Color(0, 1))

    def __init__(self, asset, *args, **kwargs):
        MathApp._addVisual(self)
        #Sprite.__init__(self, asset, args[0])
        _MathDynamic.__init__(self)
        self._movable = False
        self._selectable = False
        self._strokable = False
        self.selected = False
        """
        True if object is currently selected by the UI. 
        """
        self.mouseisdown = False
        """
        True if object is tracking UI mouse button as down. 
        """
        self._positioning = kwargs.get('positioning', 'logical')
        # positional inputs
        self._PI = namedtuple('PI', self._posinputsdef)
        # nonpositional inputs
        self._NPI = namedtuple('NPI', self._nonposinputsdef)
        # standard inputs (not positional)
        standardargs = ['size', 'width', 'color', 'style']
        self._SI = namedtuple('SI', standardargs)
        # correct number of args?
        if len(args) != len(self._posinputsdef) + len(self._nonposinputsdef):
            raise TypeError("Incorrect number of parameters provided")
        self._args = args
        # generated named tuple of functions from positional inputs
        self._posinputs = self._PI(*[self.Eval(p)
                                     for p in args][:len(self._posinputsdef)])
        self._getPhysicalInputs()
        # first positional argument must be a sprite position!
        Sprite.__init__(self, asset, self._pposinputs[0])
        # generated named tuple of functions from nonpositional inputs
        if len(self._nonposinputsdef) > 0:
            self._nposinputs = self._NPI(
                *[self.Eval(p)
                  for p in args][(-1 * len(self._nonposinputsdef)):])
        else:
            self._nposinputs = []
        self._stdinputs = self._SI(
            self.Eval(kwargs.get('size', self._defaultsize)),
            self.Eval(kwargs.get('width', self._defaultwidth)),
            self.Eval(kwargs.get('color', self._defaultcolor)),
            self.Eval(kwargs.get('style', self._defaultstyle)))
        self._sposinputs = self._PI(*[0] * len(self._posinputs))
        self._spposinputs = self._PI(*self._pposinputs)
        self._snposinputs = self._NPI(*[0] * len(self._nposinputs))
        self._sstdinputs = self._SI(*[0] * len(self._stdinputs))

    def step(self):
        self._touchAsset()

    def _saveInputs(self, inputs):
        self._sposinputs, self._spposinputs, self._snposinputs, self._sstdinputs = inputs

    def _getInputs(self):
        self._getPhysicalInputs()
        return (self._PI(*[p() for p in self._posinputs]),
                self._PI(*self._pposinputs),
                self._NPI(*[p() for p in self._nposinputs]),
                self._SI(*[p() for p in self._stdinputs]))

    def _getPhysicalInputs(self):
        """
        Translate all positional inputs to physical
        """
        pplist = []
        if self._positioning == 'logical':
            for p in self._posinputs:
                pval = p()
                try:
                    pp = MathApp.logicalToPhysical(pval)
                except AttributeError:
                    pp = MathApp._scale * pval
                pplist.append(pp)
        else:
            # already physical
            pplist = [p() for p in self._posinputs]
        self._pposinputs = self._PI(*pplist)

    def _inputsChanged(self, saved):
        return self._spposinputs != saved[1] or self._snposinputs != saved[
            2] or self._sstdinputs != saved[3]

    def destroy(self):
        MathApp._removeVisual(self)
        MathApp._removeMovable(self)
        MathApp._removeStrokable(self)
        _MathDynamic.destroy(self)
        Sprite.destroy(self)

    def _updateAsset(self, asset):
        if type(asset) != ImageAsset:
            visible = self.GFX.visible
            if App._win != None:
                App._win.remove(self.GFX)
                self.GFX.destroy()
            self.asset = asset
            self.GFX = self.asset.GFX
            self.GFX.visible = visible
            if App._win != None:
                App._win.add(self.GFX)
        self.position = self._pposinputs.pos

    @property
    def positioning(self):
        """
        Whether object was created with 'logical' or 'physical' positioning. 
        """
        return self._positioning

    @positioning.setter
    def positioning(self, val):
        pass

    @property
    def movable(self):
        """
        Whether object can be moved. Set-able and get-able. 
        """
        return self._movable

    @movable.setter
    def movable(self, val):
        if not self._dynamic:
            self._movable = val
            if val:
                MathApp._addMovable(self)
            else:
                MathApp._removeMovable(self)

    @property
    def selectable(self):
        """
        Whether object can be selected by the UI. Set-able and get-able.
        """
        return self._selectable

    @selectable.setter
    def selectable(self, val):
        self._selectable = val
        if val:
            MathApp._addSelectable(self)
        else:
            MathApp._removeSelectable(self)

    @property
    def strokable(self):
        """
        Whether the object supports a click-drag input from the UI mouse. 
        Set-able and get-able. 
        """
        return self._strokable

    @strokable.setter
    def strokable(self, val):
        self._strokable = val
        if val:
            MathApp._addStrokable(self)
        else:
            MathApp._removeStrokable(self)

    def select(self):
        """
        Place the object in a 'selected' state. 
        
        :param: None
        :returns: None 
        """
        self.selected = True

    def unselect(self):
        """
        Place the object in an 'unselected' state. 
        
        :param: None
        :returns: None
        """
        self.selected = False

    def mousedown(self):
        """
        Inform the object of a 'mouse down' event. 
        
        :param: None 
        :returns: None 
        """
        self.mouseisdown = True

    def mouseup(self):
        """
        Inform the object of a 'mouse up' event. 
        
        :param: None 
        :returns: None 
        """
        self.mouseisdown = False

    def processEvent(self, event):
        """
        Inform the object of a generic ggame event. 
        
        :param event: The ggame event object to receive and process. 
        :returns: None 
        
        This method is intended to be overridden.
        """
        pass

    @abstractmethod
    def physicalPointTouching(self, ppos):
        """
        Determine if a physical point is considered to be touching this object.
        
        :param tuple(int,int) ppos: Physical screen coordinates.
        :rtype: boolean
        :returns: True if touching, False otherwise.
        
        This method **must** be overridden.
        """
        pass

    @abstractmethod
    def translate(self, pdisp):
        """ 
        Perform necessary processing in response to being moved by the mouse/UI.
        
        :param tuple(int,int) pdisp: Translation vector (x,y) in physical screen
            units.
        :returns: None
        
        This method **must** be overridden.
        """
        pass

    def stroke(self, ppos, pdisp):
        """
        Perform necessary processing in response to click-drag action by the
        mouse/UI.
        
        :param tuple(int,int) ppos: Physical coordinates of stroke start.
        :param tuple(int,int) pdisp: Translation vector of stroke action in
            physical screen units.
        :returns: None
        
        This method is intended to be overridden.
        """
        pass

    def canStroke(self, ppos):
        """
        Can the object respond to beginning a stroke action at the given
        position.
        
        :param tuple(int,int) ppos: Physical coordinates of stroke start.
        :rtype: Boolean
        :returns: True if the object can respond, False otherwise.
        
        This method is intended to be overridden.
        """
        return False

    def _touchAsset(self, force=False):
        inputs = self._getInputs()
        changed = self._inputsChanged(inputs)
        if changed:
            self._saveInputs(inputs)
        if changed or force:
            self._updateAsset(self._buildAsset())

    @abstractmethod
    def _buildAsset(self):
        pass
예제 #9
0
"""
Example of using MathApp Point class.
"""
from ggame.asset import Color
from ggame.point import Point
from ggame.mathapp import MathApp

P1 = Point((0, 1), color=Color(0xFF8000, 1.0))
P1.movable = True
# An orange point that can be moved

P2 = Point(lambda: (P1()[0], P1()[1] + 1))
# A point position based on P1
P3 = Point((1, 0))
# A third, fixed point

MathApp().run()
예제 #10
0
class _MathVisual(Sprite, _MathDynamic, metaclass=ABCMeta):

    posinputsdef = []  # a list of names (string) of required positional inputs
    nonposinputsdef = [
    ]  # a list of names (string) of required non positional inputs
    defaultsize = 15
    defaultwidth = 200
    defaultcolor = Color(0, 1)
    defaultstyle = LineStyle(1, Color(0, 1))

    def __init__(self, asset, *args, **kwargs):
        """
        Required inputs
        
        * **asset** a ggame asset
        * **args** the list of required positional and nonpositional arguments,
          as named in the posinputsdef and nonposinputsdef lists
        * **kwargs** all other optional keyword arguments:
          positioning - logical (default) or physical, size, width, color, style
          movable
        
        """

        MathApp._addVisual(self)
        #Sprite.__init__(self, asset, args[0])
        _MathDynamic.__init__(self)
        self._movable = False
        self._selectable = False
        self._strokable = False
        self.selected = False
        self.mouseisdown = False
        #
        self.positioning = kwargs.get('positioning', 'logical')
        # positional inputs
        self.PI = namedtuple('PI', self.posinputsdef)
        # nonpositional inputs
        self.NPI = namedtuple('NPI', self.nonposinputsdef)
        # standard inputs (not positional)
        standardargs = ['size', 'width', 'color', 'style']
        self.SI = namedtuple('SI', standardargs)
        # correct number of args?
        if len(args) != len(self.posinputsdef) + len(self.nonposinputsdef):
            raise TypeError("Incorrect number of parameters provided")
        self.args = args
        # generated named tuple of functions from positional inputs
        self.posinputs = self.PI(*[self.Eval(p)
                                   for p in args][:len(self.posinputsdef)])
        self._getPhysicalInputs()
        # first positional argument must be a sprite position!
        Sprite.__init__(self, asset, self.pposinputs[0])
        # generated named tuple of functions from nonpositional inputs
        if len(self.nonposinputsdef) > 0:
            self.nposinputs = self.NPI(
                *[self.Eval(p)
                  for p in args][(-1 * len(self.nonposinputsdef)):])
        else:
            self.nposinputs = []
        self.stdinputs = self.SI(
            self.Eval(kwargs.get('size', self.defaultsize)),
            self.Eval(kwargs.get('width', self.defaultwidth)),
            self.Eval(kwargs.get('color', self.defaultcolor)),
            self.Eval(kwargs.get('style', self.defaultstyle)))
        self.sposinputs = self.PI(*[0] * len(self.posinputs))
        self.spposinputs = self.PI(*self.pposinputs)
        self.snposinputs = self.NPI(*[0] * len(self.nposinputs))
        self.sstdinputs = self.SI(*[0] * len(self.stdinputs))

    def step(self):
        self._touchAsset()

    def _saveInputs(self, inputs):
        self.sposinputs, self.spposinputs, self.snposinputs, self.sstdinputs = inputs

    def _getInputs(self):
        self._getPhysicalInputs()
        return (self.PI(*[p()
                          for p in self.posinputs]), self.PI(*self.pposinputs),
                self.NPI(*[p() for p in self.nposinputs]),
                self.SI(*[p() for p in self.stdinputs]))

    def _getPhysicalInputs(self):
        """
        Translate all positional inputs to physical
        """
        pplist = []
        if self.positioning == 'logical':
            for p in self.posinputs:
                pval = p()
                try:
                    pp = MathApp.logicalToPhysical(pval)
                except AttributeError:
                    pp = MathApp._scale * pval
                pplist.append(pp)
        else:
            # already physical
            pplist = [p() for p in self.posinputs]
        self.pposinputs = self.PI(*pplist)

    def _inputsChanged(self, saved):
        return self.spposinputs != saved[1] or self.snposinputs != saved[
            2] or self.sstdinputs != saved[3]

    def destroy(self):
        MathApp._removeVisual(self)
        MathApp._removeMovable(self)
        MathApp._removeStrokable(self)
        _MathDynamic.destroy(self)
        Sprite.destroy(self)

    def _updateAsset(self, asset):
        if type(asset) != ImageAsset:
            visible = self.GFX.visible
            if App._win != None:
                App._win.remove(self.GFX)
                self.GFX.destroy()
            self.asset = asset
            self.GFX = self.asset.GFX
            self.GFX.visible = visible
            if App._win != None:
                App._win.add(self.GFX)
        self.position = self.pposinputs.pos

    @property
    def movable(self):
        return self._movable

    @movable.setter
    def movable(self, val):
        if not self._dynamic:
            self._movable = val
            if val:
                MathApp._addMovable(self)
            else:
                MathApp._removeMovable(self)

    @property
    def selectable(self):
        return self._selectable

    @selectable.setter
    def selectable(self, val):
        self._selectable = val
        if val:
            MathApp._addSelectable(self)
        else:
            MathApp._removeSelectable(self)

    @property
    def strokable(self):
        return self._strokable

    @strokable.setter
    def strokable(self, val):
        self._strokable = val
        if val:
            MathApp._addStrokable(self)
        else:
            MathApp._removeStrokable(self)

    def select(self):
        self.selected = True

    def unselect(self):
        self.selected = False

    def mousedown(self):
        self.mouseisdown = True

    def mouseup(self):
        self.mouseisdown = False

    def processEvent(self, event):
        pass

    # define how your class responds to mouse clicks - returns True/False
    @abstractmethod
    def physicalPointTouching(self, ppos):
        pass

    # define how your class responds to being moved (physical units)
    @abstractmethod
    def translate(self, pdisp):
        pass

    # define how your class responds to being stroked (physical units)
    def stroke(self, ppos, pdisp):
        pass

    # is the mousedown in a place that will result in a stroke?
    def canstroke(self, ppos):
        return False

    def _touchAsset(self, force=False):
        inputs = self._getInputs()
        changed = self._inputsChanged(inputs)
        if changed:
            self._saveInputs(inputs)
        if changed or force:
            self._updateAsset(self._buildAsset())

    @abstractmethod
    def _buildAsset(self):
        pass