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 __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 __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
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")
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 __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 __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 __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 __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)
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()
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 __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)
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)
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 __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)
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")
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)
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 __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
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
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")
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")
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")
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)
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")
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)
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)
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)
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
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
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" %")
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)
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")
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
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)
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)
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" %")
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)
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()
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")