mirror of https://github.com/kivy/kivy.git
Merge pull request #662 from kivy/faster_text_input
Faster text input insertion and deletion
This commit is contained in:
commit
6a0e9fae77
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- encoding: utf8 -*-
|
||||||
'''
|
'''
|
||||||
Text Input
|
Text Input
|
||||||
==========
|
==========
|
||||||
|
@ -121,7 +122,12 @@ from kivy.properties import StringProperty, NumericProperty, \
|
||||||
ReferenceListProperty, BooleanProperty, AliasProperty, \
|
ReferenceListProperty, BooleanProperty, AliasProperty, \
|
||||||
ListProperty, ObjectProperty
|
ListProperty, ObjectProperty
|
||||||
|
|
||||||
Cache.register('textinput.label', timeout=60.)
|
Cache_register = Cache.register
|
||||||
|
Cache_append = Cache.append
|
||||||
|
Cache_get = Cache.get
|
||||||
|
Cache_remove = Cache.remove
|
||||||
|
Cache_register('textinput.label', timeout=60.)
|
||||||
|
Cache_register('textinput.width', timeout=60.)
|
||||||
|
|
||||||
FL_IS_NEWLINE = 0x01
|
FL_IS_NEWLINE = 0x01
|
||||||
|
|
||||||
|
@ -136,7 +142,8 @@ _textinput_list = []
|
||||||
if 'KIVY_DOC' not in environ:
|
if 'KIVY_DOC' not in environ:
|
||||||
|
|
||||||
def _textinput_clear_cache(*l):
|
def _textinput_clear_cache(*l):
|
||||||
Cache.remove('textinput.label')
|
Cache_remove('textinput.label')
|
||||||
|
Cache_remove('textinput.width')
|
||||||
for wr in _textinput_list[:]:
|
for wr in _textinput_list[:]:
|
||||||
textinput = wr()
|
textinput = wr()
|
||||||
if textinput is None:
|
if textinput is None:
|
||||||
|
@ -233,12 +240,12 @@ class TextInput(Widget):
|
||||||
self.bind(font_size=self._trigger_refresh_line_options,
|
self.bind(font_size=self._trigger_refresh_line_options,
|
||||||
font_name=self._trigger_refresh_line_options)
|
font_name=self._trigger_refresh_line_options)
|
||||||
|
|
||||||
self.bind(padding_x=self._trigger_refresh_text,
|
self.bind(padding_x=self._update_text_options,
|
||||||
padding_y=self._trigger_refresh_text,
|
padding_y=self._update_text_options,
|
||||||
tab_width=self._trigger_refresh_text,
|
tab_width=self._update_text_options,
|
||||||
font_size=self._trigger_refresh_text,
|
font_size=self._update_text_options,
|
||||||
font_name=self._trigger_refresh_text,
|
font_name=self._update_text_options,
|
||||||
size=self._trigger_refresh_text)
|
size=self._update_text_options)
|
||||||
|
|
||||||
self.bind(pos=self._trigger_update_graphics)
|
self.bind(pos=self._trigger_update_graphics)
|
||||||
|
|
||||||
|
@ -278,7 +285,8 @@ class TextInput(Widget):
|
||||||
offset = 0
|
offset = 0
|
||||||
if self.cursor_col:
|
if self.cursor_col:
|
||||||
offset = self._get_text_width(
|
offset = self._get_text_width(
|
||||||
self._lines[self.cursor_row][:self.cursor_col])
|
self._lines[self.cursor_row][:self.cursor_col], self.tab_width,
|
||||||
|
self._label_cached)
|
||||||
return offset
|
return offset
|
||||||
|
|
||||||
def get_cursor_from_index(self, index):
|
def get_cursor_from_index(self, index):
|
||||||
|
@ -329,20 +337,26 @@ class TextInput(Widget):
|
||||||
ci = sci()
|
ci = sci()
|
||||||
text = self._lines[cr]
|
text = self._lines[cr]
|
||||||
len_str = len(substring)
|
len_str = len(substring)
|
||||||
|
insert_at_end = True if text[cc:] == '' else False
|
||||||
new_text = text[:cc] + substring + text[cc:]
|
new_text = text[:cc] + substring + text[cc:]
|
||||||
self._set_line_text(cr, new_text)
|
self._set_line_text(cr, new_text)
|
||||||
if len_str > 1 or substring == '\n':
|
if len_str > 1 or substring == '\n':
|
||||||
# Avoid refreshing text on every keystroke.
|
# Avoid refreshing text on every keystroke.
|
||||||
# Allows for faster typing of text when the amount of text in
|
# Allows for faster typing of text when the amount of text in
|
||||||
# TextInput gets large.
|
# TextInput gets large.
|
||||||
self._trigger_refresh_text()
|
start = cr
|
||||||
#reset cursor
|
lines, lineflags = self._split_smart(new_text)
|
||||||
|
len_lines = len(lines)
|
||||||
|
finish = cr + (len_lines - 1)
|
||||||
|
self._trigger_refresh_text('insert', start, finish, lines,
|
||||||
|
lineflags, len_lines)
|
||||||
|
# reset cursor
|
||||||
self.cursor = cursor = self.get_cursor_from_index(ci + len_str)
|
self.cursor = cursor = self.get_cursor_from_index(ci + len_str)
|
||||||
#handle undo and redo
|
# handle undo and redo
|
||||||
self._set_unredo_insert(cc, cr, ci, sci, substring, cursor, from_undo)
|
self._set_unredo_insert(cc, cr, ci, sci, substring, cursor, from_undo)
|
||||||
|
|
||||||
def _set_unredo_insert(self, cc, cr, ci, sci, substring, cursor, from_undo):
|
def _set_unredo_insert(self, cc, cr, ci, sci, substring, cursor, from_undo):
|
||||||
#handle undo and redo
|
# handle undo and redo
|
||||||
if from_undo:
|
if from_undo:
|
||||||
return
|
return
|
||||||
count = substring.count('\n')
|
count = substring.count('\n')
|
||||||
|
@ -353,7 +367,7 @@ class TextInput(Widget):
|
||||||
|
|
||||||
self._undo.append({'undo_command': ('insert', cursor, ci, sci()),
|
self._undo.append({'undo_command': ('insert', cursor, ci, sci()),
|
||||||
'redo_command': (cc, cr, substring)})
|
'redo_command': (cc, cr, substring)})
|
||||||
#reset redo when undo is appended to
|
# reset redo when undo is appended to
|
||||||
self._redo = []
|
self._redo = []
|
||||||
|
|
||||||
def reset_undo(self):
|
def reset_undo(self):
|
||||||
|
@ -380,7 +394,6 @@ class TextInput(Widget):
|
||||||
cc, cr, substring = x_item['redo_command']
|
cc, cr, substring = x_item['redo_command']
|
||||||
self.cursor = cc, cr
|
self.cursor = cc, cr
|
||||||
self.insert_text(substring, True)
|
self.insert_text(substring, True)
|
||||||
#substring.replace('\n', '\\n').replace('\'', '\\\'')
|
|
||||||
elif undo_type == 'bkspc':
|
elif undo_type == 'bkspc':
|
||||||
cc, cr = x_item['redo_command']
|
cc, cr = x_item['redo_command']
|
||||||
self.cursor = cc, cr
|
self.cursor = cc, cr
|
||||||
|
@ -462,11 +475,11 @@ class TextInput(Widget):
|
||||||
# where large..ish text is involved.
|
# where large..ish text is involved.
|
||||||
#self._refresh_text_from_property()
|
#self._refresh_text_from_property()
|
||||||
self.cursor = cursor = self.get_cursor_from_index(cursor_index - 1)
|
self.cursor = cursor = self.get_cursor_from_index(cursor_index - 1)
|
||||||
#handle undo and redo
|
# handle undo and redo
|
||||||
self._set_undo_redo_bkspc(cc, cr, cursor, substring, from_undo)
|
self._set_undo_redo_bkspc(cc, cr, cursor, substring, from_undo)
|
||||||
|
|
||||||
def _set_undo_redo_bkspc(self, cc, cr, cursor, substring, from_undo):
|
def _set_undo_redo_bkspc(self, cc, cr, cursor, substring, from_undo):
|
||||||
#handle undo and redo for backspace
|
# handle undo and redo for backspace
|
||||||
if from_undo:
|
if from_undo:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -530,8 +543,11 @@ class TextInput(Widget):
|
||||||
cy = (self.top - self.padding_y + scrl_y * dy) - y
|
cy = (self.top - self.padding_y + scrl_y * dy) - y
|
||||||
cy = int(boundary(round(cy / dy), 0, len(l) - 1))
|
cy = int(boundary(round(cy / dy), 0, len(l) - 1))
|
||||||
dcx = 0
|
dcx = 0
|
||||||
|
_get_text_width = self._get_text_width
|
||||||
|
_tab_width = self.tab_width
|
||||||
|
_label_cached = self._label_cached
|
||||||
for i in xrange(1, len(l[cy]) + 1):
|
for i in xrange(1, len(l[cy]) + 1):
|
||||||
if self._get_text_width(l[cy][:i]) >= cx:
|
if _get_text_width(l[cy][:i], _tab_width, _label_cached) >= cx:
|
||||||
break
|
break
|
||||||
dcx = i
|
dcx = i
|
||||||
cx = dcx
|
cx = dcx
|
||||||
|
@ -556,33 +572,36 @@ class TextInput(Widget):
|
||||||
scrl_x = self.scroll_x
|
scrl_x = self.scroll_x
|
||||||
scrl_y = self.scroll_y
|
scrl_y = self.scroll_y
|
||||||
cc, cr = self.cursor
|
cc, cr = self.cursor
|
||||||
#sci = self.cursor_index
|
|
||||||
#ci = sci()
|
|
||||||
if not self._selection:
|
if not self._selection:
|
||||||
return
|
return
|
||||||
v = self.text
|
v = self.text
|
||||||
a, b = self._selection_from, self._selection_to
|
a, b = self._selection_from, self._selection_to
|
||||||
if a > b:
|
if a > b:
|
||||||
a, b = b, a
|
a, b = b, a
|
||||||
text = v[:a] + v[b:]
|
|
||||||
self.text = text
|
|
||||||
text = v[a:b]
|
|
||||||
self.cursor = cursor = self.get_cursor_from_index(a)
|
self.cursor = cursor = self.get_cursor_from_index(a)
|
||||||
|
start = cursor
|
||||||
|
finish = self.get_cursor_from_index(b)
|
||||||
|
cur_line = self._lines[start[1]][:start[0]] +\
|
||||||
|
self._lines[finish[1]][finish[0]:]
|
||||||
|
lines, lineflags = self._split_smart(cur_line)
|
||||||
|
len_lines = len(lines)
|
||||||
|
self._refresh_text(self.text, 'del', start[1], finish[1], lines,
|
||||||
|
lineflags, len_lines)
|
||||||
self.scroll_x = scrl_x
|
self.scroll_x = scrl_x
|
||||||
self.scroll_y = scrl_y
|
self.scroll_y = scrl_y
|
||||||
#handle undo and redo
|
# handle undo and redo for delete selecttion
|
||||||
self._set_unredo_delsel(cc, cr, a, b, cursor, text, from_undo)
|
self._set_unredo_delsel(cc, cr, a, b, cursor, v[a:b], from_undo)
|
||||||
self.cancel_selection()
|
self.cancel_selection()
|
||||||
|
|
||||||
def _set_unredo_delsel(self, cc, cr, ci, sci, cursor, substring, from_undo):
|
def _set_unredo_delsel(self, cc, cr, ci, sci, cursor, substring, from_undo):
|
||||||
#handle undo and redo for backspace
|
# handle undo and redo for backspace
|
||||||
if from_undo:
|
if from_undo:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._undo.append({
|
self._undo.append({
|
||||||
'undo_command': ('delsel', cursor, substring),
|
'undo_command': ('delsel', cursor, substring),
|
||||||
'redo_command': (ci, sci, cc, cr)})
|
'redo_command': (ci, sci, cc, cr)})
|
||||||
#reset redo when undo is appended to
|
# reset redo when undo is appended to
|
||||||
self._redo = []
|
self._redo = []
|
||||||
|
|
||||||
def _update_selection(self, finished=False):
|
def _update_selection(self, finished=False):
|
||||||
|
@ -603,7 +622,8 @@ class TextInput(Widget):
|
||||||
# update graphics only on new line
|
# update graphics only on new line
|
||||||
# allows smoother scrolling, noticeably
|
# allows smoother scrolling, noticeably
|
||||||
# faster when dealing with large text.
|
# faster when dealing with large text.
|
||||||
self._trigger_update_graphics()
|
self._update_graphics_selection()
|
||||||
|
#self._trigger_update_graphics()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Touch control
|
# Touch control
|
||||||
|
@ -773,11 +793,10 @@ class TextInput(Widget):
|
||||||
return
|
return
|
||||||
if Clipboard is None:
|
if Clipboard is None:
|
||||||
from kivy.core.clipboard import Clipboard
|
from kivy.core.clipboard import Clipboard
|
||||||
Clipboard
|
|
||||||
_platform = platform()
|
_platform = platform()
|
||||||
if _platform == 'win':
|
if _platform == 'win':
|
||||||
self._clip_mime_type = 'text/plain;charset=utf-8'
|
self._clip_mime_type = 'text/plain;charset=utf-8'
|
||||||
#windows clipboard uses a utf-16 encoding
|
# windows clipboard uses a utf-16 encoding
|
||||||
self._encoding = 'utf-16'
|
self._encoding = 'utf-16'
|
||||||
elif _platform == 'linux':
|
elif _platform == 'linux':
|
||||||
self._clip_mime_type = 'UTF8_STRING'
|
self._clip_mime_type = 'UTF8_STRING'
|
||||||
|
@ -815,6 +834,7 @@ class TextInput(Widget):
|
||||||
data = data.replace('\x00', '')
|
data = data.replace('\x00', '')
|
||||||
self.delete_selection()
|
self.delete_selection()
|
||||||
self.insert_text(data)
|
self.insert_text(data)
|
||||||
|
data = None
|
||||||
|
|
||||||
def _keyboard_released(self):
|
def _keyboard_released(self):
|
||||||
# Callback called when the real keyboard is taken by someone else
|
# Callback called when the real keyboard is taken by someone else
|
||||||
|
@ -822,14 +842,22 @@ class TextInput(Widget):
|
||||||
# FIXME: handle virtual keyboard.
|
# FIXME: handle virtual keyboard.
|
||||||
self.focus = False
|
self.focus = False
|
||||||
|
|
||||||
def _get_text_width(self, text):
|
def _get_text_width(self, text, tab_width, _label_cached):
|
||||||
# Return the width of a text, according to the current line options
|
# Return the width of a text, according to the current line options
|
||||||
if not self._label_cached:
|
width = Cache_get('textinput.width', text)
|
||||||
|
if width:
|
||||||
|
return width
|
||||||
|
if not _label_cached:
|
||||||
self._get_line_options()
|
self._get_line_options()
|
||||||
text = text.replace('\t', ' ' * self.tab_width)
|
_label_cached = self._label_cached
|
||||||
|
orig_text = text
|
||||||
|
text = text.replace('\t', ' ' * tab_width)
|
||||||
if not self.password:
|
if not self.password:
|
||||||
return self._label_cached.get_extents(text)[0]
|
width = _label_cached.get_extents(text)[0]
|
||||||
return self._label_cached.get_extents('*' * len(text))[0]
|
else:
|
||||||
|
width = _label_cached.get_extents('*' * len(text))[0]
|
||||||
|
Cache_append('textinput.width', orig_text, width)
|
||||||
|
return width
|
||||||
|
|
||||||
def _do_blink_cursor(self, dt):
|
def _do_blink_cursor(self, dt):
|
||||||
# Callback called by the timer to blink the cursor, according to the
|
# Callback called by the timer to blink the cursor, according to the
|
||||||
|
@ -867,23 +895,50 @@ class TextInput(Widget):
|
||||||
self.cursor = self.get_cursor_from_index(len(self.text))
|
self.cursor = self.get_cursor_from_index(len(self.text))
|
||||||
|
|
||||||
def _trigger_refresh_text(self, *largs):
|
def _trigger_refresh_text(self, *largs):
|
||||||
Clock.unschedule(self._refresh_text_from_property)
|
if len(largs) and largs[0] == self:
|
||||||
Clock.schedule_once(self._refresh_text_from_property)
|
largs = ()
|
||||||
|
Clock.unschedule(
|
||||||
|
lambda *args: self._refresh_text_from_property(*largs))
|
||||||
|
Clock.schedule_once(
|
||||||
|
lambda *args: self._refresh_text_from_property(*largs))
|
||||||
|
|
||||||
|
def _update_text_options(self, *largs):
|
||||||
|
Cache_remove('textinput.width')
|
||||||
|
self._trigger_refresh_text()
|
||||||
|
|
||||||
def _refresh_text_from_property(self, *largs):
|
def _refresh_text_from_property(self, *largs):
|
||||||
self._refresh_text(self.text)
|
self._refresh_text(self.text, *largs)
|
||||||
|
|
||||||
def _refresh_text(self, text):
|
def _refresh_text(self, text, *largs):
|
||||||
# Refresh all the lines from a new text.
|
# Refresh all the lines from a new text.
|
||||||
# By using cache in internal functions, this method should be fast.
|
# By using cache in internal functions, this method should be fast.
|
||||||
_lines, self._lines_flags = self._split_smart(text)
|
mode = 'all'
|
||||||
self._lines = _lines
|
if len(largs):
|
||||||
|
mode, start, finish, _lines, _lines_flags, len_lines = largs
|
||||||
|
else:
|
||||||
|
_lines, self._lines_flags = self._split_smart(text)
|
||||||
|
_lines_labels = []
|
||||||
|
_line_rects = []
|
||||||
_create_label = self._create_line_label
|
_create_label = self._create_line_label
|
||||||
_lines_labels = self._lines_labels =\
|
|
||||||
[_create_label(x) for x in _lines]
|
for x in _lines:
|
||||||
self._lines_rects = [Rectangle(texture=x, size=(
|
lbl = _create_label(x)
|
||||||
x.size if x else (0, 0)))
|
_lines_labels.append(lbl)
|
||||||
for x in _lines_labels]
|
_line_rects.append(
|
||||||
|
Rectangle(size=(lbl.size if lbl else (0, 0))))
|
||||||
|
lbl = None
|
||||||
|
|
||||||
|
if mode == 'all':
|
||||||
|
self._lines = _lines
|
||||||
|
self._lines_labels = _lines_labels
|
||||||
|
self._lines_rects = _line_rects
|
||||||
|
elif mode == 'del':
|
||||||
|
self._insert_lines(start, finish + 1, len_lines, _lines_flags,
|
||||||
|
_lines, _lines_labels, _line_rects)
|
||||||
|
elif mode == 'insert':
|
||||||
|
self._insert_lines(start, start + 1, len_lines, _lines_flags,
|
||||||
|
_lines, _lines_labels, _line_rects)
|
||||||
|
|
||||||
line_label = _lines_labels[0]
|
line_label = _lines_labels[0]
|
||||||
if line_label is None:
|
if line_label is None:
|
||||||
self.line_height = max(1, self.font_size + self.padding_y)
|
self.line_height = max(1, self.font_size + self.padding_y)
|
||||||
|
@ -901,6 +956,41 @@ class TextInput(Widget):
|
||||||
# with the new text don't forget to update graphics again
|
# with the new text don't forget to update graphics again
|
||||||
self._trigger_update_graphics()
|
self._trigger_update_graphics()
|
||||||
|
|
||||||
|
def _insert_lines(self, start, finish, len_lines, _lines_flags, _lines,
|
||||||
|
_lines_labels, _line_rects):
|
||||||
|
_lins_flags = []
|
||||||
|
_lins_flags.extend(self._lines_flags[:start])
|
||||||
|
if len_lines:
|
||||||
|
# if not inserting at first line then
|
||||||
|
if start:
|
||||||
|
# make sure new line is set in line flags cause
|
||||||
|
# _split_smart assumes first line to be not a new line
|
||||||
|
_lines_flags[0] = 1
|
||||||
|
_lins_flags.extend(_lines_flags)
|
||||||
|
_lins_flags.extend(self._lines_flags[finish:])
|
||||||
|
self._lines_flags = _lins_flags
|
||||||
|
|
||||||
|
_lins = []
|
||||||
|
_lins.extend(self._lines[:start])
|
||||||
|
if len_lines:
|
||||||
|
_lins.extend(_lines)
|
||||||
|
_lins.extend(self._lines[finish:])
|
||||||
|
self._lines = _lins
|
||||||
|
|
||||||
|
_lins_lbls = []
|
||||||
|
_lins_lbls.extend(self._lines_labels[:start])
|
||||||
|
if len_lines:
|
||||||
|
_lins_lbls.extend(_lines_labels)
|
||||||
|
_lins_lbls.extend(self._lines_labels[finish:])
|
||||||
|
self._lines_labels = _lins_lbls
|
||||||
|
|
||||||
|
_lins_rcts = []
|
||||||
|
_lins_rcts.extend(self._lines_rects[:start])
|
||||||
|
if len_lines:
|
||||||
|
_lins_rcts.extend(_line_rects)
|
||||||
|
_lins_rcts.extend(self._lines_rects[finish:])
|
||||||
|
self._lines_rects = _lins_rcts
|
||||||
|
|
||||||
def _trigger_update_graphics(self, *largs):
|
def _trigger_update_graphics(self, *largs):
|
||||||
Clock.unschedule(self._update_graphics)
|
Clock.unschedule(self._update_graphics)
|
||||||
Clock.schedule_once(self._update_graphics, -1)
|
Clock.schedule_once(self._update_graphics, -1)
|
||||||
|
@ -1010,7 +1100,6 @@ class TextInput(Widget):
|
||||||
miny = self.y + _padding_y
|
miny = self.y + _padding_y
|
||||||
maxy = _top - _padding_y
|
maxy = _top - _padding_y
|
||||||
draw_selection = self._draw_selection
|
draw_selection = self._draw_selection
|
||||||
#scroll_y = self.scroll_y
|
|
||||||
a, b = self._selection_from, self._selection_to
|
a, b = self._selection_from, self._selection_to
|
||||||
if a > b:
|
if a > b:
|
||||||
a, b = b, a
|
a, b = b, a
|
||||||
|
@ -1018,23 +1107,32 @@ class TextInput(Widget):
|
||||||
s1c, s1r = get_cursor_from_index(a)
|
s1c, s1r = get_cursor_from_index(a)
|
||||||
s2c, s2r = get_cursor_from_index(b)
|
s2c, s2r = get_cursor_from_index(b)
|
||||||
s2r += 1
|
s2r += 1
|
||||||
# pass only the selection lines
|
# pass only the selection lines[]
|
||||||
# passing all the lines can get slow when dealing with a lot of text
|
# passing all the lines can get slow when dealing with a lot of text
|
||||||
y -= s1r * dy
|
y -= s1r * dy
|
||||||
for line_num, value in enumerate(self._lines[s1r:s2r], start=s1r):
|
_lines = self._lines
|
||||||
|
_get_text_width = self._get_text_width
|
||||||
|
tab_width = self.tab_width
|
||||||
|
_label_cached = self._label_cached
|
||||||
|
width = self.width
|
||||||
|
padding_x = self.padding_x
|
||||||
|
x = self.x
|
||||||
|
canvas_add = self.canvas.add
|
||||||
|
selection_color = self.selection_color
|
||||||
|
for line_num, value in enumerate(_lines[s1r:s2r], start=s1r):
|
||||||
if miny <= y <= maxy + dy:
|
if miny <= y <= maxy + dy:
|
||||||
r = rects[line_num]
|
r = rects[line_num]
|
||||||
draw_selection(r.pos, r.size, line_num)
|
draw_selection(r.pos, r.size, line_num, (s1c, s1r),
|
||||||
|
(s2c, s2r - 1), _lines, _get_text_width, tab_width,
|
||||||
|
_label_cached, width, padding_x, x, canvas_add,
|
||||||
|
selection_color)
|
||||||
y -= dy
|
y -= dy
|
||||||
|
|
||||||
def _draw_selection(self, pos, size, line_num):
|
def _draw_selection(self, *largs):
|
||||||
|
pos, size, line_num, (s1c, s1r), (s2c, s2r),\
|
||||||
|
_lines, _get_text_width, tab_width, _label_cached, width,\
|
||||||
|
padding_x, x, canvas_add, selection_color = largs
|
||||||
# Draw the current selection on the widget.
|
# Draw the current selection on the widget.
|
||||||
a, b = self._selection_from, self._selection_to
|
|
||||||
if a > b:
|
|
||||||
a, b = b, a
|
|
||||||
get_cursor_from_index = self.get_cursor_from_index
|
|
||||||
s1c, s1r = get_cursor_from_index(a)
|
|
||||||
s2c, s2r = get_cursor_from_index(b)
|
|
||||||
if line_num < s1r or line_num > s2r:
|
if line_num < s1r or line_num > s2r:
|
||||||
return
|
return
|
||||||
x, y = pos
|
x, y = pos
|
||||||
|
@ -1042,18 +1140,17 @@ class TextInput(Widget):
|
||||||
x1 = x
|
x1 = x
|
||||||
x2 = x + w
|
x2 = x + w
|
||||||
if line_num == s1r:
|
if line_num == s1r:
|
||||||
lines = self._lines[line_num]
|
lines = _lines[line_num]
|
||||||
x1 += self._get_text_width(lines[:s1c])
|
x1 += _get_text_width(lines[:s1c], tab_width, _label_cached)
|
||||||
if line_num == s2r:
|
if line_num == s2r:
|
||||||
lines = self._lines[line_num]
|
lines = _lines[line_num]
|
||||||
x2 = x + self._get_text_width(lines[:s2c])
|
x2 = x + _get_text_width(lines[:s2c], tab_width, _label_cached)
|
||||||
width_minus_padding_x = self.width - self.padding_x
|
width_minus_padding_x = width - padding_x
|
||||||
maxx = x + width_minus_padding_x
|
maxx = x + width_minus_padding_x
|
||||||
if x1 > maxx:
|
if x1 > maxx:
|
||||||
return
|
return
|
||||||
x2 = min(x2, self.x + width_minus_padding_x)
|
x2 = min(x2, x + width_minus_padding_x)
|
||||||
canvas_add = self.canvas.add
|
canvas_add(Color(*selection_color, group='selection'))
|
||||||
canvas_add(Color(*self.selection_color, group='selection'))
|
|
||||||
canvas_add(Rectangle(
|
canvas_add(Rectangle(
|
||||||
pos=(x1, pos[1]), size=(x2 - x1, size[1]), group='selection'))
|
pos=(x1, pos[1]), size=(x2 - x1, size[1]), group='selection'))
|
||||||
|
|
||||||
|
@ -1099,7 +1196,7 @@ class TextInput(Widget):
|
||||||
ntext = '*' * len(ntext)
|
ntext = '*' * len(ntext)
|
||||||
kw = self._get_line_options()
|
kw = self._get_line_options()
|
||||||
cid = '%s\0%s' % (ntext, str(kw))
|
cid = '%s\0%s' % (ntext, str(kw))
|
||||||
texture = Cache.get('textinput.label', cid)
|
texture = Cache_get('textinput.label', cid)
|
||||||
|
|
||||||
if not texture:
|
if not texture:
|
||||||
# FIXME right now, we can't render very long line...
|
# FIXME right now, we can't render very long line...
|
||||||
|
@ -1130,7 +1227,7 @@ class TextInput(Widget):
|
||||||
|
|
||||||
# ok, we found it.
|
# ok, we found it.
|
||||||
texture = label.texture
|
texture = label.texture
|
||||||
Cache.append('textinput.label', cid, texture)
|
Cache_append('textinput.label', cid, texture)
|
||||||
return texture
|
return texture
|
||||||
|
|
||||||
def _tokenize(self, text):
|
def _tokenize(self, text):
|
||||||
|
@ -1165,18 +1262,21 @@ class TextInput(Widget):
|
||||||
line = []
|
line = []
|
||||||
lines = []
|
lines = []
|
||||||
lines_flags = []
|
lines_flags = []
|
||||||
|
_join = ''.join
|
||||||
|
lines_append, lines_flags_append = lines.append, lines_flags.append
|
||||||
width = self.width - self.padding_x * 2
|
width = self.width - self.padding_x * 2
|
||||||
text_width = self._get_text_width
|
text_width = self._get_text_width
|
||||||
|
_tab_width, _label_cached = self.tab_width, self._label_cached
|
||||||
|
|
||||||
# try to add each word on current line.
|
# try to add each word on current line.
|
||||||
for word in self._tokenize(text):
|
for word in self._tokenize(text):
|
||||||
is_newline = (word == '\n')
|
is_newline = (word == '\n')
|
||||||
w = text_width(word)
|
w = text_width(word, _tab_width, _label_cached)
|
||||||
# if we have more than the width, or if it's a newline,
|
# if we have more than the width, or if it's a newline,
|
||||||
# push the current line, and create a new one
|
# push the current line, and create a new one
|
||||||
if (x + w > width and line) or is_newline:
|
if (x + w > width and line) or is_newline:
|
||||||
lines.append(''.join(line))
|
lines_append(_join(line))
|
||||||
lines_flags.append(flags)
|
lines_flags_append(flags)
|
||||||
flags = 0
|
flags = 0
|
||||||
line = []
|
line = []
|
||||||
x = 0
|
x = 0
|
||||||
|
@ -1186,8 +1286,8 @@ class TextInput(Widget):
|
||||||
x += w
|
x += w
|
||||||
line.append(word)
|
line.append(word)
|
||||||
if line or flags & FL_IS_NEWLINE:
|
if line or flags & FL_IS_NEWLINE:
|
||||||
lines.append(''.join(line))
|
lines_append(_join(line))
|
||||||
lines_flags.append(flags)
|
lines_flags_append(flags)
|
||||||
|
|
||||||
return lines, lines_flags
|
return lines, lines_flags
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue