예제 #1
0
 def __init__(self,
              parent=None,
              group=None,
              value=None,
              active=False,
              label=u"Radiobox",
              **kwargs):
     """ Constructs a new radiobox. group should be a handle to a LUIRadioboxGroup.
     value will be the value returned by group.value, in case the box was
     selected. By default, the radiobox is not active. """
     assert group is not None, "LUIRadiobox needs a LUIRadioboxGroup!"
     LUIObject.__init__(self, x=0, y=0, solid=True)
     self._sprite = LUISprite(self, "Radiobox_Default", "skin")
     self._label = LUILabel(parent=self,
                            text=label,
                            margin=(0, 0, 0, 23),
                            center_vertical=True)
     self._value = value
     self._active = False
     self._hovered = False
     self._group = group
     self._group.register_box(self)
     if active:
         self.set_active()
     if parent:
         self.parent = parent
     LUIInitialState.init(self, kwargs)
예제 #2
0
 def __init__(self, template="ButtonDefault", **kwargs):
     LUIObject.__init__(self, x=0, y=0, solid=True)
     self._template = template
     self._button_sprite = LUISprite(self, template, "skin")
     self._button_sprite.width = width
     self._button_sprite.height = height
     LUIInitialState.init(self, kwargs)
예제 #3
0
    def __init__(self, parent=None, width=200, value=50, show_label=True):
        """ Constructs a new progress bar. If show_label is True, a label indicating
        the current progress is shown """
        LUIObject.__init__(self)
        self.set_width(width)

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

        self._fg_left = LUISprite(self, "ProgressbarFg_Left", "skin")
        self._fg_mid = LUISprite(self, "ProgressbarFg", "skin")
        self._fg_right = LUISprite(self, "ProgressbarFg_Right", "skin")
        self._fg_finish = LUISprite(self, "ProgressbarFg_Finish", "skin")

        self._show_label = show_label
        self._progress_pixel = 0
        self._fg_finish.right = 0

        if self._show_label:
            self._progress_label = LUILabel(parent=self, text=u"33 %")
            self._progress_label.centered = (True, True)

        self.set_value(value)
        self._update_progress()

        if parent is not None:
            self.parent = parent
예제 #4
0
class LUISpriteButton(LUIObject):
    """ Simple button that uses only two images: Default and focus. """
    def __init__(self, template="ButtonDefault", **kwargs):
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self._template = template
        self._button_sprite = LUISprite(self, template, "skin")
        if 'width' in kwargs:
            self._button_sprite.width = kwargs['width']
        if 'height' in kwargs:
            self._button_sprite.height = kwargs['height']
        LUIInitialState.init(self, kwargs)

    def on_mousedown(self, event):
        """ Internal on_mousedown handler. Do not override """
        self._button_sprite.set_texture(self.template + "Focus",
                                        "skin",
                                        resize=False)

    def on_mouseup(self, event):
        """ Internal on_mouseup handler. Do not override """
        self._button_sprite.set_texture(self.template, "skin", resize=False)

    def on_click(self, event):
        """ Internal onclick handler. Do not override """
        self.trigger_event("changed")
예제 #5
0
파일: Elements.py 프로젝트: chenhaox/LUI
    def __init__(self, parent=None, color=None):
        LUIObject.__init__(self, x=0, y=0, w=27, h=27)

        self.previewBg = LUISprite(self, "ColorpickerPreviewBg", "skin")

        self.filler = LUISprite(self, "blank", "skin")
        self.filler.width = 21
        self.filler.height = 21
        self.filler.pos = (5, 5)
        self.filler.color = (0.2, 0.6, 1.0, 1.0)

        self.overlay = LUISprite(self, "ColorpickerPreviewOverlay", "skin")
        self.overlay.pos = (2, 2)
        self.overlay.bind("click", self._open_dialog)

        self.fit_to_children()

        self.popup = LUIColorpickerPopup(self)
        self.popup.hide()

        if color is not None:
            self.colorValue = color
        else:
            # My favourite color
            self.colorValue = (0.2, 0.6, 1.0)
        self.set_color_value(self.colorValue)

        self.popup.add_change_callback(self._on_popup_color_changed)

        if parent is not None:
            self.parent = parent
예제 #6
0
 def __init__(self, template="ButtonDefault", **kwargs):
     LUIObject.__init__(self, x=0, y=0, solid=True)
     self._template = template
     self._button_sprite = LUISprite(self, template, "skin")
     if 'width' in kwargs:
         self._button_sprite.width = kwargs['width']
     if 'height' in kwargs:
         self._button_sprite.height = kwargs['height']
     LUIInitialState.init(self, kwargs)
예제 #7
0
    def __init__(self,
                 parent=None,
                 width=200,
                 placeholder=u"Enter some text ..",
                 value=u"",
                 **kwargs):
        """ Constructs a new input field. An input field always needs a width specified """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self.set_width(width)
        self._layout = LUIHorizontalStretchedLayout(parent=self,
                                                    prefix="InputField",
                                                    width="100%")

        # Container for the text
        self._text_content = LUIObject(self)
        self._text_content.margin = (5, 7, 5, 7)
        self._text_content.clip_bounds = (0, 0, 0, 0)
        self._text_content.set_size("100%", "100%")

        # Scroller for the text, so we can move right and left
        self._text_scroller = LUIObject(parent=self._text_content)
        self._text_scroller.center_vertical = True
        self._text = LUILabel(parent=self._text_scroller, text=u"")

        # Cursor for the current position
        self._cursor = LUISprite(self._text_scroller,
                                 "blank",
                                 "skin",
                                 x=0,
                                 y=0,
                                 w=2,
                                 h=15)
        self._cursor.color = (0.5, 0.5, 0.5)
        self._cursor.margin.top = 2
        self._cursor.z_offset = 20
        self._cursor_index = 0
        self._cursor.hide()
        self._value = value

        # Placeholder text, shown when out of focus and no value exists
        self._placeholder = LUILabel(parent=self._text_content,
                                     text=placeholder,
                                     shadow=False,
                                     center_vertical=True,
                                     alpha=0.2)

        # Various states
        self._tickrate = 1.0
        self._tickstart = 0.0

        self._render_text()

        if parent is not None:
            self.parent = parent

        LUIInitialState.init(self, kwargs)
예제 #8
0
 def __init__(self, parent=None, prefix="ButtonDefault", **kwargs):
     LUIObject.__init__(self)
     self._layout = LUIHorizontalLayout(self, spacing=0)
     self._layout.width = "100%"
     self._sprite_left = LUISprite(self._layout.cell(), "blank", "skin")
     self._sprite_mid = LUISprite(self._layout.cell('*'), "blank", "skin")
     self._sprite_right = LUISprite(self._layout.cell(), "blank", "skin")
     if parent is not None:
         self.parent = parent
     self.prefix = prefix
     LUIInitialState.init(self, kwargs)
예제 #9
0
 def __init__(self, checked=False, label=u"Checkbox", **kwargs):
     """ Constructs a new checkbox with the given label and state. By default,
     the checkbox is not checked. """
     LUIObject.__init__(self, x=0, y=0, solid=True)
     self._checked = checked
     self._checkbox_sprite = LUISprite(self, "Checkbox_Default", "skin")
     self._label = LUILabel(parent=self,
                            text=label,
                            margin=(0, 0, 0, 25),
                            center_vertical=True,
                            alpha=0.4)
     self._hovered = False
     LUIInitialState.init(self, kwargs)
예제 #10
0
 def __init__(self, template="ButtonDefault", **kwargs):
     LUIObject.__init__(self, x=0, y=0, solid=True)
     self._template = template
     self._button_sprite = LUISprite(self, template, "skin")
     self._button_sprite.width = width
     self._button_sprite.height = height
     LUIInitialState.init(self, kwargs)
예제 #11
0
파일: Elements.py 프로젝트: chenhaox/LUI
    def __init__(self, parent=None, key=u"A"):
        LUIObject.__init__(self)
        self.bgLeft = LUISprite(self, "Keymarker_Left", "skin")
        self.bgMid = LUISprite(self, "Keymarker", "skin")
        self.bgRight = LUISprite(self, "Keymarker_Right", "skin")

        self.label = LUILabel(parent=self, text=key, shadow=True)
        self.label.centered = (True, True)
        self.label.margin = (-3, 0, 0, -1)
        self.margin = (-1, 0, 0, -1)

        self.set_key(key)

        if parent is not None:
            self.parent = parent

        self.fit_to_children()
예제 #12
0
    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")
예제 #13
0
파일: LUICheckbox.py 프로젝트: jorjuato/LUI
 def __init__(self, checked=False, label=u"Checkbox", **kwargs):
     """ Constructs a new checkbox with the given label and state. By default,
     the checkbox is not checked. """
     LUIObject.__init__(self, x=0, y=0, solid=True)
     self._checked = checked
     self._checkbox_sprite = LUISprite(self, "Checkbox_Default", "skin")
     self._label = LUILabel(parent=self, text=label, margin=(0, 0, 0, 25),
                            center_vertical=True, alpha=0.4)
     self._hovered = False
     LUIInitialState.init(self, kwargs)
예제 #14
0
파일: LUILayouts.py 프로젝트: chenhaox/LUI
 def __init__(self, image_prefix="", **kwargs):
     """ Creates a new layout, using the image_prefix as prefix. """
     LUIObject.__init__(self)
     self.set_size("100%", "100%")
     self._prefix = image_prefix
     self._parts = {}
     for i in self._MODES:
         self._parts[i] = LUISprite(self, "blank", "skin")
     self._update_layout()
     LUIInitialState.init(self, kwargs)
예제 #15
0
파일: LUILayouts.py 프로젝트: chenhaox/LUI
class LUIHorizontalStretchedLayout(LUIObject):

    """ A layout which takes 3 sprites, a left sprite, a right sprite, and a
    middle sprite. While the left and right sprites remain untouched, the middle
    one will be stretched to fit the layout """

    def __init__(self, parent=None, prefix="ButtonDefault", **kwargs):
        LUIObject.__init__(self)
        self._layout = LUIHorizontalLayout(self, spacing=0)
        self._layout.width = "100%"
        self._sprite_left = LUISprite(self._layout.cell(), "blank", "skin")
        self._sprite_mid = LUISprite(self._layout.cell('*'), "blank", "skin")
        self._sprite_right = LUISprite(self._layout.cell(), "blank", "skin")
        if parent is not None:
            self.parent = parent
        self.prefix = prefix
        LUIInitialState.init(self, kwargs)

    def set_prefix(self, prefix):
        """ Sets the layout prefix, this controls which sprites will be used """
        self._sprite_left.set_texture(prefix + "_Left", "skin")
        self._sprite_mid.set_texture(prefix, "skin")
        self._sprite_right.set_texture(prefix + "_Right", "skin")
        self._sprite_mid.width = "100%"
        self._prefix = prefix

    def get_prefix(self):
        """ Returns the layout prefix """
        return self._prefix

    prefix = property(get_prefix, set_prefix)
