Exemplo n.º 1
0
class LUIScrollableRegion(LUIObject):
    """ Scrollable region, reparent elements to the .content_node to make them
    scroll. """
    def __init__(self,
                 parent=None,
                 width=100,
                 height=100,
                 padding=10,
                 **kwargs):
        LUIObject.__init__(self)
        self.set_size(width, height)
        self._content_parent = LUIObject(self)
        self._content_parent.set_size("100%", "100%")
        self._content_parent.clip_bounds = (0, 0, 0, 0)

        self._content_clip = LUIObject(self._content_parent,
                                       x=padding,
                                       y=padding)
        self._content_clip.set_size("100%", "100%")

        self._content_scroller = LUIObject(self._content_clip)
        self._content_scroller.width = "100%"

        self._scrollbar = LUIObject(self, x=0, y=0, w=20)
        self._scrollbar.height = "100%"
        self._scrollbar.right = -10

        self._scrollbar_bg = LUISprite(self._scrollbar, "blank", "skin")
        self._scrollbar_bg.color = (1, 1, 1, 0.05)
        self._scrollbar_bg.set_size(3, "100%")
        self._scrollbar_bg.center_horizontal = True

        # Handle
        self._scrollbar_handle = LUIObject(self._scrollbar, x=5, y=0, w=10)
        self._scroll_handle_top = LUISprite(self._scrollbar_handle,
                                            "ScrollbarHandle_Top", "skin")
        self._scroll_handle_mid = LUISprite(self._scrollbar_handle,
                                            "ScrollbarHandle", "skin")
        self._scroll_handle_bottom = LUISprite(self._scrollbar_handle,
                                               "ScrollbarHandle_Bottom",
                                               "skin")

        self._scrollbar_handle.solid = True
        self._scrollbar.solid = True

        self._scrollbar_handle.bind("mousedown", self._start_scrolling)
        self._scrollbar_handle.bind("mouseup", self._stop_scrolling)
        self._scrollbar.bind("mousedown", self._on_bar_click)
        self._scrollbar.bind("mouseup", self._stop_scrolling)

        self._handle_dragging = False
        self._drag_start_y = 0

        self._scroll_top_position = 0
        self._content_height = 400

        scroll_shadow_width = self.width - 10

        # Scroll shadow
        self._scroll_shadow_top = LUIHorizontalStretchedLayout(
            parent=self, prefix="ScrollShadowTop", width="100%")
        self._scroll_shadow_bottom = LUIHorizontalStretchedLayout(
            parent=self, prefix="ScrollShadowBottom", width="100%")
        self._scroll_shadow_bottom.bottom = 0

        self._handle_height = 100

        if parent is not None:
            self.parent = parent

        LUIInitialState.init(self, kwargs)
        self.content_node = self._content_scroller
        taskMgr.doMethodLater(0.05, lambda task: self._update(),
                              "update_scrollbar")

    def _on_bar_click(self, event):
        """ Internal handler when the user clicks on the scroll bar """
        self._scroll_to_bar_pixels(event.coordinates.y -
                                   self._scrollbar.abs_pos.y -
                                   self._handle_height / 2.0)
        self._update()
        self._start_scrolling(event)

    def _start_scrolling(self, event):
        """ Internal method when we start scrolling """
        self.request_focus()
        if not self._handle_dragging:
            self._drag_start_y = event.coordinates.y
            self._handle_dragging = True

    def _stop_scrolling(self, event):
        """ Internal handler when we should stop scrolling """
        if self._handle_dragging:
            self._handle_dragging = False
            self.blur()

    def _scroll_to_bar_pixels(self, pixels):
        """ Internal method to convert from pixels to a relative position """
        offset = pixels * self._content_height / self.height
        self._scroll_top_position = offset
        self._scroll_top_position = max(
            0,
            min(self._content_height - self._content_clip.height,
                self._scroll_top_position))

    def on_tick(self, event):
        """ Internal on tick handler """
        if self._handle_dragging:
            scroll_abs_pos = self._scrollbar.abs_pos
            clamped_coord_y = max(
                scroll_abs_pos.y,
                min(scroll_abs_pos.y + self.height, event.coordinates.y))
            offset = clamped_coord_y - self._drag_start_y
            self._drag_start_y = clamped_coord_y
            self._scroll_to_bar_pixels(self._scroll_top_position /
                                       self._content_height * self.height +
                                       offset)
        self._update()

    def _set_handle_height(self, height):
        """ Internal method to set the scrollbar height """
        self._scroll_handle_mid.top = float(self._scroll_handle_top.height)

        self._scroll_handle_mid.height = max(
            0.0, height - self._scroll_handle_top.height -
            self._scroll_handle_bottom.height)
        self._scroll_handle_bottom.top = self._scroll_handle_mid.height + self._scroll_handle_mid.top
        self._handle_height = height

    def _update(self):
        """ Internal method to update the scroll bar """
        self._content_height = max(1, self._content_scroller.get_height() + 20)
        self._content_scroller.top = -self._scroll_top_position
        scrollbar_height = max(
            0.1, min(1.0, self._content_clip.height / self._content_height))
        scrollbar_height_px = scrollbar_height * self.height

        self._set_handle_height(scrollbar_height_px)
        self._scrollbar_handle.top = self._scroll_top_position / self._content_height * self.height

        top_alpha = max(0.0, min(1.0, self._scroll_top_position / 50.0))
        bottom_alpha = max(
            0.0,
            min(1.0, (self._content_height - self._scroll_top_position -
                      self._content_clip.height) / 50.0))
        self._scroll_shadow_top.color = (1, 1, 1, top_alpha)
        self._scroll_shadow_bottom.color = (1, 1, 1, bottom_alpha)

        if self._content_height <= self.height:
            self._scrollbar_handle.hide()
        else:
            self._scrollbar_handle.show()

    def on_element_added(self):
        taskMgr.doMethodLater(0.05, lambda task: self._update(),
                              "update_layout")

    def get_scroll_percentage(self):
        """ Returns the current scroll height in percentage from 0 to 1 """
        return self._scroll_top_position / max(
            1, self._content_height - self._content_clip.height)

    def set_scroll_percentage(self, percentage):
        """ Sets the scroll position in percentage, 0 means top and 1 means bottom """
        percentage = max(0.0, min(1.0, percentage))
        pixels = max(
            0.0, self._content_height - self._content_clip.height) * percentage
        self._scroll_top_position = pixels
        self._update()

    scroll_percentage = property(get_scroll_percentage, set_scroll_percentage)

    def scroll_to_bottom(self):
        """ Scrolls to the bottom of the frame """
        taskMgr.doMethodLater(0.07,
                              lambda task: self.set_scroll_percentage(1.0),
                              "scroll_to_bottom")

    def scroll_to_top(self):
        """ Scrolls to the top of the frame """
        taskMgr.doMethodLater(0.07,
                              lambda task: self.set_scroll_percentage(0.0),
                              "scroll_to_top")
