Пример #1
0
class LUIInputField(LUIObject):
    """ Simple input field, accepting text input. This input field supports
    entering text and navigating. Selecting text is (currently) not supported.

    The input field also supports various keyboard shortcuts:

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

    """

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

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

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

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

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

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

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

        self._render_text()

        if parent is not None:
            self.parent = parent

        LUIInitialState.init(self, kwargs)

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

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

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

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

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

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

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

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

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

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

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

        self.trigger_event(key_name, self._value)

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

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

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

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

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

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

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

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

    def cursor_skip_right(self):
        """ Moves the cursor to the right, skipping the next word """
        right_hand_str = self.value[self.cursor_pos:]
        match = self.re_skip.match(right_hand_str)
        if match is not None:
            self.cursor_pos += match.end() - 1
        else:
            self.cursor_pos = len(self.value)
Пример #2
0
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)
Пример #3
0
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)
Пример #4
0
class LUIInputField(LUIObject):

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

    The input field also supports various keyboard shortcuts:

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

    """

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

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

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

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

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

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

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

        self._render_text()

        if parent is not None:
            self.parent = parent

        LUIInitialState.init(self, kwargs)

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

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

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

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

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

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

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

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

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

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

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

        self.trigger_event(key_name, self._value)

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

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

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

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

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

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

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

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

    def cursor_skip_right(self):
        """ Moves the cursor to the right, skipping the next word """
        right_hand_str = self.value[self.cursor_pos:]
        match = self.re_skip.match(right_hand_str)
        if match is not None:
            self.cursor_pos += match.end() - 1
        else:
            self.cursor_pos = len(self.value)
Пример #5
0
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)