예제 #16
0
    def __init__(self, parent=None, width=200, value=50, show_label=True):
        """ Constructs a new progress bar. If show_label is True, a label indicating
        the current progress is shown """
        LUIObject.__init__(self)
        self.set_width(width)

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

        self._fg_left = LUISprite(self, "ProgressbarFg_Left", "skin")
        self._fg_mid = LUISprite(self, "ProgressbarFg", "skin")
        self._fg_right = LUISprite(self, "ProgressbarFg_Right", "skin")
        self._fg_finish = LUISprite(self, "ProgressbarFg_Finish", "skin")

        self._show_label = show_label
        self._progress_pixel = 0
        self._fg_finish.right = 0

        if self._show_label:
            self._progress_label = LUILabel(parent=self, text=u"33 %")
            self._progress_label.centered = (True, True)

        self.set_value(value)
        self._update_progress()

        if parent is not None:
            self.parent = parent
예제 #17
0
class LUIHorizontalStretchedLayout(LUIObject):

    """ A layout which takes 3 sprites, a left sprite, a right sprite, and a
    middle sprite. While the left and right sprites remain untouched, the middle
    one will be stretched to fit the layout """

    def __init__(self, parent=None, prefix="ButtonDefault", **kwargs):
        LUIObject.__init__(self)
        self._layout = LUIHorizontalLayout(self, spacing=0)
        self._layout.width = "100%"
        self._sprite_left = LUISprite(self._layout.cell(), "blank", "skin")
        self._sprite_mid = LUISprite(self._layout.cell('*'), "blank", "skin")
        self._sprite_right = LUISprite(self._layout.cell(), "blank", "skin")
        if parent is not None:
            self.parent = parent
        self.prefix = prefix
        LUIInitialState.init(self, kwargs)

    def set_prefix(self, prefix):
        """ Sets the layout prefix, this controls which sprites will be used """
        self._sprite_left.set_texture(prefix + "_Left", "skin")
        self._sprite_mid.set_texture(prefix, "skin")
        self._sprite_right.set_texture(prefix + "_Right", "skin")
        self._sprite_mid.width = "100%"
        self._prefix = prefix

    def get_prefix(self):
        """ Returns the layout prefix """
        return self._prefix

    prefix = property(get_prefix, set_prefix)
예제 #18
0
    def __init__(self, parent=None, filled=True, min_value=0.0, max_value=1.0, width=100.0, value=None, **kwargs):
        """ Constructs a new slider. If filled is True, the part behind the knob
        will be solid """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self.set_width(width)
        self._knob = LUISprite(self, "SliderKnob", "skin")
        self._knob.z_offset = 2
        self._knob.solid = True

        # Construct the background
        self._slider_bg = LUIHorizontalStretchedLayout(parent=self, prefix="SliderBg", center_vertical=True, width="100%", margin=(-1, 0, 0, 0))

        self._filled = filled
        self._min_value = min_value
        self._max_value = max_value

        self._side_margin = self._knob.width / 4
        self._effective_width = self.width - 2 * self._side_margin

        if self._filled:
            self._slider_fill = LUIObject(self)
            self._fill_left = LUISprite(self._slider_fill, "SliderBgFill_Left", "skin")
            self._fill_mid = LUISprite(self._slider_fill, "SliderBgFill", "skin")
            self._fill_mid.left = self._fill_left.width
            self._slider_fill.z_offset = 1
            self._slider_fill.center_vertical = True

        if parent is not None:
            self.parent = parent

        # Handle various events
        self._knob.bind("mousedown", self._start_drag)
        self._knob.bind("mousemove", self._update_drag)
        self._knob.bind("mouseup", self._stop_drag)
        self._knob.bind("keydown", self._on_keydown)
        self._knob.bind("blur", self._stop_drag)
        self._knob.bind("keyrepeat", self._on_keydown)

        self._drag_start_pos = None
        self._dragging = False
        self._drag_start_val = 0
        self.current_val = 10

        # Set initial value
        if value is None:
            self.set_value( (self._min_value + self._max_value) / 2.0 )
        else:
            self.set_value(value)

        self._update_knob()

        LUIInitialState.init(self, kwargs)
예제 #19
0
class LUISpriteButton(LUIObject):

    """ Simple button that uses only two images: Default and focus. """

    def __init__(self, template="ButtonDefault", **kwargs):
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self._template = template
        self._button_sprite = LUISprite(self, template, "skin")
        self._button_sprite.width = width
        self._button_sprite.height = height
        LUIInitialState.init(self, kwargs)

    def on_mousedown(self, event):
        """ Internal on_mousedown handler. Do not override """
        self._button_sprite.set_texture(self.template + "Focus", "skin", resize=False)

    def on_mouseup(self, event):
        """ Internal on_mouseup handler. Do not override """
        self._button_sprite.set_texture(self.template, "skin", resize=False)

    def on_click(self, event):
        """ Internal onclick handler. Do not override """
        self.trigger_event("changed")
예제 #20
0
파일: LUILayouts.py 프로젝트: chenhaox/LUI
 def __init__(self, parent=None, prefix="ButtonDefault", **kwargs):
     LUIObject.__init__(self)
     self._layout = LUIHorizontalLayout(self, spacing=0)
     self._layout.width = "100%"
     self._sprite_left = LUISprite(self._layout.cell(), "blank", "skin")
     self._sprite_mid = LUISprite(self._layout.cell('*'), "blank", "skin")
     self._sprite_right = LUISprite(self._layout.cell(), "blank", "skin")
     if parent is not None:
         self.parent = parent
     self.prefix = prefix
     LUIInitialState.init(self, kwargs)
예제 #21
0
    def __init__(self, inner_padding=5, scrollable=False, style=FS_raised,
                 **kwargs):
        """ Creates a new frame with the given options and style. If scrollable
        is True, the contents of the frame will scroll if they don't fit into
        the frame height. inner_padding only has effect if scrollable is True.
        You can call fit_to_children() to make the frame fit automatically to
        it's contents."""
        LUIObject.__init__(self)

        # Each *style* has a different border size (size of the shadow). The
        # border size shouldn't get calculated to the actual framesize, so we
        # are determining it first and then substracting it.
        # TODO: We could do this automatically, determined by the sprite size
        # probably?
        self._border_size = 0
        self.padding = 10
        self.solid = True
        prefix = ""

        if style == LUIFrame.FS_raised:
            temp = LUISprite(self, "Frame_Left", "skin")
            self._border_size = temp.width
            self.remove_child(temp)
            prefix = "Frame_"
        elif style == LUIFrame.FS_sunken:
            self._border_size = 0
            prefix = "SunkenFrame_"
        else:
            raise Exception("Unkown LUIFrame style: " + style)

        self._scrollable = scrollable
        self._layout = LUICornerLayout(parent=self, image_prefix=prefix)
        self._layout.margin = -(self.padding.top + self._border_size)
        if self._scrollable:
            self._content = LUIObject(self)
            self._content.size = (self.width, self.height)
            self._content.pos = (self._border_size, self._border_size)
            self._scroll_content = LUIScrollableRegion(
                self._content,
                width=self.width - 2 * self.padding.left,
                height=self.height - 2 * self.padding.left,
                padding=inner_padding)
            self.content_node = self._scroll_content.content_node

        LUIInitialState.init(self, kwargs)
예제 #22
0
 def __init__(self, parent=None, group=None, value=None, active=False, label=u"Radiobox", **kwargs):
     """ Constructs a new radiobox. group should be a handle to a LUIRadioboxGroup.
     value will be the value returned by group.value, in case the box was
     selected. By default, the radiobox is not active. """
     assert group is not None, "LUIRadiobox needs a LUIRadioboxGroup!"
     LUIObject.__init__(self, x=0, y=0, solid=True)
     self._sprite = LUISprite(self, "Radiobox_Default", "skin")
     self._label = LUILabel(parent=self, text=label, margin=(0, 0, 0, 23),
         center_vertical=True)
     self._value = value
     self._active = False
     self._hovered = False
     self._group = group
     self._group.register_box(self)
     if active:
         self.set_active()
     if parent:
         self.parent = parent
     LUIInitialState.init(self, kwargs)
예제 #23
0
    def __init__(self, parent=None, width=200, placeholder=u"Enter some text ..", value=u"", **kwargs):
        """ Constructs a new input field. An input field always needs a width specified """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self.set_width(width)
        self._layout = LUIHorizontalStretchedLayout(parent=self, prefix="InputField", width="100%")

        # Container for the text
        self._text_content = LUIObject(self)
        self._text_content.margin = (5, 7, 5, 7)
        self._text_content.clip_bounds = (0,0,0,0)
        self._text_content.set_size("100%", "100%")

        # Scroller for the text, so we can move right and left
        self._text_scroller = LUIObject(parent=self._text_content)
        self._text_scroller.center_vertical = True
        self._text = LUILabel(parent=self._text_scroller, text=u"")

        # Cursor for the current position
        self._cursor = LUISprite(self._text_scroller, "blank", "skin", x=0, y=0, w=2, h=15)
        self._cursor.color = (0.5, 0.5, 0.5)
        self._cursor.margin.top = 2
        self._cursor.z_offset = 20
        self._cursor_index = 0
        self._cursor.hide()
        self._value = value

        # Placeholder text, shown when out of focus and no value exists
        self._placeholder = LUILabel(parent=self._text_content, text=placeholder, shadow=False,
            center_vertical=True, alpha=0.2)

        # Various states
        self._tickrate = 1.0
        self._tickstart = 0.0

        self._render_text()

        if parent is not None:
            self.parent = parent

        LUIInitialState.init(self, kwargs)
예제 #24
0
    def __init__(self, parent, width=200):
        LUIObject.__init__(self, x=0, y=0, w=width, h=1, solid=True)

        self._layout = LUICornerLayout(parent=self,
                                       image_prefix="Selectdrop_",
                                       width=width + 10,
                                       height=100)
        self._layout.margin.left = -3

        self._opener = LUISprite(self, "SelectboxOpen_Right", "skin")
        self._opener.right = -4
        self._opener.top = -25
        self._opener.z_offset = 3

        self._container = LUIObject(self._layout, 0, 0, 0, 0)
        self._container.width = self.width
        self._container.clip_bounds = (0, 0, 0, 0)
        self._container.left = 5
        self._container.solid = True
        self._container.bind("mousedown", lambda *args: self.request_focus())

        self._selectbox = parent
        self._option_focus = False
        self.parent = self._selectbox
