class LUIKeyMarker(LUIObject): 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 set_key(self, key): self.label.set_text(key) self.width = self.label.width + self.bgLeft.width + self.bgRight.width + 7 self.bgMid.width = self.width - self.bgLeft.width - self.bgRight.width self.bgMid.left = self.bgLeft.width self.bgRight.left = self.bgMid.width + self.bgMid.left self.fit_to_children()
class LUIProgressbar(LUIObject): def __init__(self, parent=None, width=200, value=50, show_label=True): LUIObject.__init__(self, x=0, y=0, w=width, h=0) self.bgLeft = LUISprite(self, "ProgressbarBg_Left", "skin") self.bgMid = LUISprite(self, "ProgressbarBg", "skin") self.bgRight = LUISprite(self, "ProgressbarBg_Right", "skin") self.bgMid.width = self.width - self.bgLeft.width - self.bgRight.width self.bgMid.left = self.bgLeft.width self.bgRight.left = self.bgMid.width + self.bgMid.left self.fgLeft = LUISprite(self, "ProgressbarFg_Left", "skin") self.fgMid = LUISprite(self, "ProgressbarFg", "skin") self.fgRight = LUISprite(self, "ProgressbarFg_Right", "skin") self.fgFinish = LUISprite(self, "ProgressbarFg_Finish", "skin") self.showLabel = show_label self.progressPixel = 0 self.fgFinish.right = 0 self.fit_to_children() if self.showLabel: self.progressLabel = LUILabel(parent=self, text=u"33 %", shadow=True) self.progressLabel.centered = (True, False) self.progressLabel.top = -1 self.set_value(value) self._update_progress() if parent is not None: self.parent = parent def set_value(self, val): val = max(0, min(100, val)) self.progressPixel = int(val / 100.0 * self.width) self._update_progress() def _update_progress(self): self.fgFinish.hide() if self.progressPixel <= self.fgLeft.width + self.fgRight.width: self.fgMid.hide() self.fgRight.left = self.fgLeft.width else: self.fgMid.show() self.fgMid.left = self.fgLeft.width self.fgMid.width = self.progressPixel - self.fgRight.width - self.fgLeft.width self.fgRight.left = self.fgMid.left + self.fgMid.width if self.progressPixel >= self.width - self.fgRight.width: self.fgFinish.show() self.fgFinish.right = -(self.width - self.progressPixel) self.fgFinish.clip_bounds = (0, self.width - self.progressPixel, 0, 0) if self.showLabel: percentage = self.progressPixel / self.width * 100.0 self.progressLabel.set_text(unicode(int(percentage)) + u" %")
class LUISliderWithLabel(LUIObject, LUICallback): def __init__(self, parent=None, width=100.0, filled=False, min_value=0, max_value=1.0, precision=2, value=None): LUIObject.__init__(self, x=0, y=0, w=width, h=0) LUICallback.__init__(self) max_numbers_before = max(len(str(int(max_value))), len(str(int(min_value)))) number_space_required = max_numbers_before if precision > 0: number_space_required += 1 + precision pixels_per_number = 7 self.precision = precision self.slider = LUISlider( self, width=width - pixels_per_number * number_space_required - 5, filled=filled, min_value=min_value, max_value=max_value, value=value, ) self.label = LUILabel(parent=self, shadow=True, text=u"1.23") self.label.right = 0 self.label.top = self.label.height - self.slider.height self.label.color = (1, 1, 1, 0.5) self.slider.add_change_callback(self._on_slider_changed) self.slider.add_change_callback(self._trigger_callback) self._on_slider_changed(self.slider, self.slider.get_value()) if parent is not None: self.parent = parent self.fit_to_children() def get_value(self): return self.slider.get_value() def set_value(self, val): self.slider.set_value(val) def _on_slider_changed(self, obj, value): self.label.set_text(("{:." + str(self.precision) + "f}").format(value))
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)
# Constructor f.add_constructor_parameter("text", "Label") f.add_constructor_parameter("shadow", "True") f.add_constructor_parameter("font_size", "14") f.add_constructor_parameter("font", "'label'") # Functions f.add_public_function("get_text", [], "string") f.add_public_function("set_text", [("text", "string")]) f.add_property("text", "string") f.add_property("text_handle", "LUIText") # Events f.construct_sourcecode("LUILabel") # Create a new label label = LUILabel(parent=f.get_widget_node(), text="This is a fancy label") f.set_actions({ "Set Random Text": lambda: label.set_text(str(random.randint(100, 10000))), "Set Random Color": lambda: label.set_color(random.random(), random.random(), random.random(), 1) }) run()
class LUIInputField(LUIObject, LUICallback): def __init__(self, parent=None, width=200, placeholder=u"Enter some text ..", value=u""): LUIObject.__init__(self, x=0, y=0, w=width, h=0) LUICallback.__init__(self) self.bgLeft = LUISprite(self, "InputField_Left", "skin") self.bgMid = LUISprite(self, "InputField", "skin") self.bgRight = LUISprite(self, "InputField_Right", "skin") self.textContent = LUIObject(self) self.textContent.margin = (5, 8, 5, 8) self.textContent.clip_bounds = (0, 0, 0, 0) self.textContent.height = self.bgMid.height - 10 self.textContent.width = self.width - 16 self.textScroller = LUIObject(parent=self.textContent, x=0, y=0) self.text = LUILabel(parent=self.textScroller, text=u"", shadow=True) self.cursor = LUISprite(self.textScroller, "blank", "skin", x=0, y=0, w=2, h=15.0) self.cursor.color = (0.5, 0.5, 0.5) self.cursor.margin_top = 3 self.cursor.z_offset = 20 self.cursor_index = 0 self.cursor.hide() self.value = value self.placeholder = LUILabel(parent=self.textContent, text=placeholder, shadow=False) self.placeholder.color = (1, 1, 1, 0.5) self.bgMid.width = self.width - self.bgLeft.width - self.bgRight.width self.bgMid.left = self.bgLeft.width self.bgRight.left = self.bgMid.width + self.bgMid.left if len(self.value) > 0: self.placeholder.hide() self.tickrate = 1.0 self.tickstart = 0.0 self.fit_to_children() self._render_text() if parent is not None: self.parent = parent def _set_cursor_pos(self, pos): self.cursor_index = max(0, min(len(self.value), pos)) self._reset_cursor_tick() def on_tick(self, event): frametime = globalClock.getFrameTime() - 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): 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): self.request_focus() def on_mousedown(self, event): local_x_offset = self.text.text.get_relative_pos(event.coordinates).x self._set_cursor_pos(self.text.text.get_char_index(local_x_offset)) self._render_text() def _reset_cursor_tick(self): self.tickstart = globalClock.getFrameTime() def on_focus(self, event): self.cursor.show() self.placeholder.hide() self._reset_cursor_tick() self.bgLeft.color = (0.9, 0.9, 0.9, 1) self.bgMid.color = (0.9, 0.9, 0.9, 1) self.bgRight.color = (0.9, 0.9, 0.9, 1) def on_keydown(self, event): key_name = event.get_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_callback(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_callback(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() def on_keyrepeat(self, event): self.on_keydown(event) def on_textinput(self, event): self._add_text(event.get_message()) self._trigger_callback(self.value) def on_blur(self, event): self.cursor.hide() if len(self.value) < 1: self.placeholder.show() self.bgLeft.color = (1, 1, 1, 1) self.bgMid.color = (1, 1, 1, 1) self.bgRight.color = (1, 1, 1, 1) def get_value(self): return self.value def set_value(self, value): self.value = value # QUESTION: Should we trigger a callback when the user changes the value hisself? self._trigger_callback(self.value) self._render_text() def _render_text(self): self.text.set_text(self.value) self.cursor.left = self.text.left + self.text.text.get_char_pos(self.cursor_index) + 1 max_left = self.width - 20 # Scroll if the cursor is outside of the clip bounds relX = self.get_relative_pos(self.cursor.get_abs_pos()).x if relX >= max_left: self.textScroller.left = min(0, max_left - self.cursor.left) if relX <= 0: self.textScroller.left = min(0, -self.cursor.left - relX)
from DemoFramework import DemoFramework from LUILabel import LUILabel import random f = DemoFramework() f.prepare_demo("LUILabel") # Constructor f.add_constructor_parameter("text", "u'Label'") f.add_constructor_parameter("shadow", "True") f.add_constructor_parameter("font_size", "14") f.add_constructor_parameter("font", "'label'") # Functions f.add_public_function("get_text", [], "string") f.add_public_function("set_text", [("text", "string")]) # Events f.construct_sourcecode("LUILabel") # Create a new label label = LUILabel(parent=f.get_widget_node(), text="This is a fancy label") f.set_actions({ "Set Random Text": lambda: label.set_text(unicode(random.randint(100, 10000))), }) run()
# Constructor f.add_constructor_parameter("text", "u'Label'") f.add_constructor_parameter("shadow", "True") f.add_constructor_parameter("font_size", "14") f.add_constructor_parameter("font", "'label'") # Functions f.add_public_function("get_text", [], "string") f.add_public_function("set_text", [("text", "string")]) f.add_property("text", "string") f.add_property("text_handle", "LUIText") # Events f.construct_sourcecode("LUILabel") # Create a new label label = LUILabel(parent=f.get_widget_node(), text="This is a fancy label") f.set_actions({ "Set Random Text": lambda: label.set_text(unicode(random.randint(100, 10000))), "Set Random Color": lambda: label.set_color(random.random(), random.random(), random.random(), 1) }) run()
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 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 LUISelectbox(LUIObject, LUICallback): def __init__(self, width=200, options=None, selectedOption=None, **kwargs): LUIObject.__init__(self, x=0, y=0, w=width+4, h=0, solid=True) LUIInitialState.init(self, kwargs) LUICallback.__init__(self) # The selectbox has a small border, to correct this we move it self.margin_left = -2 self.bgLeft = LUISprite(self, "Selectbox_Left", "skin") self.bgMid = LUISprite(self, "Selectbox", "skin") self.bgRight = LUISprite(self, "Selectbox_Right", "skin") self.bgMid.width = self.width - self.bgLeft.width - self.bgRight.width self.bgMid.left = self.bgLeft.width self.bgRight.left = self.bgMid.width + self.bgMid.left self.bgRight.z_offset = 1 self.labelContainer = LUIObject(self, x=10, y=6, w=width - 20 - self.bgRight.width, h=self.bgMid.height - 6) self.labelContainer.clip_bounds = (0,0,0,0) self.label = LUILabel(parent=self.labelContainer, text=u"Select an option ..", shadow=True) self.bgRight.bind("mouseover", self._knob_mouseover) self.bgRight.bind("mouseout", self._knob_mouseout) self.bgRight.bind("click", self.on_click) self.bgRight.bind("click", self.on_click) self.fit_to_children() self.dropMenu = UISelectdrop(parent=self, width=width) self.dropMenu.top = self.bgMid.height - 7 self.dropMenu.topmost = True self.dropOpen = False self.dropMenu.hide() self.options = [] self.currentOptionId = None if options is not None: self.options = options self._select_option(selectedOption) def get_selected_option(self): return self.currentOptionId def _render_options(self): self.dropMenu._render_options(self.options) def set_options(self, options): self.options = options self.currentOptionId = None self._render_options() def _select_option(self, optid): self.label.color = (1,1,1,1) for optID, optVal in self.options: if optID == optid: self.label.set_text(optVal) self.currentOptionId = optID return self.label.color = (1,1,1,0.5) def _knob_mouseover(self, event): self.bgRight.color = (0.9,0.9,0.9,1.0) def _knob_mouseout(self, event): self.bgRight.color = (1,1,1,1.0) def on_click(self, event): self.request_focus() if self.dropOpen: self._close_drop() else: self._open_drop() def on_mousedown(self, event): self.bgLeft.color = (0.9,0.9,0.9,1.0) self.bgMid.color = (0.9,0.9,0.9,1.0) def on_mouseup(self, event): self.bgLeft.color = (1,1,1,1.0) self.bgMid.color = (1,1,1,1.0) def _open_drop(self): if not self.dropOpen: self._render_options() self.dropMenu.show() self.request_focus() self.dropOpen = True def _close_drop(self): if self.dropOpen: self.dropMenu.hide() self.dropOpen = False def _on_option_selected(self, optid): self._select_option(optid) self._close_drop() def on_blur(self, event): self._close_drop()
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)
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 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)