# $Id: ScrollBar.py,v 1.46.2.2 2006/09/14 23:32:01 marcusva Exp $
#
# Copyright (c) 2004-2006, Marcus von Appen
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  * Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""A widget, which allows scrolling using buttons and a slider."""

from pygame import K_KP_PLUS, K_PLUS, K_RIGHT, K_DOWN, K_KP_MINUS, K_MINUS
from pygame import K_LEFT, K_UP, K_PAGEUP, K_PAGEDOWN, K_HOME, K_END, Rect
from Range import Range
from Constants import *
from StyleInformation import StyleInformation
import base

# Timer value for the button press delay.
_TIMER = 25

class ScrollBar (Range):
    """ScrollBar () -> ScrollBar

    An abstract widget class, which is suitable for scrolling.

    The ScrollBar widget works much the same like a Scale widget except
    that it supports buttons for adjusting the value and that its
    minimum value always is 0. It is suitable for widgets which need
    scrolling ability and a scrolling logic.

    Inheriting widgets have to implement the _get_value_from_coords()
    and _get_coords_from_value() methods, which calculate the value of
    the ScrollBar using a pair of coordinates and vice versa. Example
    implementations can be found in the HScrollBar and VScrollBar widget
    classes. They also need to implement the _get_button_coords()
    method, which has to return a tuple of the both button coordinates
    [(x, y, width, height)].
    
    Default action (invoked by activate()):
    Give the ScrollBar the input focus.
    
    Mnemonic action (invoked by activate_mnemonic()):
    None

    Signals:
    SIG_MOUSEDOWN - Invoked, when a mouse button is pressed on the
                    ScrollBar.
    SIG_MOUSEUP   - Invoked, when a mouse buttor is released on the
                    ScrollBar.
    SIG_MOUSEMOVE - Invoked, when the mouse moves over the ScrollBar.

    Attributes:
    button_dec - Indicates, if the decrease button is pressed.
    button_inc - Indicates, if the increase button is pressed.
    """
    def __init__ (self):
        Range.__init__ (self, 0, 1, 1)

        # Signals.
        self._signals[SIG_MOUSEDOWN] = []
        self._signals[SIG_MOUSEMOVE] = []
        self._signals[SIG_MOUSEUP] = []
        self._signals[SIG_KEYDOWN] = None # Dummy for keyboard activation.
        self._signals[SIG_TICK] = None # Dummy for automatic scrolling.
        
        # Internal state handlers for the events. Those need to be known by
        # the inheritors.
        self._buttondec = False
        self._buttoninc = False

        self._timer = _TIMER
        self._click = False

    def activate (self):
        """S.activate () -> None

        Activates the ScrollBar default action.

        Activates the ScrollBar default action. This usually means giving
        the ScrollBar the input focus.
        """
        if not self.sensitive:
            return
        self.focus = True
    
    def _get_button_coords (self, area):
        """S._get_button_coords (...) -> tuple

        Gets a tuple with the coordinates of the in- and decrease buttons.
        
        This method has to be implemented by inherited widgets.
        """
        raise NotImplementedError

    def _get_coords_from_value (self):
        """S._get_coords_from_value () -> float

        Calculates the slider coordinates for the ScrollBar.
        
        This method has to be implemented by inherited widgets.
        """
        raise NotImplementedError

    def _get_value_from_coords (self, area, coords):
        """S._get_value_from_coords (...) -> float

        Calculates the slider coordinates for the ScrollBar.
        
        This method has to be implemented by inherited widgets.
        """
        raise NotImplementedError

    def _get_slider_size (self):
        """S._get_slider_size (...) -> int

        Calculates the size of the slider knob.
        
        This method has to be implemented by inherited widgets.
        """
        raise NotImplementedError
    
    def _check_collision (self, pos, rect):
        """S._check_collirion (...) -> bool

        Checks the collision of the given position with the passed rect.
        """
        # Rect: (x, y, width, height), pos: (x, y).
        return (pos[0] >= rect[0]) and (pos[0] <= (rect[2] + rect[0])) and \
               (pos[1] >= rect[1]) and (pos[1] <= (rect[3] + rect[1]))
    
    def set_minimum (self, minimum):
        """S.set_minimum (...) -> Exception

        This method does not have any use.
        """
        pass

    def notify (self, event):
        """S.notify (...) -> None

        Notifies the ScrollBar about an event.
        """
        if not self.sensitive:
            return

        if event.signal in SIGNALS_MOUSE:
            eventarea = self.rect_to_client ()

            if event.signal == SIG_MOUSEDOWN and \
                   eventarea.collidepoint (event.data.pos):
                self.focus = True
                # Act only on left clicks or scrollwheel events.
                if event.data.button == 1:
                    self.state = STATE_ACTIVE
                self.run_signal_handlers (SIG_MOUSEDOWN, event.data)

                if event.data.button == 1:
                    buttons = self._get_button_coords (eventarea)
                    if self._check_collision (event.data.pos, buttons[0]):
                        self._buttondec = True
                        self._buttoninc = False
                        self._click = False
                        self.decrease ()
                    elif self._check_collision (event.data.pos, buttons[1]):
                        self._buttoninc = True
                        self._buttondec = False
                        self._click = False
                        self.increase ()
                    else:
                        self._click = True
                        self._buttondec = False
                        self._buttoninc = False
                        val = self._get_value_from_coords (eventarea,
                                                           event.data.pos)
                        if val != self.value:
                            self.value = val
                # Mouse wheel.
                elif event.data.button == 4:
                    self.decrease ()
                elif event.data.button == 5:
                    self.increase ()
                event.handled = True

            elif event.signal == SIG_MOUSEMOVE:
                dirty = False
                if eventarea.collidepoint (event.data.pos):
                    self.focus = True
                    if self.state == STATE_NORMAL:
                        self.state = STATE_ENTERED
                    self.run_signal_handlers (SIG_MOUSEMOVE, event.data)

                    buttons = self._get_button_coords (eventarea)
                    if not self._check_collision (event.data.pos, buttons[0]) \
                           and self._buttondec:
                        self._buttondec = False
                        dirty = True
                    if not self._check_collision (event.data.pos, buttons[1]) \
                           and self._buttoninc:
                        self._buttoninc = False
                        dirty = True
                    if self._click:
                        val = self._get_value_from_coords (eventarea,
                                                           event.data.pos)
                        if val != self.value:
                            self.value = val
                            dirty = False
                    self.dirty = dirty

                elif self.state == STATE_ENTERED:
                    self.state = STATE_NORMAL

            elif event.signal == SIG_MOUSEUP:
                if self._click or self._buttoninc or self._buttondec:
                    self._buttondec = False
                    self._buttoninc = False
                    self._click = False

                if eventarea.collidepoint (event.data.pos):
                    if event.data.button == 1:
                        if self.state == STATE_ACTIVE:
                            self.state = STATE_ENTERED
                    self.run_signal_handlers (SIG_MOUSEUP, event.data)
                else:
                    self.state = STATE_NORMAL
                # Reset timer
                self._timer = _TIMER

        # The user holds the mouse clicked over one button.
        elif (self._buttondec or self._buttoninc) and \
                 (event.signal == SIG_TICK):
            # Wait half a second before starting to in/decrease.
            if self._timer > 0:
                self._timer -= 1
            else:
                if self._buttondec:
                    self.decrease ()
                elif self._buttoninc:
                    self.increase ()

        # Keyboard activation.
        elif (event.signal == SIG_KEYDOWN) and self.focus:
            if event.data.key in (K_KP_PLUS, K_PLUS, K_RIGHT, K_DOWN):
                self.increase ()
            elif event.data.key in (K_KP_MINUS, K_MINUS, K_LEFT, K_UP):
                self.decrease ()
            elif event.data.key == K_PAGEUP:
                val = self.value - 10 * self.step
                if val > self.minimum:
                    self.value = val
                else:
                    self.value = self.minimum
            elif event.data.key == K_PAGEDOWN:
                val = self.value + 10 * self.step
                if val < self.maximum:
                    self.value = val
                else:
                    self.value = self.maximum
            elif event.data.key == K_END:
                self.value = self.maximum
            elif event.data.key == K_HOME:
                self.value = self.minimum

        Range.notify (self, event)

    button_dec = property (lambda self: self._buttondec,
                           doc = """Indicates, whether the decrease
                           button is pressed.""")
    button_inc = property (lambda self: self._buttoninc,
                           doc = """Indicates, whether the increase
                           button is pressed.""")
        
class HScrollBar (ScrollBar):
    """HScrollBar (width, scroll) -> HScrollBar

    A horizontal ScrollBar widget.

    A ScrollBar widget with a horizontal orientation. By default, its
    height is the sum of the button height (HSCROLLBAR_BUTTON_SIZE) and
    the border drawn around it (2 * SCROLLBAR_BORDER) and has the passed
    width. The scrolling area is the passed scroll value minus the width
    of the ScrollBar.

    Thus, if the area to scroll is 200 pixels wide and the ScrollBar is
    about 100 pixels long, the ScrollBar its value range will go from 0
    to 100 (maximum = scroll - width). If the ScrollBar is longer than
    the area to scroll (scroll < width), then the value range will be 0.

    Note: The minimum size of the scrollbar is at least twice its
    size[1] parameter. This means, that it can display the both
    scrolling buttons next to each other. This will override the passed
    width value in the constructor, if necessary.
    """
    def __init__ (self, width, scroll):
        ScrollBar.__init__ (self)
        # Minimum size for the two scrolling buttons next to each other
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("SCROLLBAR_BORDER")) * 2
        height = StyleInformation.get ("HSCROLLBAR_BUTTON_SIZE")[1] + border
        if width < 2 * height:
            width = 2 * height

        self.lock ()
        self.minsize = (width, height) # Default size.
        self.maximum = scroll
        self.unlock ()

    def set_maximum (self, maximum):
        """H.set_maximum (...) -> None

        Sets the maximum value to scroll.

        The passed maximum value differs from maximum value of the
        slider. The HScrollBar also subtracts its own height from the
        scrolling maximum, so that the real maximum of its value range
        can be expressed in the formula:

        real_maximum = maximum - self.minsize[1]

        That means, that if the HScrollBar is 100 pixels high and the
        passed maximum value is 200, the scrolling range of the
        HScrollBar will go from 0 to 100 (100 + size = 200).

        Raises a ValueError, if the passed argument is smaller than
        the first element of the ScrollBar its size.
        """
        if maximum < self.minsize[0]:
            raise ValueError ("maximum must be greater than or equal to %d"
                              % self.minsize[0])
        ScrollBar.set_maximum (self, maximum - self.minsize[0])
        self.dirty = True

    def _get_button_coords (self, area):
        """H._get_button_coords (...) -> tuple

        Gets a tuple with the coordinates of the in- and decrease buttons.
        """
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("SCROLLBAR_BORDER"))
        # Respect the set shadow for the ScrollBar.
        button1 = (area.left + border, area.top + border,
                   area.height - 2 * border, area.height - 2 * border)
        button2 = (area.left + area.width - area.height - border,
                   area.top + border, area.height - 2 * border,
                   area.height - 2 * border)
        return (button1, button2)

    def _get_slider_size (self):
        """H._get_slider_size () -> int

        Calculates the size of the slider knob.
        """
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("SCROLLBAR_BORDER"))
        
        # Minimum slider size, if the scrollbar is big enough.
        minsize = 10
        fullsize = self.size[0] - 2 * self.size[1]
        if fullsize == 0:
            # If only the both scrolling buttons can be displayed, we will
            # completely skip the slider.
            return 0

        # Full size.
        fullsize += 2 * border
        slider_width = fullsize
        if self.maximum != 0:
            slider_width = fullsize / (float (self.maximum) + fullsize) * \
                           fullsize
            if slider_width < minsize:
                slider_width = minsize
        return int (slider_width)
    
    def _get_coords_from_value (self):
        """H._get_coords_from_value () -> int

        Calculates the slider coordinates for the HScrollBar.
        """
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("SCROLLBAR_BORDER"))

        val = 0
        if self.maximum > 0:
            slider = self._get_slider_size ()
            # Start offset for scrolling - this is the height
            # (button + 2 * border) - border plus the half of the
            # slider.
            sl_x = self.minsize[1] - border + float (slider) / 2

            # Valid sliding range.
            slide = self.minsize[0] - 2 * sl_x
            step = self.maximum / float (slide)
            val = self.value / step + sl_x
            return val
        return self.size[0] / 2
    
    def _get_value_from_coords (self, area, coords):
        """H._get_value_from_coords (...) -> float

        Calculates the slider coordinates for the HScrollBar.
        """
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("SCROLLBAR_BORDER"))

        val = 0
        if self.maximum > 0:
            slider = self._get_slider_size ()
            sl_x = self.minsize[1] - border + float (slider) / 2
            slide = self.minsize[0] - 2 * sl_x
            n = coords[0] - area.left - sl_x
            step = self.maximum / float (slide)
            val = n * step
            if val > self.maximum:
                val = self.maximum
            elif val < 0:
                val = 0
        return val
    
    def draw_bg (self):
        """H.draw_bg () -> Surface

        Draws the HScrollBar background surface and returns it.

        Creates the visible surface of the HScrollBar and returns it to
        the caller.
        """
        return base.GlobalStyle.engine.draw_scrollbar (self,
                                                       ORIENTATION_HORIZONTAL)

    def draw (self):
        """H.draw () -> None

        Draws the HScrollBar surface and places its Buttons and slider on it.
        """
        ScrollBar.draw (self)

        cls = self.__class__
        style = base.GlobalStyle
        st = self.style or style.get_style (cls)
        rect = self.image.get_rect ()

        # Create both buttons.
        border = style.get_border_size \
                 (cls, st, StyleInformation.get ("SCROLLBAR_BORDER"))
        button_type = StyleInformation.get ("SCROLLBAR_BUTTON_BORDER")

        width_button = rect.height - 2 * border

        # We use a temporary state here, so that just the buttons will
        # have the typical sunken effect.
        tmp_state = self.state
        if self.state == STATE_ACTIVE:
            tmp_state = STATE_NORMAL

        # First button.
        state_button = tmp_state
        if self.button_dec:
            state_button = STATE_ACTIVE
        button1 = style.engine.draw_rect (width_button, width_button,
                                          state_button, cls, st)
        style.engine.draw_border (button1, state_button, cls, st, button_type)
        rect_button1 = button1.get_rect ()

        # Draw the arrow.
        style.engine.draw_arrow (button1, ARROW_LEFT, state_button, cls, st)
        rect_button1.x = border
        rect_button1.centery = rect.centery
        self.image.blit (button1, rect_button1)

        # Second button
        state_button = tmp_state
        if self.button_inc:
            state_button = STATE_ACTIVE
        
        button2 = style.engine.draw_rect (width_button, width_button,
                                          state_button, cls, st)
        style.engine.draw_border (button2, state_button, cls, st, button_type)
        rect_button2 = button2.get_rect ()

        # Draw the arrow.
        style.engine.draw_arrow (button2, ARROW_RIGHT, state_button, cls, st)
        rect_button2.x = rect.width - width_button - border
        rect_button2.centery = rect.centery
        self.image.blit (button2, rect_button2)

        # Create the slider.
        slider_size = self._get_slider_size ()
        if slider_size > 0:
            sl = style.engine.draw_slider (slider_size, width_button,
                                           tmp_state, cls, st)
            r = sl.get_rect ()
            r.centerx = self._get_coords_from_value ()
            r.centery = rect.centery
            self.image.blit (sl, r)

class VScrollBar (ScrollBar):
    """VScrollBar (height, scroll) -> VScrollBar

    A vertical ScrollBar widget.

    A ScrollBar widget with a vertical orientation. By default, its
    width is the sum of the button width (VSCROLLBAR_BUTTON_SIZE) and
    the border drawn around it (2 * SCROLLBAR_BORDER) and has the passed
    height. The scrolling area is the passed scroll value minus the
    height of the ScrollBar.

    Thus, if the area to scroll is 200 pixels high and the ScrollBar is
    about 100 pixels high, the ScrollBar its value range will go from 0
    to 100 (maximum = scroll - height). If the ScrollBar is longer than
    the area to scroll (scroll < height), then the value range will be 0.

    Note: The minimum size of the scrollbar is at least twice its
    size[0] parameter. This means, that it can display the both
    scrolling buttons next to each other. This will override the passed
    width value in the constructor, if necessary.
    """
    def __init__ (self, height, scroll):
        ScrollBar.__init__ (self)
        # Minimum size for the two scrolling buttons next to each other.
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("SCROLLBAR_BORDER")) * 2
        
        width = StyleInformation.get ("VSCROLLBAR_BUTTON_SIZE")[0] + border
        if height < 2 * width:
            height = 2 * width

        self.lock ()
        self.minsize = (width, height) # Default size.
        self.maximum = scroll
        self.unlock ()

    def set_maximum (self, maximum):
        """V.set_maximum (...) -> None

        Sets the maximum value to scroll.

        The passed maximum value differs from maximum value of the
        slider. The VScrollBar also subtracts its own width from the
        scrolling maximum, so that the real maximum of its value range
        can be expressed in the formula:

        real_maximum = maximum - self.minsize[0]

        That means, that if the VScrollBar is 100 pixels long and the
        passed maximum value is 200, the scrolling range of the
        VScrollBar will go from 0 to 100 (100 + size = 200).

        Raises a ValueError, if the passed argument is smaller than
        the second element of the ScrollBar its size.
        """
        if maximum < self.minsize[1]:
            raise ValueError ("maximum must be greater than or equal to %d"
                              % self.minsize[1])
        ScrollBar.set_maximum (self, maximum - self.minsize[1])
        self.dirty = True

    def _get_button_coords (self, area):
        """V._get_button_coords (...) -> tuple

        Gets a tuple with the coordinates of the in- and decrease buttons.
        """
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("SCROLLBAR_BORDER"))

        # Respect the set shadow for the ScrollBar.
        button1 = (area.left + border, area.top + border,
                   area.width - 2 * border, area.width - 2 * border)
        button2 = (area.left + border,
                   area.top + area.height - area.width - border,
                   area.width - 2 * border, area.width - border)
        return (button1, button2)

    def _get_slider_size (self):
        """V._get_slider_size () -> int

        Calculates the size of the slider knob.
        """
        # Minimum slider size.
        minsize = 10
        if (self.size[1] - 2 * self.size[0]) == 0:
            # If only the both scrolling buttons can be displayed, we will
            # completely skip the slider.
            return 0
        
        # Full size.
        fullsize = self.size[1] - 2 * self.size[0]
        slider_height = fullsize
        if self.maximum != 0:
            slider_height = fullsize / (float (self.maximum) + fullsize) * \
                            fullsize
            if slider_height < minsize:
                slider_height = minsize
        return int (slider_height)
    
    def _get_coords_from_value (self):
        """V._get_coords_from_value () -> int

        Calculates the slider coordinates for the VScrollBar.
        """
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("SCROLLBAR_BORDER"))
        
        val = 0
        if self.maximum > 0:
            slider = self._get_slider_size ()
            sl_y = self.minsize[0] - border + float (slider) / 2
            slide = self.minsize[1] - 2 * sl_y
            step = self.maximum / float (slide)
            val = self.value / step + sl_y
            return val
        return self.size[1] / 2
    
    def _get_value_from_coords (self, area, coords):
        """V._get_value_from_coords (...) -> float

        Calculates the slider coordinates for the VScrollBar.
        """
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("SCROLLBAR_BORDER"))

        val = 0
        if self.maximum > 0:
            slider = self._get_slider_size ()

            # Start offset for scrolling - this is the width
            # (button + 2 * border) - border plus the half of the
            # slider.
            sl_y = self.minsize[0] - border + float (slider) / 2

            # Valid sliding range.
            slide = self.minsize[1] - 2 * sl_y
            
            n = coords[1] - area.top - sl_y
            step = self.maximum / float (slide)
            val = n * step
            if val > self.maximum:
                val = self.maximum
            elif val < 0:
                val = 0
        return val
    
    def draw_bg (self):
        """V.draw_bg (...) -> Surface

        Draws the VScrollBar background surface and returns it.

        Creates the visible surface of the VScrollBar and returns it to
        the caller.
        """
        return base.GlobalStyle.engine.draw_scrollbar (self,
                                                       ORIENTATION_VERTICAL)

    def draw (self):
        """V.draw () -> None

        Draws the VScrollBar surface and places its Buttons and slider on it.
        """
        ScrollBar.draw (self)
        cls = self.__class__
        style = base.GlobalStyle
        st = self.style or style.get_style (cls)
        rect = self.image.get_rect ()

        # Create both buttons.
        border = style.get_border_size \
                 (cls, st, StyleInformation.get ("SCROLLBAR_BORDER"))
        button_type = StyleInformation.get ("SCROLLBAR_BUTTON_BORDER")
        
        width_button = rect.width - 2 * border

        # We use a temporary state here, so that just the buttons will
        # have the typical sunken effect.
        tmp_state = self.state
        if self.state == STATE_ACTIVE:
            tmp_state = STATE_NORMAL

        # First button.
        state_button = tmp_state
        if self.button_dec:
            state_button = STATE_ACTIVE
        button1 = style.engine.draw_rect (width_button, width_button,
                                          state_button, cls, st)
        style.engine.draw_border (button1, state_button, cls, st, button_type)
        rect_button1 = button1.get_rect ()

        # Draw the arrow.
        style.engine.draw_arrow (button1, ARROW_UP, state_button, cls, st)
        rect_button1.y = border
        rect_button1.centerx = rect.centerx
        self.image.blit (button1, rect_button1)

        # Second button
        state_button = tmp_state
        if self.button_inc:
            state_button = STATE_ACTIVE
        
        button2 = style.engine.draw_rect (width_button, width_button,
                                          state_button, cls, st)
        style.engine.draw_border (button2, state_button, cls, st, button_type)
        rect_button2 = button2.get_rect ()

        # Draw the arrow.
        style.engine.draw_arrow (button2, ARROW_DOWN, state_button, cls, st)
        rect_button2.y = rect.height - width_button - border
        rect_button2.centerx = rect.centerx
        self.image.blit (button2, rect_button2)

        # Create the slider.
        slider_size = self._get_slider_size ()
        if slider_size > 0:
            sl = style.engine.draw_slider (width_button, slider_size,
                                           tmp_state, cls, st)
            r = sl.get_rect ()
            r.centerx = rect.centerx
            r.centery = self._get_coords_from_value ()
            self.image.blit (sl, r)