예제 #25
0
    def _render_options(self, options):
        """ Internal method to update the options """
        num_visible_options = min(30, len(options))
        offset_top = 6
        self._layout.height = num_visible_options * 30 + offset_top + 11
        self._container.height = num_visible_options * 30 + offset_top + 1
        self._container.remove_all_children()

        current_y = offset_top
        for opt_id, opt_val in options:
            opt_container = LUIObject(self._container, x=0, y=current_y, w=self._container.width - 30, h=30)

            opt_bg = LUISprite(opt_container, "blank", "skin")
            opt_bg.width = self._container.width
            opt_bg.height = opt_container.height
            opt_bg.color = (0,0,0,0)
            opt_bg.bind("mouseover", self._on_opt_over)
            opt_bg.bind("mouseout", self._on_opt_out)
            opt_bg.bind("mousedown", lambda *args: self.request_focus())
            opt_bg.bind("click", partial(self._on_opt_click, opt_id))
            opt_bg.solid = True

            opt_label = LUILabel(parent=opt_container, text=unicode(opt_val))
            opt_label.top = 8
            opt_label.left = 8

            if opt_id == self._selectbox.selected_option:
                opt_label.color = (0.6, 0.9, 0.4, 1.0)

            divider = LUISprite(opt_container, "SelectdropDivider", "skin")
            divider.top = 30 - divider.height / 2
            divider.width = self._container.width

            current_y += 30
예제 #26
0
    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
        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")
예제 #27
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
        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")
예제 #28
0
class LUIRadiobox(LUIObject):

    """ A radiobox which can be used in combination with a LUIRadioboxGroup """

    def __init__(self, parent=None, group=None, value=None, active=False, label=u"Radiobox", **kwargs):
        """ Constructs a new radiobox. group should be a handle to a LUIRadioboxGroup.
        value will be the value returned by group.value, in case the box was
        selected. By default, the radiobox is not active. """
        assert group is not None, "LUIRadiobox needs a LUIRadioboxGroup!"
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self._sprite = LUISprite(self, "Radiobox_Default", "skin")
        self._label = LUILabel(parent=self, text=label, margin=(0, 0, 0, 23),
            center_vertical=True)
        self._value = value
        self._active = False
        self._hovered = False
        self._group = group
        self._group.register_box(self)
        if active:
            self.set_active()
        if parent:
            self.parent = parent
        LUIInitialState.init(self, kwargs)

    def on_click(self, event):
        """ Internal onclick handler. Do not override. """
        self.set_active()

    def on_mouseover(self, event):
        """ Internal mouseover handler """
        self._hovered = True
        self._update_sprite()

    def on_mouseout(self, event):
        """ Internal mouseout handler """
        self._hovered = False
        self._update_sprite()

    def set_active(self):
        """ Internal function to set the radiobox active """
        if self._group is not None:
            self._group.set_active_box(self)
        else:
            self._update_state(True)

    def get_value(self):
        """ Returns the value of the radiobox """
        return self._value

    def set_value(self, value):
        """ Sets the value of the radiobox """
        self._value = value

    value = property(get_value, set_value)

    def get_label(self):
        """ Returns a handle to the label, so it can be modified (e.g. change
            its text) """
        return self._label

    label = property(get_label)

    def _update_state(self, active):
        """ Internal method to update the state of the radiobox. Called by the
        LUIRadioboxGroup """
        self._active = active
        self.trigger_event("changed")
        self._update_sprite()

    def on_mousedown(self, event):
        """ Internal onmousedown handler. Do not override. """
        self._sprite.color = (0.86,0.86,0.86,1.0)

    def on_mouseup(self, event):
        """ Internal onmouseup handler. Do not override. """
        self._sprite.color = (1,1,1,1)

    def _update_sprite(self):
        """ Internal function to update the sprite of the radiobox """
        img = "Radiobox_Active" if self._active else "Radiobox_Default"
        if self._hovered:
            img += "Hover"
        self._sprite.set_texture(img, "skin")
예제 #29
0
class LUIInputField(LUIObject):
    """ Simple input field, accepting text input. This input field supports
    entering text and navigating. Selecting text is (currently) not supported.

    The input field also supports various keyboard shortcuts:

        [pos1]                  Move to the beginning of the text
        [end]                   Move to the end of the text
        [arrow_left]            Move one character to the left
        [arrow_right]           Move one character to the right
        [ctrl] + [arrow_left]   Move to the left, skipping over words
        [ctrl] + [arrow_right]  Move to the right, skipping over words
        [escape]                Un-focus input element

    """

    re_skip = re.compile("\W*\w+\W")

    def __init__(self,
                 parent=None,
                 width=200,
                 placeholder=u"Enter some text ..",
                 value=u"",
                 **kwargs):
        """ Constructs a new input field. An input field always needs a width specified """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self.set_width(width)
        self._layout = LUIHorizontalStretchedLayout(parent=self,
                                                    prefix="InputField",
                                                    width="100%")

        # Container for the text
        self._text_content = LUIObject(self)
        self._text_content.margin = (5, 7, 5, 7)
        self._text_content.clip_bounds = (0, 0, 0, 0)
        self._text_content.set_size("100%", "100%")

        # Scroller for the text, so we can move right and left
        self._text_scroller = LUIObject(parent=self._text_content)
        self._text_scroller.center_vertical = True
        self._text = LUILabel(parent=self._text_scroller, text=u"")

        # Cursor for the current position
        self._cursor = LUISprite(self._text_scroller,
                                 "blank",
                                 "skin",
                                 x=0,
                                 y=0,
                                 w=2,
                                 h=15)
        self._cursor.color = (0.5, 0.5, 0.5)
        self._cursor.margin.top = 2
        self._cursor.z_offset = 20
        self._cursor_index = 0
        self._cursor.hide()
        self._value = value

        # Placeholder text, shown when out of focus and no value exists
        self._placeholder = LUILabel(parent=self._text_content,
                                     text=placeholder,
                                     shadow=False,
                                     center_vertical=True,
                                     alpha=0.2)

        # Various states
        self._tickrate = 1.0
        self._tickstart = 0.0

        self._render_text()

        if parent is not None:
            self.parent = parent

        LUIInitialState.init(self, kwargs)

    @property
    def value(self):
        """ Returns the value of the input field """
        return self._value

    @value.setter
    def value(self, new_value):
        """ Sets the value of the input field """
        if sys.version_info[0] < 3:
            self._value = unicode(new_value)
        else:
            self._value = str(new_value)
        self._render_text()
        self.trigger_event("changed", self._value)

    def clear(self):
        """ Clears the input value """
        self.value = u""

    @property
    def cursor_pos(self):
        """ Set the cursor position """
        return self._cursor_index

    @cursor_pos.setter
    def cursor_pos(self, pos):
        """ Set the cursor position """
        if pos >= 0:
            self._cursor_index = max(0, min(len(self._value), pos))
        else:
            self._cursor_index = max(len(self._value) + pos + 1, 0)
        self._reset_cursor_tick()
        self._render_text()

    def on_tick(self, event):
        """ Tick handler, gets executed every frame """
        frame_time = globalClock.get_frame_time() - self._tickstart
        show_cursor = frame_time % self._tickrate < 0.5 * self._tickrate
        if show_cursor:
            self._cursor.color = (0.5, 0.5, 0.5, 1)
        else:
            self._cursor.color = (1, 1, 1, 0)

    def on_click(self, event):
        """ Internal on click handler """
        self.request_focus()

    def on_mousedown(self, event):
        """ Internal mousedown handler """
        local_x_offset = self._text.text_handle.get_relative_pos(
            event.coordinates).x
        self.cursor_pos = self._text.text_handle.get_char_index(local_x_offset)

    def _reset_cursor_tick(self):
        """ Internal method to reset the cursor tick """
        self._tickstart = globalClock.get_frame_time()

    def on_focus(self, event):
        """ Internal focus handler """
        self._cursor.show()
        self._placeholder.hide()
        self._reset_cursor_tick()
        self._layout.color = (0.9, 0.9, 0.9, 1)

    def on_keydown(self, event):
        """ Internal keydown handler. Processes the special keys, and if none are
        present, redirects the event """
        key_name = event.message
        if key_name == "backspace":
            self._value = self._value[:max(0, self._cursor_index -
                                           1)] + self._value[self.
                                                             _cursor_index:]
            self.cursor_pos -= 1
            self.trigger_event("changed", self._value)
        elif key_name == "delete":
            post_value = self._value[min(len(self._value), self._cursor_index +
                                         1):]
            self._value = self._value[:self._cursor_index] + post_value
            self.cursor_pos = self._cursor_index
            self.trigger_event("changed", self._value)
        elif key_name == "arrow_left":
            if event.get_modifier_state("alt") or event.get_modifier_state(
                    "ctrl"):
                self.cursor_skip_left()
            else:
                self.cursor_pos -= 1
        elif key_name == "arrow_right":
            if event.get_modifier_state("alt") or event.get_modifier_state(
                    "ctrl"):
                self.cursor_skip_right()
            else:
                self.cursor_pos += 1
        elif key_name == "escape":
            self.blur()
        elif key_name == "home":
            self.cursor_pos = 0
        elif key_name == "end":
            self.cursor_pos = len(self.value)

        self.trigger_event(key_name, self._value)

    def on_keyrepeat(self, event):
        """ Internal keyrepeat handler """
        self.on_keydown(event)

    def on_textinput(self, event):
        """ Internal textinput handler """
        self._value = self._value[:self._cursor_index] + event.message + \
            self._value[self._cursor_index:]
        self.cursor_pos = self._cursor_index + len(event.message)
        self.trigger_event("changed", self._value)

    def on_blur(self, event):
        """ Internal blur handler """
        self._cursor.hide()
        if len(self._value) < 1:
            self._placeholder.show()

        self._layout.color = (1, 1, 1, 1)

    def _render_text(self):
        """ Internal method to render the text """
        self._text.set_text(self._value)
        self._cursor.left = self._text.left + \
            self._text.text_handle.get_char_pos(self._cursor_index) + 1
        max_left = self.width - 15

        if self._value:
            self._placeholder.hide()
        else:
            if not self.focused:
                self._placeholder.show()

        # Scroll if the cursor is outside of the clip bounds
        rel_pos = self.get_relative_pos(self._cursor.get_abs_pos()).x
        if rel_pos >= max_left:
            self._text_scroller.left = min(0, max_left - self._cursor.left)
        if rel_pos <= 0:
            self._text_scroller.left = min(0, -self._cursor.left - rel_pos)

    def cursor_skip_left(self):
        """ Moves the cursor to the left, skipping the previous word """
        left_hand_str = ''.join(reversed(self.value[0:self.cursor_pos]))
        match = self.re_skip.match(left_hand_str)
        if match is not None:
            self.cursor_pos -= match.end() - 1
        else:
            self.cursor_pos = 0

    def cursor_skip_right(self):
        """ Moves the cursor to the right, skipping the next word """
        right_hand_str = self.value[self.cursor_pos:]
        match = self.re_skip.match(right_hand_str)
        if match is not None:
            self.cursor_pos += match.end() - 1
        else:
            self.cursor_pos = len(self.value)