Exemplo n.º 2
0
class LUISelectbox(LUIObject):
    """ Selectbox widget, showing several options whereas the user can select
    only one. """
    def __init__(self,
                 width=200,
                 options=None,
                 selected_option=None,
                 **kwargs):
        """ Constructs a new selectbox with a given width """
        LUIObject.__init__(self, x=0, y=0, w=width + 4, solid=True)
        LUIInitialState.init(self, kwargs)

        # The selectbox has a small border, to correct this we move it
        self.margin.left = -2

        self._bg_layout = LUIHorizontalStretchedLayout(parent=self,
                                                       prefix="Selectbox",
                                                       width="100%")

        self._label_container = LUIObject(self, x=10, y=0)
        self._label_container.set_size("100%", "100%")
        self._label_container.clip_bounds = (0, 0, 0, 0)
        self._label = LUILabel(parent=self._label_container,
                               text=u"Select an option ..")
        self._label.center_vertical = True

        self._drop_menu = LUISelectdrop(parent=self, width=width)
        self._drop_menu.top = self._bg_layout._sprite_right.height - 7
        self._drop_menu.topmost = True

        self._drop_open = False
        self._drop_menu.hide()

        self._options = []
        self._current_option_id = None

        if options is not None:
            self._options = options

        self._select_option(selected_option)

    def get_selected_option(self):
        """ Returns the selected option """
        return self._current_option_id

    def set_selected_option(self, option_id):
        """ Sets the selected option """
        raise NotImplementedError()

    selected_option = property(get_selected_option, set_selected_option)

    def _render_options(self):
        """ Internal method to render all available options """
        self._drop_menu._render_options(self._options)

    def get_options(self):
        """ Returns the list of options """
        return self._options

    def set_options(self, options):
        """ Sets the list of options, options should be a list containing entries
        whereas each entry is a tuple in the format (option_id, option_label).
        The option ID can be an arbitrary object, and will not get modified. """
        self._options = options
        self._current_option_id = None
        self._render_options()

    options = property(get_options, set_options)

    def _select_option(self, opt_id):
        """ Internal method to select an option """
        self._label.alpha = 1.0
        for elem_opt_id, opt_val in self._options:
            if opt_id == elem_opt_id:
                self._label.text = opt_val
                self._current_option_id = opt_id
                return
        self._label.alpha = 0.3

    # def on_mouseover(self, event):
    #     """ Internal handle when the select-knob was hovered """
    #     self._bg_layout.color = (0.9,0.9,0.9,1.0)

    # def on_mouseout(self, event):
    #     """ Internal handle when the select-knob was no longer hovered """
    #     self._bg_layout.color = (1,1,1,1.0)

    def on_click(self, event):
        """ On-Click handler """
        self.request_focus()
        if self._drop_open:
            self._close_drop()
        else:
            self._open_drop()

    def on_mousedown(self, event):
        """ Mousedown handler """
        self._bg_layout.alpha = 0.9

    def on_mouseup(self, event):
        """ Mouseup handler """
        self._bg_layout.alpha = 1

    def on_blur(self, event):
        """ Internal handler when the selectbox lost focus """
        if not self._drop_menu.focused:
            self._close_drop()

    def _open_drop(self):
        """ Internal method to show the dropdown menu """
        if not self._drop_open:
            self._render_options()
            self._drop_menu.show()
            self.request_focus()
            self._drop_open = True

    def _close_drop(self):
        """ Internal method to close the dropdown menu """
        if self._drop_open:
            self._drop_menu.hide()
            self._drop_open = False

    def _on_option_selected(self, opt_id):
        """ Internal method when an option got selected """
        self._select_option(opt_id)
        self._close_drop()