class TestWindow(pyglet.window.Window): def __init__(self, *args, **kwargs): super(TestWindow, self).__init__(*args, **kwargs) self.batch = pyglet.graphics.Batch() self.document = pyglet.text.decode_attributed(doctext) for i in range(0, len(doctext), 300): self.document.insert_element(i, TestElement(60, -10, 70)) self.margin = 2 self.layout = IncrementalTextLayout(self.document, self.width - self.margin * 2, self.height - self.margin * 2, multiline=True, batch=self.batch) self.caret = caret.Caret(self.layout) self.push_handlers(self.caret) self.set_mouse_cursor(self.get_system_mouse_cursor('text')) def on_resize(self, width, height): super(TestWindow, self).on_resize(width, height) self.layout.begin_update() self.layout.x = self.margin self.layout.y = self.margin self.layout.width = width - self.margin * 2 self.layout.height = height - self.margin * 2 self.layout.end_update() def on_mouse_scroll(self, x, y, scroll_x, scroll_y): self.layout.view_x -= scroll_x self.layout.view_y += scroll_y * 16 def on_draw(self): pyglet.gl.glClearColor(0, 0, 0, 1) self.clear() self.batch.draw() def on_key_press(self, symbol, modifiers): super(TestWindow, self).on_key_press(symbol, modifiers) if symbol == pyglet.window.key.TAB: self.caret.on_text('\t')
class WeaponBar(object): """docstring for WeaponBar""" def __init__(self, batch, scale): super(WeaponBar, self).__init__() self.batch = batch self.weapons = {} self.ammos = [] self.tris = [] self.scale = scale def __len__(self): return len(self.weapons) def init_bar(self, weapons): try: if self.ammolayout._document: self.ammolayout.delete() for tri in self.tris: tri.remove() except AttributeError: pass ammotext = '\t'.join(str(w.ammo) for key, w in weapons.iteritems() if key != 'w0') self.ammos = ammotext.split('\t') self.ammodoc = FormattedDocument(ammotext) self.ammodoc.set_style(0, len(ammotext), dict(color=[255]*4, tab_stops=[120*(i+1) for i in range(6)], font_size=24, align='center', wrap=False)) self.ammolayout = IncrementalTextLayout(self.ammodoc, width=600, height=50, batch=self.batch, multiline=True) self.ammolayout.x = (1280 / 2) * self.scale.x self.ammolayout.y = 20 * self.scale.y self.ammolayout.anchor_x = 'center' self.ammolayout.anchor_y = 'bottom' w = self.ammolayout.content_width colorlist = [weaponcolors[key] for key in weapons if key != 'w0'] self.tris = [Triangle(640*self.scale.x-w/2-52+120*i, 35*self.scale.y, 50, 50, col, self.batch, 0, 0) for i, col in enumerate(colorlist)] def update(self, weapons): if len(weapons) != len(self.weapons): self.init_bar(weapons) self.weapons = weapons.copy() else: ammotext = '\t'.join(str(w.ammo) for key, w in weapons.iteritems() if key != 'w0') ammos = ammotext.split('\t') self.ammolayout.begin_update() for i, a in enumerate(ammos): if a != self.ammos[i]: ln = sum(len(self.ammos[j]) for j in range(i)) + i self.ammodoc.delete_text(ln, ln + len(self.ammos[i])) self.ammodoc.insert_text(ln, a) self.ammolayout.end_update() self.ammos = ammos def remove(self): try: self.weapons = {} self.ammolayout.delete() for tri in self.tris: tri.remove() except AttributeError: pass
class Editable(Control): def __init__(self, value="", x=0, y=0, width=125, height=30, padding=(0, 0), wrap=True, id=None, **kwargs): """ An editable text box. When clicked, it has the focus and can receive keyboard events. With wrap=True, several lines of text will wrap around the width. Optional parameters can include fill, font, fontsize, fontweight. """ txt = Text( value or " ", **{ "fill": _popdefault(kwargs, "fill", Color(0, 0.9)), "font": _popdefault(kwargs, "font", theme["fontname"]), "fontsize": _popdefault(kwargs, "fontsize", theme["fontsize"]), "fontweight": _popdefault(kwargs, "fontweight", theme["fontweight"]), "lineheight": _popdefault(kwargs, "lineheight", 1), "align": LEFT, } ) kwargs["width"] = width kwargs["height"] = height Control.__init__(self, x=x, y=y, id=id, **kwargs) self.reserved = kwargs.get("reserved", [ENTER, TAB]) self._padding = padding self._i = 0 # Index of character on which the mouse is pressed. self._empty = value == "" and True or False self._editor = IncrementalTextLayout(txt._label.document, width, height, multiline=wrap) self._editor.content_valign = wrap and "top" or "center" self._editor.selection_background_color = (170, 200, 230, 255) self._editor.selection_color = txt._label.color self._editor.caret = Caret(self._editor) self._editor.caret.visible = False self._editing = False # When True, cursor is blinking and text can be edited. Editable._pack(self) # On init, call Editable._pack(), not the derived Field._pack(). def _pack(self): self._editor.x = self._padding[0] self._editor.y = self._padding[1] self._editor.width = max(0, self.width - self._padding[0] * 2) self._editor.height = max(0, self.height - self._padding[1] * 2) def _get_value(self): # IncrementalTextLayout in Pyglet 1.1.4 has a bug with empty strings. # We keep track of empty strings with Editable._empty to avoid this. return not self._empty and self._editor.document.text or u"" def _set_value(self, string): self._editor.begin_update() self._editor.document.text = string or " " self._editor.end_update() self._empty = string == "" and True or False value = property(_get_value, _set_value) def _get_editing(self): return self._editing def _set_editing(self, b): self._editing = b self._editor.caret.visible = b global EDITING if b is False and EDITING == self: EDITING = None if b is True: EDITING = self # Cursor is blinking and text can be edited. # Visit all layers on the canvas. # Remove the caret from all other Editable controls. for layer in self.root.canvas and self.root.canvas.layers or []: layer.traverse( visit=lambda layer: isinstance(layer, Editable) and layer != self and setattr(layer, "editing", False) ) editing = property(_get_editing, _set_editing) @property def selection(self): # Yields a (start, stop)-tuple with the indices of the current selected text. return (self._editor.selection_start, self._editor.selection_end) @property def selected(self): # Yields True when text is currently selected. return self.selection[0] != self.selection[1] @property def cursor(self): # Yields the index at the text cursor (caret). return self._editor.caret.position def index(self, x, y): """ Returns the index of the character in the text at position x, y. """ x0, y0 = self.absolute_position() i = self._editor.get_position_from_point(x - x0, y - y0) if self._editor.get_point_from_position(0)[0] > x - x0: # Pyglet bug? i = 0 if self._empty: i = 0 return i def on_mouse_enter(self, mouse): mouse.cursor = TEXT def on_mouse_press(self, mouse): i = self._i = self.index(mouse.x, mouse.y) self._editor.set_selection(0, 0) self.editing = True self._editor.caret.position = i Control.on_mouse_press(self, mouse) def on_mouse_release(self, mouse): if not self.dragged: self._editor.caret.position = self.index(mouse.x, mouse.y) Control.on_mouse_release(self, mouse) def on_mouse_drag(self, mouse): i = self.index(mouse.x, mouse.y) self._editor.selection_start = max(min(self._i, i), 0) self._editor.selection_end = min(max(self._i, i), len(self.value)) self._editor.caret.visible = False Control.on_mouse_drag(self, mouse) def on_mouse_doubleclick(self, mouse): # Select the word at the mouse position. # Words are delimited by non-alphanumeric characters. i = self.index(mouse.x, mouse.y) delimiter = lambda ch: not (ch.isalpha() or ch.isdigit()) if i < len(self.value) and delimiter(self.value[i]): self._editor.set_selection(i, i + 1) if i == len(self.value) and self.value != "" and delimiter(self.value[i - 1]): self._editor.set_selection(i - 1, i) a = _find(lambda (i, ch): delimiter(ch), enumerate(reversed(self.value[:i]))) b = _find(lambda (i, ch): delimiter(ch), enumerate(self.value[i:])) a = a and i - a[0] or 0 b = b and i + b[0] or len(self.value) self._editor.set_selection(a, b) def on_key_press(self, keys): if self._editing: self._editor.caret.visible = True i = self._editor.caret.position if keys.code == LEFT: # The left arrow moves the text cursor to the left. self._editor.caret.position = max(i - 1, 0) elif keys.code == RIGHT: # The right arrow moves the text cursor to the right. self._editor.caret.position = min(i + 1, len(self.value)) elif keys.code in (UP, DOWN): # The up arrows moves the text cursor to the previous line. # The down arrows moves the text cursor to the next line. y = keys.code == UP and -1 or +1 n = self._editor.get_line_count() i = self._editor.get_position_on_line( max(self._editor.get_line_from_position(i) + y, 0), self._editor.get_point_from_position(i)[0] ) self._editor.caret.position = i elif keys.code == TAB and TAB in self.reserved: # The tab key navigates away from the control. self._editor.caret.position = 0 self.editing = False elif keys.code == ENTER and ENTER in self.reserved: # The enter key executes on_action() and navigates away from the control. self._editor.caret.position = 0 self.editing = False self.on_action() elif keys.code == BACKSPACE and self.selected: # The backspace key removes the current text selection. self.value = self.value[: self.selection[0]] + self.value[self.selection[1] :] self._editor.caret.position = max(self.selection[0], 0) elif keys.code == BACKSPACE and i > 0: # The backspace key removes the character at the text cursor. self.value = self.value[: i - 1] + self.value[i:] self._editor.caret.position = max(i - 1, 0) elif keys.char: if self.selected: # Typing replaces any text currently selected. self.value = self.value[: self.selection[0]] + self.value[self.selection[1] :] self._editor.caret.position = i = max(self.selection[0], 0) ch = keys.char ch = ch.replace("\r", "\n\r") self.value = self.value[:i] + ch + self.value[i:] self._editor.caret.position = min(i + 1, len(self.value)) self._editor.set_selection(0, 0) def draw(self): self._editor.draw()
class Editable(Control): def __init__(self, value="", x=0, y=0, width=125, height=30, padding=(0,0), wrap=True, id=None, **kwargs): """ An editable text box. When clicked, it has the focus and can receive keyboard events. With wrap=True, several lines of text will wrap around the width. Optional parameters can include fill, font, fontsize, fontweight. """ txt = Text(value or " ", **{ "fill" : _popdefault(kwargs, "fill", Color(0,0.9)), "font" : _popdefault(kwargs, "font", theme["fontname"]), "fontsize" : _popdefault(kwargs, "fontsize", theme["fontsize"]), "fontweight" : _popdefault(kwargs, "fontweight", theme["fontweight"]), "lineheight" : _popdefault(kwargs, "lineheight", 1), "align" : LEFT }) kwargs["width"] = width kwargs["height"] = height Control.__init__(self, x=x, y=y, id=id, **kwargs) self.reserved = kwargs.get("reserved", [ENTER, TAB]) self._padding = padding self._i = 0 # Index of character on which the mouse is pressed. self._empty = value == "" and True or False self._editor = IncrementalTextLayout(txt._label.document, width, height, multiline=wrap) self._editor.content_valign = wrap and "top" or "center" self._editor.selection_background_color = (170, 200, 230, 255) self._editor.selection_color = txt._label.color self._editor.caret = Caret(self._editor) self._editor.caret.visible = False self._editing = False # When True, cursor is blinking and text can be edited. Editable._pack(self) # On init, call Editable._pack(), not the derived Field._pack(). def _pack(self): self._editor.x = self._padding[0] self._editor.y = self._padding[1] self._editor.width = max(0, self.width - self._padding[0] * 2) self._editor.height = max(0, self.height - self._padding[1] * 2) def _get_value(self): # IncrementalTextLayout in Pyglet 1.1.4 has a bug with empty strings. # We keep track of empty strings with Editable._empty to avoid this. return not self._empty and self._editor.document.text or u"" def _set_value(self, string): self._editor.begin_update() self._editor.document.text = string or " " self._editor.end_update() self._empty = string == "" and True or False value = property(_get_value, _set_value) def _get_editing(self): return self._editing def _set_editing(self, b): self._editing = b self._editor.caret.visible = b global EDITING if b is False and EDITING == self: EDITING = None if b is True: EDITING = self # Cursor is blinking and text can be edited. # Visit all layers on the canvas. # Remove the caret from all other Editable controls. for layer in (self.root.canvas and self.root.canvas.layers or []): layer.traverse(visit=lambda layer: \ isinstance(layer, Editable) and layer != self and \ setattr(layer, "editing", False)) editing = property(_get_editing, _set_editing) @property def selection(self): # Yields a (start, stop)-tuple with the indices of the current selected text. return (self._editor.selection_start, self._editor.selection_end) @property def selected(self): # Yields True when text is currently selected. return self.selection[0] != self.selection[1] @property def cursor(self): # Yields the index at the text cursor (caret). return self._editor.caret.position def index(self, x, y): """ Returns the index of the character in the text at position x, y. """ x0, y0 = self.absolute_position() i = self._editor.get_position_from_point(x-x0, y-y0) if self._editor.get_point_from_position(0)[0] > x-x0: # Pyglet bug? i = 0 if self._empty: i = 0 return i def on_mouse_enter(self, mouse): mouse.cursor = TEXT def on_mouse_press(self, mouse): i = self._i = self.index(mouse.x, mouse.y) self._editor.set_selection(0, 0) self.editing = True self._editor.caret.position = i Control.on_mouse_press(self, mouse) def on_mouse_release(self, mouse): if not self.dragged: self._editor.caret.position = self.index(mouse.x, mouse.y) Control.on_mouse_release(self, mouse) def on_mouse_drag(self, mouse): i = self.index(mouse.x, mouse.y) self._editor.selection_start = max(min(self._i, i), 0) self._editor.selection_end = min(max(self._i, i), len(self.value)) self._editor.caret.visible = False Control.on_mouse_drag(self, mouse) def on_mouse_doubleclick(self, mouse): # Select the word at the mouse position. # Words are delimited by non-alphanumeric characters. i = self.index(mouse.x, mouse.y) delimiter = lambda ch: not (ch.isalpha() or ch.isdigit()) if i < len(self.value) and delimiter(self.value[i]): self._editor.set_selection(i, i+1) if i == len(self.value) and self.value != "" and delimiter(self.value[i-1]): self._editor.set_selection(i-1, i) a = _find(lambda (i,ch): delimiter(ch), enumerate(reversed(self.value[:i]))) b = _find(lambda (i,ch): delimiter(ch), enumerate(self.value[i:])) a = a and i-a[0] or 0 b = b and i+b[0] or len(self.value) self._editor.set_selection(a, b) def on_key_press(self, key): if self._editing: self._editor.caret.visible = True i = self._editor.caret.position if key.code == LEFT: # The left arrow moves the text cursor to the left. self._editor.caret.position = max(i-1, 0) elif key.code == RIGHT: # The right arrow moves the text cursor to the right. self._editor.caret.position = min(i+1, len(self.value)) elif key.code in (UP, DOWN): # The up arrows moves the text cursor to the previous line. # The down arrows moves the text cursor to the next line. y = key.code == UP and -1 or +1 n = self._editor.get_line_count() i = self._editor.get_position_on_line( max(self._editor.get_line_from_position(i)+y, 0), self._editor.get_point_from_position(i)[0]) self._editor.caret.position = i elif key.code == TAB and TAB in self.reserved: # The tab key navigates away from the control. self._editor.caret.position = 0 self.editing = False elif key.code == ENTER and ENTER in self.reserved: # The enter key executes on_action() and navigates away from the control. self._editor.caret.position = 0 self.editing = False self.on_action() elif key.code == BACKSPACE and self.selected: # The backspace key removes the current text selection. self.value = self.value[:self.selection[0]] + self.value[self.selection[1]:] self._editor.caret.position = max(self.selection[0], 0) elif key.code == BACKSPACE and i > 0: # The backspace key removes the character at the text cursor. self.value = self.value[:i-1] + self.value[i:] self._editor.caret.position = max(i-1, 0) elif key.char: if self.selected: # Typing replaces any text currently selected. self.value = self.value[:self.selection[0]] + self.value[self.selection[1]:] self._editor.caret.position = i = max(self.selection[0], 0) ch = key.char ch = ch.replace("\r", "\n\r") self.value = self.value[:i] + ch + self.value[i:] self._editor.caret.position = min(i+1, len(self.value)) self._editor.set_selection(0, 0) def draw(self): self._editor.draw()