예제 #30
0
파일: LUICheckbox.py 프로젝트: jorjuato/LUI
class LUICheckbox(LUIObject):

    """ This is a simple checkbox, including a Label. The checkbox can either
    be checked or unchecked. """

    def __init__(self, checked=False, label=u"Checkbox", **kwargs):
        """ Constructs a new checkbox with the given label and state. By default,
        the checkbox is not checked. """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self._checked = checked
        self._checkbox_sprite = LUISprite(self, "Checkbox_Default", "skin")
        self._label = LUILabel(parent=self, text=label, margin=(0, 0, 0, 25),
                               center_vertical=True, alpha=0.4)
        self._hovered = False
        LUIInitialState.init(self, kwargs)

    @property
    def checked(self):
        """ Returns True if the checkbox is currently checked """
        return self._checked

    @checked.setter
    def checked(self, checked):
        """ Sets the checkbox state """
        self._checked = checked
        self._update_sprite()

    def toggle(self):
        """ Toggles the checkbox state """
        self.checked = not self.checked

    @property
    def label(self):
        """ Returns a handle to the label, so it can be modified """
        return self._label

    @property
    def sprite(self):
        """ Returns a handle to the internal checkbox sprite """
        return self._checkbox_sprite

    def on_click(self, event):
        """ Internal onclick handler. Do not override """
        self._checked = not self._checked
        self.trigger_event("changed")
        self._update_sprite()

    def on_mousedown(self, event):
        """ Internal mousedown handler. """
        self._checkbox_sprite.color = (0.9, 0.9, 0.9, 1.0)

    def on_mouseup(self, event):
        """ Internal on_mouseup handler. """
        self._checkbox_sprite.color = (1, 1, 1, 1)

    def on_mouseover(self, event):
        """ Internal mouseover handler """
        self._hovered = True
        self._update_sprite()

    def on_mouseout(self, event):
        """ Internal mouseout handler """
        self._hovered = False
        self._update_sprite()

    def _update_sprite(self):
        """ Internal method to update the sprites """
        img = "Checkbox_Checked" if self._checked else "Checkbox_Default"
        if self._hovered:
            img += "Hover"
        self._checkbox_sprite.set_texture(img, "skin")
예제 #31
0
class LUICheckbox(LUIObject):
    """ This is a simple checkbox, including a Label. The checkbox can either
    be checked or unchecked. """
    def __init__(self, checked=False, label=u"Checkbox", **kwargs):
        """ Constructs a new checkbox with the given label and state. By default,
        the checkbox is not checked. """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self._checked = checked
        self._checkbox_sprite = LUISprite(self, "Checkbox_Default", "skin")
        self._label = LUILabel(parent=self,
                               text=label,
                               margin=(0, 0, 0, 25),
                               center_vertical=True,
                               alpha=0.4)
        self._hovered = False
        LUIInitialState.init(self, kwargs)

    @property
    def checked(self):
        """ Returns True if the checkbox is currently checked """
        return self._checked

    @checked.setter
    def checked(self, checked):
        """ Sets the checkbox state """
        self._checked = checked
        self._update_sprite()

    def toggle(self):
        """ Toggles the checkbox state """
        self.checked = not self.checked

    @property
    def label(self):
        """ Returns a handle to the label, so it can be modified """
        return self._label

    @property
    def sprite(self):
        """ Returns a handle to the internal checkbox sprite """
        return self._checkbox_sprite

    def on_click(self, event):
        """ Internal onclick handler. Do not override """
        self._checked = not self._checked
        self.trigger_event("changed")
        self._update_sprite()

    def on_mousedown(self, event):
        """ Internal mousedown handler. """
        self._checkbox_sprite.color = (0.9, 0.9, 0.9, 1.0)

    def on_mouseup(self, event):
        """ Internal on_mouseup handler. """
        self._checkbox_sprite.color = (1, 1, 1, 1)

    def on_mouseover(self, event):
        """ Internal mouseover handler """
        self._hovered = True
        self._update_sprite()

    def on_mouseout(self, event):
        """ Internal mouseout handler """
        self._hovered = False
        self._update_sprite()

    def _update_sprite(self):
        """ Internal method to update the sprites """
        img = "Checkbox_Checked" if self._checked else "Checkbox_Default"
        if self._hovered:
            img += "Hover"
        self._checkbox_sprite.set_texture(img, "skin")
예제 #32
0
class LUISlider(LUIObject):

    """ Slider which can be used to control values """

    def __init__(self, parent=None, filled=True, min_value=0.0, max_value=1.0, width=100.0, value=None, **kwargs):
        """ Constructs a new slider. If filled is True, the part behind the knob
        will be solid """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self.set_width(width)
        self._knob = LUISprite(self, "SliderKnob", "skin")
        self._knob.z_offset = 2
        self._knob.solid = True

        # Construct the background
        self._slider_bg = LUIHorizontalStretchedLayout(parent=self, prefix="SliderBg", center_vertical=True, width="100%", margin=(-1, 0, 0, 0))

        self._filled = filled
        self._min_value = min_value
        self._max_value = max_value

        self._side_margin = self._knob.width / 4
        self._effective_width = self.width - 2 * self._side_margin

        if self._filled:
            self._slider_fill = LUIObject(self)
            self._fill_left = LUISprite(self._slider_fill, "SliderBgFill_Left", "skin")
            self._fill_mid = LUISprite(self._slider_fill, "SliderBgFill", "skin")
            self._fill_mid.left = self._fill_left.width
            self._slider_fill.z_offset = 1
            self._slider_fill.center_vertical = True

        if parent is not None:
            self.parent = parent

        # Handle various events
        self._knob.bind("mousedown", self._start_drag)
        self._knob.bind("mousemove", self._update_drag)
        self._knob.bind("mouseup", self._stop_drag)
        self._knob.bind("keydown", self._on_keydown)
        self._knob.bind("blur", self._stop_drag)
        self._knob.bind("keyrepeat", self._on_keydown)

        self._drag_start_pos = None
        self._dragging = False
        self._drag_start_val = 0
        self.current_val = 10

        # Set initial value
        if value is None:
            self.set_value( (self._min_value + self._max_value) / 2.0 )
        else:
            self.set_value(value)

        self._update_knob()

        LUIInitialState.init(self, kwargs)

    def on_click(self, event):
        """ Internal on click handler """
        # I don't like this behaviour
        # relative_pos = self.get_relative_pos(event.coordinates)
        # if not self._dragging:
        #     self._set_current_val(relative_pos.x)

    def _update_knob(self):
        """ Internal method to update the slider knob """
        self._knob.left = self.current_val - (self._knob.width / 2) + self._side_margin
        if self._filled:
            self._fill_mid.width = self.current_val - self._fill_left.width + self._side_margin

    def _set_current_val(self, pixels):
        """ Internal method to set the current value in pixels """
        pixels = max(0, min(self._effective_width, pixels))
        self.current_val = pixels
        self.trigger_event("changed")
        self._update_knob()

    def _start_drag(self, event):
        """ Internal drag start handler """
        self._knob.request_focus()
        if not self._dragging:
            self._drag_start_pos = event.coordinates
            self._dragging = True
            self._drag_start_val = self.current_val
            self._knob.color = (0.8,0.8,0.8,1.0)

    def set_value(self, value):
        """ Sets the value of the slider, should be between minimum and maximum. """
        scaled = (float(value) - float(self._min_value)) \
                 / (float(self._max_value) - float(self._min_value)) \
                 * self._effective_width
        self._set_current_val(scaled)

    def get_value(self):
        """ Returns the current value of the slider """
        return (self.current_val / float(self._effective_width)) \
                 * (float(self._max_value) - float(self._min_value)) \
                + self._min_value

    value = property(get_value, set_value)

    def _on_keydown(self, event):
        """ Internal keydown handler """
        if event.message == "arrow_right":
            self._set_current_val(self.current_val + 2)
        elif event.message == "arrow_left":
            self._set_current_val(self.current_val - 2)
        elif event.message == "escape":
            self.current_val = self._drag_start_val
            self._stop_drag(event)
            self._update_knob()

    def _update_drag(self, event):
        """ Internal drag handler """
        if self._dragging:
            dragOffset = event.coordinates.x - self._drag_start_pos.x
            finalValue = self._drag_start_val + dragOffset
            self._set_current_val(finalValue)

    def _stop_drag(self, event):
        """ Internal drag stop handelr """
        self._drag_start_pos = None
        self._dragging = False
        self._drag_start_val = self.current_val
        self._knob.color = (1,1,1,1)
예제 #33
0
class LUIInputField(LUIObject):

    """ Simple input field, accepting text input. This input field supports
    entering text and navigating. Selecting text is (currently) not supported.

    The input field also supports various keyboard shortcuts:

        [pos1]                  Move to the beginning of the text
        [end]                   Move to the end of the text
        [arrow_left]            Move one character to the left
        [arrow_right]           Move one character to the right
        [ctrl] + [arrow_left]   Move to the left, skipping over words
        [ctrl] + [arrow_right]  Move to the right, skipping over words
        [escape]                Un-focus input element

    """

    re_skip = re.compile("\W*\w+\W")

    def __init__(self, parent=None, width=200, placeholder=u"Enter some text ..", value=u"", **kwargs):
        """ Constructs a new input field. An input field always needs a width specified """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self.set_width(width)
        self._layout = LUIHorizontalStretchedLayout(parent=self, prefix="InputField", width="100%")

        # Container for the text
        self._text_content = LUIObject(self)
        self._text_content.margin = (5, 7, 5, 7)
        self._text_content.clip_bounds = (0,0,0,0)
        self._text_content.set_size("100%", "100%")

        # Scroller for the text, so we can move right and left
        self._text_scroller = LUIObject(parent=self._text_content)
        self._text_scroller.center_vertical = True
        self._text = LUILabel(parent=self._text_scroller, text=u"")

        # Cursor for the current position
        self._cursor = LUISprite(self._text_scroller, "blank", "skin", x=0, y=0, w=2, h=15)
        self._cursor.color = (0.5, 0.5, 0.5)
        self._cursor.margin.top = 2
        self._cursor.z_offset = 20
        self._cursor_index = 0
        self._cursor.hide()
        self._value = value

        # Placeholder text, shown when out of focus and no value exists
        self._placeholder = LUILabel(parent=self._text_content, text=placeholder, shadow=False,
            center_vertical=True, alpha=0.2)

        # Various states
        self._tickrate = 1.0
        self._tickstart = 0.0

        self._render_text()

        if parent is not None:
            self.parent = parent

        LUIInitialState.init(self, kwargs)

    @property
    def value(self):
        """ Returns the value of the input field """
        return self._value

    @value.setter
    def value(self, new_value):
        """ Sets the value of the input field """
        self._value = unicode(new_value)
        self._render_text()
        self.trigger_event("changed", self._value)

    def clear(self):
        """ Clears the input value """
        self.value = u""

    @property
    def cursor_pos(self):
        """ Set the cursor position """
        return self._cursor_index

    @cursor_pos.setter
    def cursor_pos(self, pos):
        """ Set the cursor position """
        if pos >= 0:
            self._cursor_index = max(0, min(len(self._value), pos))
        else:
            self._cursor_index = max(len(self._value) + pos + 1, 0)
        self._reset_cursor_tick()
        self._render_text()

    def on_tick(self, event):
        """ Tick handler, gets executed every frame """
        frame_time = globalClock.get_frame_time() - self._tickstart
        show_cursor = frame_time % self._tickrate < 0.5 * self._tickrate
        if show_cursor:
            self._cursor.color = (0.5, 0.5, 0.5, 1)
        else:
            self._cursor.color = (1, 1, 1, 0)

    def on_click(self, event):
        """ Internal on click handler """
        self.request_focus()

    def on_mousedown(self, event):
        """ Internal mousedown handler """
        local_x_offset = self._text.text_handle.get_relative_pos(event.coordinates).x
        self.cursor_pos = self._text.text_handle.get_char_index(local_x_offset)

    def _reset_cursor_tick(self):
        """ Internal method to reset the cursor tick """
        self._tickstart = globalClock.get_frame_time()

    def on_focus(self, event):
        """ Internal focus handler """
        self._cursor.show()
        self._placeholder.hide()
        self._reset_cursor_tick()
        self._layout.color = (0.9, 0.9, 0.9, 1)

    def on_keydown(self, event):
        """ Internal keydown handler. Processes the special keys, and if none are
        present, redirects the event """
        key_name = event.message
        if key_name == "backspace":
            self._value = self._value[:max(0, self._cursor_index - 1)] + self._value[self._cursor_index:]
            self.cursor_pos -= 1
            self.trigger_event("changed", self._value)
        elif key_name == "delete":
            post_value = self._value[min(len(self._value), self._cursor_index + 1):]
            self._value = self._value[:self._cursor_index] + post_value
            self.cursor_pos = self._cursor_index
            self.trigger_event("changed", self._value)
        elif key_name == "arrow_left":
            if event.get_modifier_state("alt") or event.get_modifier_state("ctrl"):
                self.cursor_skip_left()
            else:
                self.cursor_pos -= 1
        elif key_name == "arrow_right":
            if event.get_modifier_state("alt") or event.get_modifier_state("ctrl"):
                self.cursor_skip_right()
            else:
                self.cursor_pos += 1
        elif key_name == "escape":
            self.blur()
        elif key_name == "home":
            self.cursor_pos = 0
        elif key_name == "end":
            self.cursor_pos = len(self.value)

        self.trigger_event(key_name, self._value)

    def on_keyrepeat(self, event):
        """ Internal keyrepeat handler """
        self.on_keydown(event)

    def on_textinput(self, event):
        """ Internal textinput handler """
        self._value = self._value[:self._cursor_index] + event.message + \
            self._value[self._cursor_index:]
        self.cursor_pos = self._cursor_index + len(event.message)
        self.trigger_event("changed", self._value)

    def on_blur(self, event):
        """ Internal blur handler """
        self._cursor.hide()
        if len(self._value) < 1:
            self._placeholder.show()

        self._layout.color = (1, 1, 1, 1)

    def _render_text(self):
        """ Internal method to render the text """
        self._text.set_text(self._value)
        self._cursor.left = self._text.left + \
            self._text.text_handle.get_char_pos(self._cursor_index) + 1
        max_left = self.width - 15

        if self._value:
            self._placeholder.hide()
        else:
            if not self.focused:
                self._placeholder.show()

        # Scroll if the cursor is outside of the clip bounds
        rel_pos = self.get_relative_pos(self._cursor.get_abs_pos()).x
        if rel_pos >= max_left:
            self._text_scroller.left = min(0, max_left - self._cursor.left)
        if rel_pos <= 0:
            self._text_scroller.left = min(0, - self._cursor.left - rel_pos)

    def cursor_skip_left(self):
        """ Moves the cursor to the left, skipping the previous word """
        left_hand_str = ''.join(reversed(self.value[0:self.cursor_pos]))
        match = self.re_skip.match(left_hand_str)
        if match is not None:
            self.cursor_pos -= match.end() - 1
        else:
            self.cursor_pos = 0

    def cursor_skip_right(self):
        """ Moves the cursor to the right, skipping the next word """
        right_hand_str = self.value[self.cursor_pos:]
        match = self.re_skip.match(right_hand_str)
        if match is not None:
            self.cursor_pos += match.end() - 1
        else:
            self.cursor_pos = len(self.value)
예제 #34
0
    def prepare_demo(self, demo_title=u"Some Demo"):

        # Background
        self._background = LUISprite(self._root, "res/DemoBackground.png")
        # Make the background solid and recieve events
        self._background.solid = True

        # Logo
        self._logo = LUISprite(self._root, "res/LUILogo.png")
        self._logo.top_left = 15, 20

        # Title
        self._title_label = LUILabel(parent=self._root,
                                     text=demo_title,
                                     font_size=40,
                                     font="header",
                                     pos=(120, 27))
        self._subtitle_label = LUILabel(parent=self._root,
                                        text="Widget Demo",
                                        font_size=14,
                                        font="default",
                                        pos=(121, 70),
                                        alpha=0.3)

        # Right bar

        self._right_bar = LUIVerticalLayout(parent=self._root)
        self._left_bar = LUIVerticalLayout(parent=self._root)
        self._right_bar.width = 350
        self._right_bar.pos = (410, 120)
        self._right_bar.spacing = 10
        self._left_bar.width = 350
        self._left_bar.pos = (20, 120)
        self._left_bar.spacing = 10

        # Public functions
        self._public_functions = LUIFrame(width=340, style=LUIFrame.FS_sunken)
        self._functions_label = LUILabel(text=U"Additional Public functions")
        self._functions_layout = LUIVerticalLayout(
            parent=self._public_functions)
        self._functions_layout.add(self._functions_label, 30)

        # Events
        self._events = LUIFrame(width=340, style=LUIFrame.FS_sunken)
        self._events_label = LUILabel(text=U"Additional Events")
        self._events_layout = LUIVerticalLayout(parent=self._events)
        self._events_layout.add(self._events_label, 30)

        # Actions
        self._actions = LUIFrame(width=340, style=LUIFrame.FS_sunken)
        self._actions_label = LUILabel(parent=self._actions,
                                       text=U"Demo-Actions")
        self._actions_select = LUISelectbox(parent=self._actions,
                                            width=225,
                                            top=30)
        self._actions_btn = LUIButton(parent=self._actions,
                                      right=0,
                                      top=30,
                                      text=u"Execute",
                                      template="ButtonGreen")
        self._actions_btn.bind("click", self._exec_action)

        # Properties
        self._properties = LUIFrame(width=340, style=LUIFrame.FS_sunken)
        self._properties_label = LUILabel(text=u"Additional Properties")
        self._properties_layout = LUIVerticalLayout(parent=self._properties)
        self._properties_layout.add(self._properties_label, 30)

        self._right_bar.add(self._actions)
        self._right_bar.add(self._public_functions)
        self._right_bar.add(self._properties)
        self._right_bar.add(self._events)

        # Widget
        self._widget_container = LUIFrame(width=360,
                                          height=250,
                                          style=LUIFrame.FS_sunken)
        self._widget_label = LUILabel(parent=self._widget_container,
                                      text=u"Widget Demo")
        self._left_bar.add(self._widget_container)

        # Source Code
        self._source_container = LUIFrame(width=360,
                                          height=190,
                                          style=LUIFrame.FS_sunken)
        self._source_label = LUILabel(parent=self._source_container,
                                      text=u"Default Constructor")
        self._copy_code_button = LUIButton(parent=self._source_container,
                                           text=u"Copy to Clipboard",
                                           template="ButtonGreen",
                                           bottom_right=(0, 0))
        self._source_content = LUIObject(self._source_container)
        self._source_content.top = 40
        self._left_bar.add(self._source_container)

        self._widget_node = LUIObject(self._widget_container, x=0, y=40)
예제 #35
0
파일: Elements.py 프로젝트: chenhaox/LUI
    def __init__(self, parent=None):
        LUIPopup.__init__(self, parent=parent, width=240, height=146)
        LUICallback.__init__(self)

        self.field = LUIObject(self.content, x=0, y=0, w=128, h=128)

        self.fieldBG = LUISprite(self.field, "blank", "skin")
        self.fieldBG.size = (128, 128)
        self.fieldBG.color = (0.2, 0.6, 1.0)
        self.fieldFG = LUISprite(self.field, "ColorpickerFieldOverlay", "skin")
        self.fieldFG.pos = (-2, 0)

        self.fieldBG.bind("mousedown", self._start_field_dragging)
        self.fieldBG.bind("mouseup", self._stop_field_dragging)

        self.fieldHandle = LUISprite(self.field, "ColorpickerFieldHandle",
                                     "skin")
        self.fieldHandle.bind("mousedown", self._start_field_dragging)
        self.fieldHandle.bind("mouseup", self._stop_field_dragging)

        self.fieldDragging = False

        self.hueSlider = LUIObject(self.content, x=140, y=0, w=40, h=128)
        self.hueSliderFG = LUISprite(self.hueSlider, "ColorpickerHueSlider",
                                     "skin")

        self.hueHandle = LUISprite(self.hueSlider, "ColorpickerHueHandle",
                                   "skin")
        self.hueHandle.left = (self.hueSliderFG.width -
                               self.hueHandle.width) / 2.0
        self.hueHandle.top = 50

        self.hueDragging = False
        self.hueSlider.bind("mousedown", self._start_hue_dragging)
        self.hueSlider.bind("mouseup", self._stop_hue_dragging)

        self.labels = LUIVerticalLayout(self.content, width=40)
        self.labels.pos = (177, 42)

        colors = [u"R", u"G", u"B"]
        self.colorLabels = []

        for color in colors:
            label = LUILabel(text=color, shadow=True)
            label.color = (1, 1, 1, 0.3)

            valueLabel = LUILabel(text=u"255", shadow=True)
            valueLabel.right = 0
            self.labels.add(label, valueLabel)
            self.colorLabels.append(valueLabel)

        self.activeColor = LUIObject(self.content, x=177, y=0)
        self.activeColorBG = LUISprite(self.activeColor, "blank", "skin")
        self.activeColorFG = LUISprite(self.activeColor,
                                       "ColorpickerActiveColorOverlay", "skin")

        self.activeColorBG.size = (40, 40)
        self.activeColorBG.pos = (2, 0)
        self.activeColorBG.color = (0.2, 0.6, 1.0, 1.0)

        self.closeButton = LUIButton(parent=self.content,
                                     text=u"Done",
                                     width=45,
                                     template="ButtonGreen")
        self.closeButton.left = 177
        self.closeButton.top = 98
        self.closeButton.bind("click", self._close_popup)

        self._set_hue(0.5)
        self._set_sat_val(0.5, 0.5)

        self.widget = parent
예제 #36
0
파일: Elements.py 프로젝트: chenhaox/LUI
class LUIColorpickerPopup(LUIPopup, LUICallback):
    def __init__(self, parent=None):
        LUIPopup.__init__(self, parent=parent, width=240, height=146)
        LUICallback.__init__(self)

        self.field = LUIObject(self.content, x=0, y=0, w=128, h=128)

        self.fieldBG = LUISprite(self.field, "blank", "skin")
        self.fieldBG.size = (128, 128)
        self.fieldBG.color = (0.2, 0.6, 1.0)
        self.fieldFG = LUISprite(self.field, "ColorpickerFieldOverlay", "skin")
        self.fieldFG.pos = (-2, 0)

        self.fieldBG.bind("mousedown", self._start_field_dragging)
        self.fieldBG.bind("mouseup", self._stop_field_dragging)

        self.fieldHandle = LUISprite(self.field, "ColorpickerFieldHandle",
                                     "skin")
        self.fieldHandle.bind("mousedown", self._start_field_dragging)
        self.fieldHandle.bind("mouseup", self._stop_field_dragging)

        self.fieldDragging = False

        self.hueSlider = LUIObject(self.content, x=140, y=0, w=40, h=128)
        self.hueSliderFG = LUISprite(self.hueSlider, "ColorpickerHueSlider",
                                     "skin")

        self.hueHandle = LUISprite(self.hueSlider, "ColorpickerHueHandle",
                                   "skin")
        self.hueHandle.left = (self.hueSliderFG.width -
                               self.hueHandle.width) / 2.0
        self.hueHandle.top = 50

        self.hueDragging = False
        self.hueSlider.bind("mousedown", self._start_hue_dragging)
        self.hueSlider.bind("mouseup", self._stop_hue_dragging)

        self.labels = LUIVerticalLayout(self.content, width=40)
        self.labels.pos = (177, 42)

        colors = [u"R", u"G", u"B"]
        self.colorLabels = []

        for color in colors:
            label = LUILabel(text=color, shadow=True)
            label.color = (1, 1, 1, 0.3)

            valueLabel = LUILabel(text=u"255", shadow=True)
            valueLabel.right = 0
            self.labels.add(label, valueLabel)
            self.colorLabels.append(valueLabel)

        self.activeColor = LUIObject(self.content, x=177, y=0)
        self.activeColorBG = LUISprite(self.activeColor, "blank", "skin")
        self.activeColorFG = LUISprite(self.activeColor,
                                       "ColorpickerActiveColorOverlay", "skin")

        self.activeColorBG.size = (40, 40)
        self.activeColorBG.pos = (2, 0)
        self.activeColorBG.color = (0.2, 0.6, 1.0, 1.0)

        self.closeButton = LUIButton(parent=self.content,
                                     text=u"Done",
                                     width=45,
                                     template="ButtonGreen")
        self.closeButton.left = 177
        self.closeButton.top = 98
        self.closeButton.bind("click", self._close_popup)

        self._set_hue(0.5)
        self._set_sat_val(0.5, 0.5)

        self.widget = parent

    def _load_rgb(self, rgb):
        hsv = colorsys.rgb_to_hsv(*rgb)
        self._set_hue(hsv[0])
        self._set_sat_val(hsv[1], hsv[2])

    def _close_popup(self, event):
        self.widget._on_popup_closed()
        self.close()

    def _update(self, event):
        if self.hueDragging:
            offset = event.coordinates.y - self.hueSliderFG.abs_pos.y
            offset /= 128.0
            offset = 1.0 - max(0.0, min(1.0, offset))
            self._set_hue(offset)

        if self.fieldDragging:
            offset = event.coordinates - self.fieldBG.abs_pos
            saturation = max(0.0, min(1.0, offset.x / 128.0))
            value = 1.0 - max(0.0, min(1.0, offset.y / 128.0))
            self._set_sat_val(saturation, value)

        self._update_color()

    def _set_sat_val(self, sat, val):
        self.saturation = sat
        self.valueValue = val

        self.fieldHandle.top = (
            1.0 - self.valueValue) * 128.0 - self.fieldHandle.height / 2.0
        self.fieldHandle.left = self.saturation * 128.0 - self.fieldHandle.width / 2.0

    def _set_hue(self, hue):
        self.hueValue = min(0.999, hue)
        self.hueHandle.top = (1.0 - hue) * 128.0 - self.hueHandle.height / 2
        self.fieldBG.color = colorsys.hsv_to_rgb(self.hueValue, 1, 1)

    def _update_color(self):
        rgb = colorsys.hsv_to_rgb(self.hueValue, self.saturation,
                                  self.valueValue)
        self.activeColorBG.color = rgb

        self.colorLabels[0].set_text(str(int(rgb[0] * 255.0)).encode('utf-8'))
        self.colorLabels[1].set_text(str(int(rgb[1] * 255.0)).encode('utf-8'))
        self.colorLabels[2].set_text(str(int(rgb[2] * 255.0)).encode('utf-8'))

        self._trigger_callback(rgb)

    def _start_field_dragging(self, event):
        if not self.fieldDragging:
            self.fieldDragging = True

    def _stop_field_dragging(self, event):
        if self.fieldDragging:
            self.fieldDragging = False

    def _start_hue_dragging(self, event):
        if not self.hueDragging:
            self.hueDragging = True

    def _stop_hue_dragging(self, event):
        if self.hueDragging:
            self.hueDragging = False
예제 #37
0
class LUIProgressbar(LUIObject):

    """ A simple progress bar """

    def __init__(self, parent=None, width=200, value=50, show_label=True):
        """ Constructs a new progress bar. If show_label is True, a label indicating
        the current progress is shown """
        LUIObject.__init__(self)
        self.set_width(width)

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

        self._fg_left = LUISprite(self, "ProgressbarFg_Left", "skin")
        self._fg_mid = LUISprite(self, "ProgressbarFg", "skin")
        self._fg_right = LUISprite(self, "ProgressbarFg_Right", "skin")
        self._fg_finish = LUISprite(self, "ProgressbarFg_Finish", "skin")

        self._show_label = show_label
        self._progress_pixel = 0
        self._fg_finish.right = 0

        if self._show_label:
            self._progress_label = LUILabel(parent=self, text=u"33 %")
            self._progress_label.centered = (True, True)

        self.set_value(value)
        self._update_progress()

        if parent is not None:
            self.parent = parent

    def get_value(self):
        """ Returns the current value of the progress bar """
        return (self._progress_pixel / self.width * 100.0)

    def set_value(self, val):
        """ Sets the value of the progress bar """
        val = max(0, min(100, val))
        self._progress_pixel = int(val / 100.0 * self.width)
        self._update_progress()

    value = property(get_value, set_value)

    def _update_progress(self):
        """ Internal method to update the progressbar """
        self._fg_finish.hide()

        if self._progress_pixel <= self._fg_left.width + self._fg_right.width:
            self._fg_mid.hide()
            self._fg_right.left = self._fg_left.width
        else:
            self._fg_mid.show()
            self._fg_mid.left = self._fg_left.width
            self._fg_mid.width = self._progress_pixel - self._fg_right.width - self._fg_left.width
            self._fg_right.left = self._fg_mid.left + self._fg_mid.width

            if self._progress_pixel >= self.width - self._fg_right.width:
                self._fg_finish.show()
                self._fg_finish.right = - (self.width - self._progress_pixel)
                self._fg_finish.clip_bounds = (0, self.width - self._progress_pixel, 0, 0)

        if self._show_label:
            percentage = self._progress_pixel / self.width * 100.0
            self._progress_label.set_text(unicode(int(percentage)) + u" %")
예제 #38
0
파일: LUIInputField.py 프로젝트: pmp-p/LUI
class LUIInputField(LUIObject):

    """ Simple input field """

    def __init__(self, parent=None, width=200, placeholder=u"Enter some text ..", value=u"", **kwargs):
        """ Constructs a new input field. An input field always needs a width specified """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self.set_width(width)
        self._layout = LUIHorizontalStretchedLayout(parent=self, prefix="InputField", width="100%")

        # Container for the text
        self._text_content = LUIObject(self)
        self._text_content.margin = (5, 7, 5, 7)
        self._text_content.clip_bounds = (0,0,0,0)
        self._text_content.set_size("100%", "100%")

        # Scroller for the text, so we can move right and left
        self._text_scroller = LUIObject(parent=self._text_content)
        self._text_scroller.center_vertical = True
        self._text = LUILabel(parent=self._text_scroller, text=u"")

        # Cursor for the current position
        self._cursor = LUISprite(self._text_scroller, "blank", "skin", x=0, y=0, w=2, h=15)
        self._cursor.color = (0.5, 0.5, 0.5)
        self._cursor.margin.top = 2
        self._cursor.z_offset = 20
        self._cursor_index = 0
        self._cursor.hide()
        self._value = value

        # Placeholder text, shown when out of focus and no value exists
        self._placeholder = LUILabel(parent=self._text_content, text=placeholder, shadow=False,
            center_vertical=True, alpha=0.2)

        # Various states
        self._tickrate = 1.0
        self._tickstart = 0.0

        self._render_text()

        if parent is not None:
            self.parent = parent

        LUIInitialState.init(self, kwargs)

    def get_value(self):
        """ Returns the value of the input field """
        return self._value

    def set_value(self, value):
        """ Sets the value of the input field """
        self._value = unicode(value)
        self.trigger_event("changed", self._value)
        self._render_text()

    value = property(get_value, set_value)

    def clear(self):
        """ Clears the input value """
        self.value = u""

    def _set_cursor_pos(self, pos):
        """ Internal method to set the cursor position """
        self._cursor_index = max(0, min(len(self._value), pos))
        self._reset_cursor_tick()

    def on_tick(self, event):
        """ Tick handler, gets executed every frame """
        frametime = globalClock.get_frame_time() - self._tickstart
        show_cursor = frametime % self._tickrate < 0.5 * self._tickrate
        if show_cursor:
            self._cursor.color = (0.5,0.5,0.5,1)
        else:
            self._cursor.color = (1,1,1,0)

    def _add_text(self, text):
        """ Internal method to append text """
        self._value = self._value[:self._cursor_index] + text + self._value[self._cursor_index:]
        self._set_cursor_pos(self._cursor_index + len(text))
        self._render_text()

    def on_click(self, event):
        """ Internal on click handler """
        self.request_focus()

    def on_mousedown(self, event):
        """ Internal mousedown handler """
        local_x_offset = self._text.text_handle.get_relative_pos(event.coordinates).x
        self._set_cursor_pos(self._text.text_handle.get_char_index(local_x_offset))
        self._render_text()

    def _reset_cursor_tick(self):
        """ Internal method to reset the cursor tick """
        self._tickstart = globalClock.getFrameTime()

    def on_focus(self, event):
        """ Internal focus handler """
        self._cursor.show()
        self._placeholder.hide()
        self._reset_cursor_tick()

        self._layout.color  = (0.9,0.9,0.9,1)

    def on_keydown(self, event):
        """ Internal keydown handler """
        key_name = event.message
        if key_name == "backspace":
            self._value = self._value[:max(0, self._cursor_index - 1)] + self._value[self._cursor_index:]
            self._set_cursor_pos(self._cursor_index - 1)
            self.trigger_event("changed", self._value)
            self._render_text()
        elif key_name == "delete":
            self._value = self._value[:self._cursor_index] + self._value[min(len(self._value), self._cursor_index + 1):]
            self._set_cursor_pos(self._cursor_index)
            self.trigger_event("changed", self._value)
            self._render_text()
        elif key_name == "arrow_left":
            self._set_cursor_pos(self._cursor_index - 1)
            self._render_text()
        elif key_name == "arrow_right":
            self._set_cursor_pos(self._cursor_index + 1)
            self._render_text()

        self.trigger_event(key_name, self._value)

    def on_keyrepeat(self, event):
        """ Internal keyrepeat handler """
        self.on_keydown(event)

    def on_textinput(self, event):
        """ Internal textinput handler """
        self._add_text(event.message)
        self.trigger_event("changed", self._value)

    def on_blur(self, event):
        """ Internal blur handler """
        self._cursor.hide()
        if len(self._value) < 1:
            self._placeholder.show()

        self._layout.color = (1,1,1,1)

    def _render_text(self):
        """ Internal method to render the text """
        self._text.set_text(self._value)
        self._cursor.left = self._text.left + self._text.text_handle.get_char_pos(self._cursor_index) + 1
        max_left = self.width - 15

        if self._value:
            self._placeholder.hide()
        else:
            if not self.focused:
                self._placeholder.show()

        # Scroll if the cursor is outside of the clip bounds
        rel_pos = self.get_relative_pos(self._cursor.get_abs_pos()).x
        if rel_pos >= max_left:
            self._text_scroller.left = min(0, max_left - self._cursor.left)
        if rel_pos <= 0:
            self._text_scroller.left = min(0, - self._cursor.left - rel_pos)
예제 #39
0
class LUIRadiobox(LUIObject):
    """ A radiobox which can be used in combination with a LUIRadioboxGroup """
    def __init__(self,
                 parent=None,
                 group=None,
                 value=None,
                 active=False,
                 label=u"Radiobox",
                 **kwargs):
        """ Constructs a new radiobox. group should be a handle to a LUIRadioboxGroup.
        value will be the value returned by group.value, in case the box was
        selected. By default, the radiobox is not active. """
        assert group is not None, "LUIRadiobox needs a LUIRadioboxGroup!"
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self._sprite = LUISprite(self, "Radiobox_Default", "skin")
        self._label = LUILabel(parent=self,
                               text=label,
                               margin=(0, 0, 0, 23),
                               center_vertical=True)
        self._value = value
        self._active = False
        self._hovered = False
        self._group = group
        self._group.register_box(self)
        if active:
            self.set_active()
        if parent:
            self.parent = parent
        LUIInitialState.init(self, kwargs)

    def on_click(self, event):
        """ Internal onclick handler. Do not override. """
        self.set_active()

    def on_mouseover(self, event):
        """ Internal mouseover handler """
        self._hovered = True
        self._update_sprite()

    def on_mouseout(self, event):
        """ Internal mouseout handler """
        self._hovered = False
        self._update_sprite()

    def set_active(self):
        """ Internal function to set the radiobox active """
        if self._group is not None:
            self._group.set_active_box(self)
        else:
            self._update_state(True)

    def get_value(self):
        """ Returns the value of the radiobox """
        return self._value

    def set_value(self, value):
        """ Sets the value of the radiobox """
        self._value = value

    value = property(get_value, set_value)

    def get_label(self):
        """ Returns a handle to the label, so it can be modified (e.g. change
            its text) """
        return self._label

    label = property(get_label)

    def _update_state(self, active):
        """ Internal method to update the state of the radiobox. Called by the
        LUIRadioboxGroup """
        self._active = active
        self.trigger_event("changed")
        self._update_sprite()

    def on_mousedown(self, event):
        """ Internal onmousedown handler. Do not override. """
        self._sprite.color = (0.86, 0.86, 0.86, 1.0)

    def on_mouseup(self, event):
        """ Internal onmouseup handler. Do not override. """
        self._sprite.color = (1, 1, 1, 1)

    def _update_sprite(self):
        """ Internal function to update the sprite of the radiobox """
        img = "Radiobox_Active" if self._active else "Radiobox_Default"
        if self._hovered:
            img += "Hover"
        self._sprite.set_texture(img, "skin")
예제 #40
0
    def _render_options(self, options):
        """ Internal method to update the options """
        num_visible_options = min(30, len(options))
        offset_top = 6
        self._layout.height = num_visible_options * 30 + offset_top + 11
        self._container.height = num_visible_options * 30 + offset_top + 1
        self._container.remove_all_children()

        current_y = offset_top
        for opt_id, opt_val in options:
            opt_container = LUIObject(self._container,
                                      x=0,
                                      y=current_y,
                                      w=self._container.width - 30,
                                      h=30)

            opt_bg = LUISprite(opt_container, "blank", "skin")
            opt_bg.width = self._container.width
            opt_bg.height = opt_container.height
            opt_bg.color = (0, 0, 0, 0)
            opt_bg.bind("mouseover", self._on_opt_over)
            opt_bg.bind("mouseout", self._on_opt_out)
            opt_bg.bind("mousedown", lambda *args: self.request_focus())
            opt_bg.bind("click", partial(self._on_opt_click, opt_id))
            opt_bg.solid = True

            opt_label = LUILabel(parent=opt_container,
                                 text=opt_val.encode('utf-8'))
            opt_label.top = 8
            opt_label.left = 8

            if opt_id == self._selectbox.selected_option:
                opt_label.color = (0.6, 0.9, 0.4, 1.0)

            divider = LUISprite(opt_container, "SelectdropDivider", "skin")
            divider.top = 30 - divider.height / 2
            divider.width = self._container.width

            current_y += 30
예제 #41
0
파일: LUIInputField.py 프로젝트: pmp-p/LUI
class LUIInputField(LUIObject):
    """ Simple input field """
    def __init__(self,
                 parent=None,
                 width=200,
                 placeholder=u"Enter some text ..",
                 value=u"",
                 **kwargs):
        """ Constructs a new input field. An input field always needs a width specified """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self.set_width(width)
        self._layout = LUIHorizontalStretchedLayout(parent=self,
                                                    prefix="InputField",
                                                    width="100%")

        # Container for the text
        self._text_content = LUIObject(self)
        self._text_content.margin = (5, 7, 5, 7)
        self._text_content.clip_bounds = (0, 0, 0, 0)
        self._text_content.set_size("100%", "100%")

        # Scroller for the text, so we can move right and left
        self._text_scroller = LUIObject(parent=self._text_content)
        self._text_scroller.center_vertical = True
        self._text = LUILabel(parent=self._text_scroller, text=u"")

        # Cursor for the current position
        self._cursor = LUISprite(self._text_scroller,
                                 "blank",
                                 "skin",
                                 x=0,
                                 y=0,
                                 w=2,
                                 h=15)
        self._cursor.color = (0.5, 0.5, 0.5)
        self._cursor.margin.top = 2
        self._cursor.z_offset = 20
        self._cursor_index = 0
        self._cursor.hide()
        self._value = value

        # Placeholder text, shown when out of focus and no value exists
        self._placeholder = LUILabel(parent=self._text_content,
                                     text=placeholder,
                                     shadow=False,
                                     center_vertical=True,
                                     alpha=0.2)

        # Various states
        self._tickrate = 1.0
        self._tickstart = 0.0

        self._render_text()

        if parent is not None:
            self.parent = parent

        LUIInitialState.init(self, kwargs)

    def get_value(self):
        """ Returns the value of the input field """
        return self._value

    def set_value(self, value):
        """ Sets the value of the input field """
        self._value = unicode(value)
        self.trigger_event("changed", self._value)
        self._render_text()

    value = property(get_value, set_value)

    def clear(self):
        """ Clears the input value """
        self.value = u""

    def _set_cursor_pos(self, pos):
        """ Internal method to set the cursor position """
        self._cursor_index = max(0, min(len(self._value), pos))
        self._reset_cursor_tick()

    def on_tick(self, event):
        """ Tick handler, gets executed every frame """
        frametime = globalClock.get_frame_time() - self._tickstart
        show_cursor = frametime % self._tickrate < 0.5 * self._tickrate
        if show_cursor:
            self._cursor.color = (0.5, 0.5, 0.5, 1)
        else:
            self._cursor.color = (1, 1, 1, 0)

    def _add_text(self, text):
        """ Internal method to append text """
        self._value = self._value[:self._cursor_index] + text + self._value[
            self._cursor_index:]
        self._set_cursor_pos(self._cursor_index + len(text))
        self._render_text()

    def on_click(self, event):
        """ Internal on click handler """
        self.request_focus()

    def on_mousedown(self, event):
        """ Internal mousedown handler """
        local_x_offset = self._text.text_handle.get_relative_pos(
            event.coordinates).x
        self._set_cursor_pos(
            self._text.text_handle.get_char_index(local_x_offset))
        self._render_text()

    def _reset_cursor_tick(self):
        """ Internal method to reset the cursor tick """
        self._tickstart = globalClock.getFrameTime()

    def on_focus(self, event):
        """ Internal focus handler """
        self._cursor.show()
        self._placeholder.hide()
        self._reset_cursor_tick()

        self._layout.color = (0.9, 0.9, 0.9, 1)

    def on_keydown(self, event):
        """ Internal keydown handler """
        key_name = event.message
        if key_name == "backspace":
            self._value = self._value[:max(0, self._cursor_index -
                                           1)] + self._value[self.
                                                             _cursor_index:]
            self._set_cursor_pos(self._cursor_index - 1)
            self.trigger_event("changed", self._value)
            self._render_text()
        elif key_name == "delete":
            self._value = self._value[:self._cursor_index] + self._value[
                min(len(self._value), self._cursor_index + 1):]
            self._set_cursor_pos(self._cursor_index)
            self.trigger_event("changed", self._value)
            self._render_text()
        elif key_name == "arrow_left":
            self._set_cursor_pos(self._cursor_index - 1)
            self._render_text()
        elif key_name == "arrow_right":
            self._set_cursor_pos(self._cursor_index + 1)
            self._render_text()

        self.trigger_event(key_name, self._value)

    def on_keyrepeat(self, event):
        """ Internal keyrepeat handler """
        self.on_keydown(event)

    def on_textinput(self, event):
        """ Internal textinput handler """
        self._add_text(event.message)
        self.trigger_event("changed", self._value)

    def on_blur(self, event):
        """ Internal blur handler """
        self._cursor.hide()
        if len(self._value) < 1:
            self._placeholder.show()

        self._layout.color = (1, 1, 1, 1)

    def _render_text(self):
        """ Internal method to render the text """
        self._text.set_text(self._value)
        self._cursor.left = self._text.left + self._text.text_handle.get_char_pos(
            self._cursor_index) + 1
        max_left = self.width - 15

        if self._value:
            self._placeholder.hide()
        else:
            if not self.focused:
                self._placeholder.show()

        # Scroll if the cursor is outside of the clip bounds
        rel_pos = self.get_relative_pos(self._cursor.get_abs_pos()).x
        if rel_pos >= max_left:
            self._text_scroller.left = min(0, max_left - self._cursor.left)
        if rel_pos <= 0:
            self._text_scroller.left = min(0, -self._cursor.left - rel_pos)
예제 #42
0
class LUISlider(LUIObject):
    """ Slider which can be used to control values """
    def __init__(self,
                 parent=None,
                 filled=True,
                 min_value=0.0,
                 max_value=1.0,
                 width=100.0,
                 value=None,
                 **kwargs):
        """ Constructs a new slider. If filled is True, the part behind the knob
        will be solid """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self.set_width(width)
        self._knob = LUISprite(self, "SliderKnob", "skin")
        self._knob.z_offset = 2
        self._knob.solid = True

        # Construct the background
        self._slider_bg = LUIHorizontalStretchedLayout(parent=self,
                                                       prefix="SliderBg",
                                                       center_vertical=True,
                                                       width="100%",
                                                       margin=(-1, 0, 0, 0))

        self._filled = filled
        self._min_value = min_value
        self._max_value = max_value

        self._side_margin = self._knob.width / 4
        self._effective_width = self.width - 2 * self._side_margin

        if self._filled:
            self._slider_fill = LUIObject(self)
            self._fill_left = LUISprite(self._slider_fill, "SliderBgFill_Left",
                                        "skin")
            self._fill_mid = LUISprite(self._slider_fill, "SliderBgFill",
                                       "skin")
            self._fill_mid.left = self._fill_left.width
            self._slider_fill.z_offset = 1
            self._slider_fill.center_vertical = True

        if parent is not None:
            self.parent = parent

        # Handle various events
        self._knob.bind("mousedown", self._start_drag)
        self._knob.bind("mousemove", self._update_drag)
        self._knob.bind("mouseup", self._stop_drag)
        self._knob.bind("keydown", self._on_keydown)
        self._knob.bind("blur", self._stop_drag)
        self._knob.bind("keyrepeat", self._on_keydown)

        self._drag_start_pos = None
        self._dragging = False
        self._drag_start_val = 0
        self.current_val = 10

        # Set initial value
        if value is None:
            self.set_value((self._min_value + self._max_value) / 2.0)
        else:
            self.set_value(value)

        self._update_knob()

        LUIInitialState.init(self, kwargs)

    def on_click(self, event):
        """ Internal on click handler """
        # I don't like this behaviour
        # relative_pos = self.get_relative_pos(event.coordinates)
        # if not self._dragging:
        #     self._set_current_val(relative_pos.x)

    def _update_knob(self):
        """ Internal method to update the slider knob """
        self._knob.left = self.current_val - (self._knob.width /
                                              2) + self._side_margin
        if self._filled:
            self._fill_mid.width = self.current_val - self._fill_left.width + self._side_margin

    def _set_current_val(self, pixels):
        """ Internal method to set the current value in pixels """
        pixels = max(0, min(self._effective_width, pixels))
        self.current_val = pixels
        self.trigger_event("changed")
        self._update_knob()

    def _start_drag(self, event):
        """ Internal drag start handler """
        self._knob.request_focus()
        if not self._dragging:
            self._drag_start_pos = event.coordinates
            self._dragging = True
            self._drag_start_val = self.current_val
            self._knob.color = (0.8, 0.8, 0.8, 1.0)

    def set_value(self, value):
        """ Sets the value of the slider, should be between minimum and maximum. """
        scaled = (float(value) - float(self._min_value)) \
                 / (float(self._max_value) - float(self._min_value)) \
                 * self._effective_width
        self._set_current_val(scaled)

    def get_value(self):
        """ Returns the current value of the slider """
        return (self.current_val / float(self._effective_width)) \
                 * (float(self._max_value) - float(self._min_value)) \
                + self._min_value

    value = property(get_value, set_value)

    def _on_keydown(self, event):
        """ Internal keydown handler """
        if event.message == "arrow_right":
            self._set_current_val(self.current_val + 2)
        elif event.message == "arrow_left":
            self._set_current_val(self.current_val - 2)
        elif event.message == "escape":
            self.current_val = self._drag_start_val
            self._stop_drag(event)
            self._update_knob()

    def _update_drag(self, event):
        """ Internal drag handler """
        if self._dragging:
            dragOffset = event.coordinates.x - self._drag_start_pos.x
            finalValue = self._drag_start_val + dragOffset
            self._set_current_val(finalValue)

    def _stop_drag(self, event):
        """ Internal drag stop handelr """
        self._drag_start_pos = None
        self._dragging = False
        self._drag_start_val = self.current_val
        self._knob.color = (1, 1, 1, 1)
예제 #43
0
class LUIProgressbar(LUIObject):
    """ A simple progress bar """
    def __init__(self, parent=None, width=200, value=50, show_label=True):
        """ Constructs a new progress bar. If show_label is True, a label indicating
        the current progress is shown """
        LUIObject.__init__(self)
        self.set_width(width)

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

        self._fg_left = LUISprite(self, "ProgressbarFg_Left", "skin")
        self._fg_mid = LUISprite(self, "ProgressbarFg", "skin")
        self._fg_right = LUISprite(self, "ProgressbarFg_Right", "skin")
        self._fg_finish = LUISprite(self, "ProgressbarFg_Finish", "skin")

        self._show_label = show_label
        self._progress_pixel = 0
        self._fg_finish.right = 0

        if self._show_label:
            self._progress_label = LUILabel(parent=self, text=u"33 %")
            self._progress_label.centered = (True, True)

        self.set_value(value)
        self._update_progress()

        if parent is not None:
            self.parent = parent

    def get_value(self):
        """ Returns the current value of the progress bar """
        return (self._progress_pixel / self.width * 100.0)

    def set_value(self, val):
        """ Sets the value of the progress bar """
        val = max(0, min(100, val))
        self._progress_pixel = int(val / 100.0 * self.width)
        self._update_progress()

    value = property(get_value, set_value)

    def _update_progress(self):
        """ Internal method to update the progressbar """
        self._fg_finish.hide()

        if self._progress_pixel <= self._fg_left.width + self._fg_right.width:
            self._fg_mid.hide()
            self._fg_right.left = self._fg_left.width
        else:
            self._fg_mid.show()
            self._fg_mid.left = self._fg_left.width
            self._fg_mid.width = self._progress_pixel - self._fg_right.width - self._fg_left.width
            self._fg_right.left = self._fg_mid.left + self._fg_mid.width

            if self._progress_pixel >= self.width - self._fg_right.width:
                self._fg_finish.show()
                self._fg_finish.right = -(self.width - self._progress_pixel)
                self._fg_finish.clip_bounds = (0, self.width -
                                               self._progress_pixel, 0, 0)

        if self._show_label:
            percentage = self._progress_pixel / self.width * 100.0
            self._progress_label.set_text(unicode(int(percentage)) + u" %")
예제 #44
0
    def __init__(self,
                 parent=None,
                 filled=True,
                 min_value=0.0,
                 max_value=1.0,
                 width=100.0,
                 value=None,
                 **kwargs):
        """ Constructs a new slider. If filled is True, the part behind the knob
        will be solid """
        LUIObject.__init__(self, x=0, y=0, solid=True)
        self.set_width(width)
        self._knob = LUISprite(self, "SliderKnob", "skin")
        self._knob.z_offset = 2
        self._knob.solid = True

        # Construct the background
        self._slider_bg = LUIHorizontalStretchedLayout(parent=self,
                                                       prefix="SliderBg",
                                                       center_vertical=True,
                                                       width="100%",
                                                       margin=(-1, 0, 0, 0))

        self._filled = filled
        self._min_value = min_value
        self._max_value = max_value

        self._side_margin = self._knob.width / 4
        self._effective_width = self.width - 2 * self._side_margin

        if self._filled:
            self._slider_fill = LUIObject(self)
            self._fill_left = LUISprite(self._slider_fill, "SliderBgFill_Left",
                                        "skin")
            self._fill_mid = LUISprite(self._slider_fill, "SliderBgFill",
                                       "skin")
            self._fill_mid.left = self._fill_left.width
            self._slider_fill.z_offset = 1
            self._slider_fill.center_vertical = True

        if parent is not None:
            self.parent = parent

        # Handle various events
        self._knob.bind("mousedown", self._start_drag)
        self._knob.bind("mousemove", self._update_drag)
        self._knob.bind("mouseup", self._stop_drag)
        self._knob.bind("keydown", self._on_keydown)
        self._knob.bind("blur", self._stop_drag)
        self._knob.bind("keyrepeat", self._on_keydown)

        self._drag_start_pos = None
        self._dragging = False
        self._drag_start_val = 0
        self.current_val = 10

        # Set initial value
        if value is None:
            self.set_value((self._min_value + self._max_value) / 2.0)
        else:
            self.set_value(value)

        self._update_knob()

        LUIInitialState.init(self, kwargs)
예제 #45
0
파일: Elements.py 프로젝트: chenhaox/LUI
class LUIColorpicker(LUIObject):
    def __init__(self, parent=None, color=None):
        LUIObject.__init__(self, x=0, y=0, w=27, h=27)

        self.previewBg = LUISprite(self, "ColorpickerPreviewBg", "skin")

        self.filler = LUISprite(self, "blank", "skin")
        self.filler.width = 21
        self.filler.height = 21
        self.filler.pos = (5, 5)
        self.filler.color = (0.2, 0.6, 1.0, 1.0)

        self.overlay = LUISprite(self, "ColorpickerPreviewOverlay", "skin")
        self.overlay.pos = (2, 2)
        self.overlay.bind("click", self._open_dialog)

        self.fit_to_children()

        self.popup = LUIColorpickerPopup(self)
        self.popup.hide()

        if color is not None:
            self.colorValue = color
        else:
            # My favourite color
            self.colorValue = (0.2, 0.6, 1.0)
        self.set_color_value(self.colorValue)

        self.popup.add_change_callback(self._on_popup_color_changed)

        if parent is not None:
            self.parent = parent

    def _open_dialog(self, event):
        if self.has_focus():
            self.blur()
        else:
            self.request_focus()

    def on_focus(self, event):
        self.popup._load_rgb(self.colorValue)
        self.popup.open_at(self, 14.0)

    def set_color_value(self, rgb):
        self.colorValue = rgb
        self.filler.color = rgb

    def get_color_value(self):
        return self.colorValue

    def on_tick(self, event):
        self.popup._update(event)

    def on_blur(self, event):
        self.popup.close()

    def _on_popup_color_changed(self, popup, rgb):
        self.set_color_value(rgb)

    def _on_popup_closed(self):
        self.blur()
예제 #46
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")