Add mouse events, event demo.
authorRadek Brich <radek.brich@devl.cz>
Sat, 21 Feb 2015 12:01:57 +0100
changeset 118 8c7970520632
parent 117 8680c2333546
child 119 dd91747504dd
Add mouse events, event demo.
demos/03_application.py
demos/03_event.py
demos/04_texteditor.py
demos/05_fixedlayout.py
demos/06_gridlayout.py
demos/10_application.py
demos/11_fixedlayout.py
demos/12_gridlayout.py
demos/20_texteditor.py
tuikit/core/container.py
tuikit/core/events.py
tuikit/core/widget.py
tuikit/driver/cursesw.py
tuikit/driver/driver.py
tuikit/scrollwindow.py
tuikit/widgets/button.py
tuikit/widgets/scrollbar.py
tuikit/widgets/textbox.py
--- a/demos/03_application.py	Mon Feb 16 21:17:43 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-#!/usr/bin/env python3
-
-from tuikit.core.application import Application
-from tuikit.widgets.label import Label
-from tuikit.widgets.button import Button
-from tuikit.widgets.textfield import TextField
-
-label = Label('Hello there!')
-button1 = Button()
-button2 = Button()
-field = TextField('text field')
-
-app = Application()
-app.root_window.add(label, 20, 10)
-app.root_window.add(button1, 20, 20)
-app.root_window.add(button2, 30, 20)
-app.root_window.add(field, 20, 30)
-app.root_window.focus_widget = field
-
-def on_keypress(ev):
-    if ev.keyname == 'escape':
-        app.stop()
-
-app.window_manager.sig_keypress.connect(on_keypress)
-
-app.start()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/03_event.py	Sat Feb 21 12:01:57 2015 +0100
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+
+from tuikit.core.buffer import Buffer
+from tuikit.driver.cursesw import CursesWDriver
+
+buffer = Buffer()
+line = 0
+with CursesWDriver() as driver:
+    buffer.resize(*driver.size)
+    buffer.puts(str(driver.size), 0, 10)
+    while True:
+        for event in driver.getevents():
+            buffer.puts(str(event), 0, line)
+            line += 1
+        driver.draw(buffer)
+        driver.flush()
+        if line >= buffer.size.h:
+            buffer.fill()
+            line = 0
--- a/demos/04_texteditor.py	Mon Feb 16 21:17:43 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-
-from tuikit.core.application import Application
-#from tuikit.scrollview import ScrollView
-from tuikit.widgets.textbox import TextBox
-
-
-class MyApplication(Application):
-
-    def __init__(self):
-        Application.__init__(self)
-        self.window_manager.sig_keypress.connect(self.on_wm_keypress)
-        #self.top.add_handler('keypress', self.on_top_keypress)
-
-        t = open(sys.argv[0]).read()
-        editbox = TextBox(t)
-
-        #scroll = ScrollView()
-        #scroll.add(editbox)
-
-        self.root_window.add(editbox)
-        #self.root_window.add(scroll, halign='fill', valign='fill')
-
-    def on_wm_keypress(self, ev):
-        if ev.keyname == 'escape':
-            self.stop()
-            return True
-
-if __name__ == '__main__':
-    app = MyApplication()
-    app.start()
-
--- a/demos/05_fixedlayout.py	Mon Feb 16 21:17:43 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-#!/usr/bin/env python3
-
-from tuikit.core.application import Application
-from tuikit.widgets.button import Button
-
-app = Application()
-app.add(Button('left=0'), left=0)
-app.add(Button('left=5'), left=5, top=2)
-app.add(Button('right=0'), right=0)
-app.add(Button('right=5'), right=5, top=2)
-app.add(Button('center=x'), center='x', top=3)
-app.add(Button('center=x, left=5'), center='x', left=5, top=5)
-app.add(Button('center=x, right=5'), center='x', right=5, top=7)
-app.add(Button('center=xy'), center='xy')
-app.add(Button('fill=x'), fill='x', top=9)
-app.add(Button('fill=x, left=5'), fill='x', left=5, top=11)
-app.add(Button('fill=x, right=5'), fill='x', right=5, top=13)
-app.add(Button('fill=x, left=5, right=5'), fill='x', left=5, right=5, top=15)
-
-app.window_manager.sig_keypress.connect(lambda ev: app.stop())
-app.start()
--- a/demos/06_gridlayout.py	Mon Feb 16 21:17:43 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#!/usr/bin/env python3
-
-from tuikit.layouts.grid import GridLayout
-from tuikit.widgets.label import Label
-
-l1 = Label('Hello')
-l1.sizemin.update(10, 1)
-
-grid = GridLayout()
-grid.add(l1, 1, 1)
-grid.update(10, 10)
-
-print(grid._grid_size)
-print(grid._grid)
-
-for row in range(grid.row_count):
-    for col in range(grid.column_count):
-        w = grid.get_widget_at(row, col)
-        name = w.name if w else '--'
-        print(name.center(16), end='')
-    print()
-
-print(l1.pos)
-print(l1.size)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/10_application.py	Sat Feb 21 12:01:57 2015 +0100
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+
+from tuikit.core.application import Application
+from tuikit.widgets.label import Label
+from tuikit.widgets.button import Button
+from tuikit.widgets.textfield import TextField
+
+label = Label('Hello there!')
+button1 = Button()
+button2 = Button()
+field = TextField('text field')
+
+app = Application()
+app.root_window.add(label, 20, 10)
+app.root_window.add(button1, 20, 20)
+app.root_window.add(button2, 30, 20)
+app.root_window.add(field, 20, 30)
+app.root_window.focus_widget = field
+
+def on_keypress(ev):
+    if ev.keyname == 'escape':
+        app.stop()
+
+app.window_manager.sig_keypress.connect(on_keypress)
+
+app.start()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/11_fixedlayout.py	Sat Feb 21 12:01:57 2015 +0100
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+
+from tuikit.core.application import Application
+from tuikit.widgets.button import Button
+
+app = Application()
+app.add(Button('left=0'), left=0)
+app.add(Button('left=5'), left=5, top=2)
+app.add(Button('right=0'), right=0)
+app.add(Button('right=5'), right=5, top=2)
+app.add(Button('center=x'), center='x', top=3)
+app.add(Button('center=x, left=5'), center='x', left=5, top=5)
+app.add(Button('center=x, right=5'), center='x', right=5, top=7)
+app.add(Button('center=xy'), center='xy')
+app.add(Button('fill=x'), fill='x', top=9)
+app.add(Button('fill=x, left=5'), fill='x', left=5, top=11)
+app.add(Button('fill=x, right=5'), fill='x', right=5, top=13)
+app.add(Button('fill=x, left=5, right=5'), fill='x', left=5, right=5, top=15)
+
+app.window_manager.sig_keypress.connect(lambda ev: app.stop())
+app.start()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/12_gridlayout.py	Sat Feb 21 12:01:57 2015 +0100
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+
+from tuikit.layouts.grid import GridLayout
+from tuikit.widgets.label import Label
+
+l1 = Label('Hello')
+l1.sizemin.update(10, 1)
+
+grid = GridLayout()
+grid.add(l1, 1, 1)
+grid.update(10, 10)
+
+print(grid._grid_size)
+print(grid._grid)
+
+for row in range(grid.row_count):
+    for col in range(grid.column_count):
+        w = grid.get_widget_at(row, col)
+        name = w.name if w else '--'
+        print(name.center(16), end='')
+    print()
+
+print(l1.pos)
+print(l1.size)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/20_texteditor.py	Sat Feb 21 12:01:57 2015 +0100
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+
+import sys
+
+from tuikit.core.application import Application
+#from tuikit.scrollview import ScrollView
+from tuikit.widgets.textbox import TextBox
+
+
+class MyApplication(Application):
+
+    def __init__(self):
+        Application.__init__(self)
+        self.window_manager.sig_keypress.connect(self.on_wm_keypress)
+        #self.top.add_handler('keypress', self.on_top_keypress)
+
+        t = open(sys.argv[0]).read()
+        editbox = TextBox(t)
+
+        #scroll = ScrollView()
+        #scroll.add(editbox)
+
+        self.root_window.add(editbox)
+        #self.root_window.add(scroll, halign='fill', valign='fill')
+
+    def on_wm_keypress(self, ev):
+        if ev.keyname == 'escape':
+            self.stop()
+            return True
+
+if __name__ == '__main__':
+    app = MyApplication()
+    app.start()
+
--- a/tuikit/core/container.py	Mon Feb 16 21:17:43 2015 +0100
+++ b/tuikit/core/container.py	Sat Feb 21 12:01:57 2015 +0100
@@ -91,21 +91,20 @@
         if Widget.keypress_event(self, ev):
             return True
 
-    def mousedown(self, button, pos):
-        self.mouse_child = None
-        for child in reversed(self.children):
-            if pos in child.boundaries:
-                child.mousedown(button, pos - child.pos)
-                self.mouse_child = child
+    def mousedown_event(self, ev):
+        self.mouse_widget = None
+        for child in reversed(self._widgets):
+            if ev.pos in child.boundaries:
+                child.mousedown_event(ev.rebase(child.pos))
+                self.mouse_widget = child
 
-    def mouseup(self, button, pos):
-        if self.mouse_child:
-            self.mouse_child.mouseup(button, pos - self.mouse_child.pos)
+    def mouseup_event(self, ev):
+        if self.mouse_widget:
+            self.mouse_widget.mouseup_event(ev.rebase(self.mouse_widget.pos))
 
-    def mousemove(self, button, pos, relpos):
-        if self.mouse_child:
-            self.mouse_child.mousemove(button,
-                pos - self.mouse_child.pos, relpos)
+    def mousemove_event(self, ev):
+        if self.mouse_widget:
+            self.mouse_widget.mousemove_event(ev.rebase(self.mouse_widget.pos))
 
     ## focus ##
 
--- a/tuikit/core/events.py	Mon Feb 16 21:17:43 2015 +0100
+++ b/tuikit/core/events.py	Sat Feb 21 12:01:57 2015 +0100
@@ -51,3 +51,16 @@
                 res.append(mod)
         res.append(self.keyname or self.char)
         return sep.join(res)
+
+
+class MouseEvent(Event):
+
+    def __init__(self, name, button, pos, relpos=None):
+        Event.__init__(self, name,
+                       ('name', 'button', 'pos', 'relpos'),
+                       (name, button, pos, relpos))
+
+    def rebase(self, zero_pos):
+        """Return new MouseEvent with position rebased to zero_pos."""
+        pos = self.pos - zero_pos
+        return MouseEvent(self.name, self.button, pos, self.relpos)
--- a/tuikit/core/widget.py	Mon Feb 16 21:17:43 2015 +0100
+++ b/tuikit/core/widget.py	Sat Feb 21 12:01:57 2015 +0100
@@ -130,27 +130,24 @@
 
         Override to accept keyboard input.
 
-        Returns True if event was consumed.
+        Return True if event was consumed.
 
-        Call this implementation from inherited classes
-        if it does not consume the event.
+        Call parent implementation from inherited classes
+        when not consuming the event.
 
         """
         if self.sig_keypress(ev):
             return True
         self.log.debug('Not consumed: %s', ev)
 
-    def mousedown(self, button, pos):
-        self._log.debug('mousedown(btn=%r, pos=%r)',
-                        button, pos)
+    def mousedown_event(self, ev):
+        self.log.debug('Not consumed: %s', ev)
 
-    def mouseup(self, button, pos):
-        self._log.debug('mouseup(btn=%r, pos=%r)',
-                        button, pos)
+    def mouseup_event(self, ev):
+        self.log.debug('Not consumed: %s', ev)
 
-    def mousemove(self, button, pos, relpos):
-        self._log.debug('mousemove(btn=%r, pos=%r, relpos=%r)',
-                        button, pos, relpos)
+    def mousemove_event(self, ev):
+        self.log.debug('Not consumed: %s', ev)
 
     ## timeouts ##
 
--- a/tuikit/driver/cursesw.py	Mon Feb 16 21:17:43 2015 +0100
+++ b/tuikit/driver/cursesw.py	Sat Feb 21 12:01:57 2015 +0100
@@ -3,7 +3,7 @@
 import logging
 
 from tuikit.driver.driver import Driver
-from tuikit.core.events import ResizeEvent, KeypressEvent
+from tuikit.core.events import ResizeEvent, KeypressEvent, MouseEvent
 from tuikit.core.coords import Point
 
 
@@ -106,7 +106,12 @@
                 self.stdscr.addstr(y, x, ch)
             else:
                 raise TypeError('Integer or one-char string is required.')
-        except curses.error as e:
+        except curses.error:
+            if x == self.size.w - 1 and y == self.size.h - 1:
+                # Curses putch to lower-right corner gives error because
+                # scrolling is disabled and cursor cannot move to next char.
+                # Let's just ignore that for now.
+                return
             self._log.exception('putch(%r, %s, %s) error:' % (ch, x, y))
 
     def draw(self, buffer, x=0, y=0):
@@ -162,7 +167,7 @@
         timeout -- float, in seconds (None=infinite)
 
         Returns:
-            [('event', param1, ...), ...]
+            List of Event objects.
 
         """
         # Set timeout
@@ -200,21 +205,21 @@
         return res
 
     def _process_mouse(self):
-        out = []
+        res = []
         try:
             _id, x, y, _z, bstate = curses.getmouse()
         except curses.error:
-            return out
+            return res
 
         pos = Point(x, y)
         if bstate & curses.REPORT_MOUSE_POSITION:
             if self._mouse_last_pos != pos:
                 if self._mouse_last_pos:
                     relpos = pos - self._mouse_last_pos
-                    out += [('mousemove', 0, pos, relpos)]
+                    res.append(MouseEvent('mousemove', 0, pos, relpos))
                 self._mouse_last_pos = pos
 
-        # we are interested only in changes, not buttons already pressed before event
+        # We are interested only in changes, not buttons already pressed before event
         if self._mouse_last_bstate is not None:
             old = self._mouse_last_bstate
             new = bstate
@@ -224,23 +229,23 @@
             self._mouse_last_bstate = bstate
 
         if bstate & curses.BUTTON1_PRESSED:
-            out += [('mousedown', 1, pos)]
+            res.append(MouseEvent('mousedown', 1, pos))
         if bstate & curses.BUTTON2_PRESSED:
-            out += [('mousedown', 2, pos)]
+            res.append(MouseEvent('mousedown', 2, pos))
         if bstate & curses.BUTTON3_PRESSED:
-            out += [('mousedown', 3, pos)]
+            res.append(MouseEvent('mousedown', 3, pos))
         if bstate & curses.BUTTON1_RELEASED:
-            out += [('mouseup', 1, pos)]
+            res.append(MouseEvent('mouseup', 1, pos))
         if bstate & curses.BUTTON2_RELEASED:
-            out += [('mouseup', 2, pos)]
+            res.append(MouseEvent('mouseup', 2, pos))
         if bstate & curses.BUTTON3_RELEASED:
-            out += [('mouseup', 3, pos)]
+            res.append(MouseEvent('mouseup', 3, pos))
 
-        # reset last pos when pressed/released
-        if len(out) > 0 and out[-1][0] in ('mousedown', 'mouseup'):
+        # Reset last pos when pressed/released
+        if len(res) > 0 and res[-1].name in ('mousedown', 'mouseup'):
             self._mouse_last_pos = None
 
-        return out
+        return res
 
     def _split_keyname_mods(self, keyname):
         """Parse keynames in form "shift+tab", return (keyname, mod)."""
--- a/tuikit/driver/driver.py	Mon Feb 16 21:17:43 2015 +0100
+++ b/tuikit/driver/driver.py	Sat Feb 21 12:01:57 2015 +0100
@@ -86,6 +86,7 @@
 
     def __enter__(self):
         self.init()
+        return self
 
     def __exit__(self, exc_type, exc_val, exc_tb):
         self.close()
--- a/tuikit/scrollwindow.py	Mon Feb 16 21:17:43 2015 +0100
+++ b/tuikit/scrollwindow.py	Sat Feb 21 12:01:57 2015 +0100
@@ -14,4 +14,3 @@
     def add(self, widget, **kwargs):
         Window.add(self, widget, **kwargs)
         self._connect_child(widget)
-
--- a/tuikit/widgets/button.py	Mon Feb 16 21:17:43 2015 +0100
+++ b/tuikit/widgets/button.py	Sat Feb 21 12:01:57 2015 +0100
@@ -24,6 +24,7 @@
 
         self.color = 'default'
         self.color_active = 'default on red'
+        self.color_highlight = 'default on yellow'
         self.highlight = False
 
         self.sig_clicked = Signal()
@@ -47,6 +48,8 @@
         self.color_active = theme.button_active
 
     def _get_color(self):
+        if self.highlight:
+            return self.color_highlight
         if self.has_focus():
             return self.color_active
         return self.color
@@ -71,20 +74,18 @@
             # suffix
             buffer.puts(self.suffix, pos)
 
-    def on_mousedown(self, ev):
+    def mousedown_event(self, ev):
         self.highlight = True
-        self.redraw()
+        #self.redraw()
 
-    def on_mouseup(self, ev):
+    def mouseup_event(self, ev):
         self.highlight = False
-        self.redraw()
+        #self.redraw()
+        self.sig_clicked()
 
-        if self.enclose(ev.px, ev.py):
-            self.emit('click')
-
-    def on_keypress(self, ev):
+    def keypress_event(self, ev):
         if ev.keyname == 'enter':
-            self.emit('click')
+            self.sig_clicked()
 
     def _divide_padding(self, pad):
         # default is 'left'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/widgets/scrollbar.py	Sat Feb 21 12:01:57 2015 +0100
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+
+from tuikit.core.widget import Widget
+from tuikit.core.signal import Signal
+
+
+class Scrollbar(Widget):
+
+    """Abstract base class for scrollbars."""
+
+    def __init__(self):
+        Widget.__init__(self)
+
+        # Scrolling range is 0 .. _scroll_max
+        self._scroll_max = self._get_length() - 3
+        # Current position of scrollbar in scrolling range.
+        self._scroll_pos = 0
+        # Current position of thumb on scrollbar - used for draw.
+        self._thumb_pos = 0
+        # Auxilliary variable, True when user holds mouse on thumb.
+        self._dragging = False
+        # Auxilliary variable, 'up' or 'down' depending on last move direction.
+        self._move = None
+
+        #: delay before continuous scrolling when user holds mouse on scrollbar arrow
+        self.scroll_delay = 0.150
+        #: interval for continuous scrolling
+        self.scroll_interval = 0.030
+
+        # change event is emitted when user moves scrollbar (even programmatically)
+        self.sig_changed = Signal()
+
+    @property
+    def scroll_max(self):
+        """Maximum for scrolling position."""
+        return self._scroll_max
+
+    @scroll_max.setter
+    def scroll_max(self, value):
+        if value < 0:
+            value = 0
+        self._scroll_max = value
+        if self._scroll_pos > value:
+            self._scroll_pos = value
+            self.sig_changed()
+        self._update_thumb_pos()
+
+    @property
+    def scroll_pos(self):
+        """Scrolling position.
+
+        Integer number between 0 and 'max'.
+
+        """
+        return self._scroll_pos
+
+    @scroll_pos.setter
+    def scroll_pos(self, value):
+        if self._scroll_pos != value:
+            self._scroll_pos = value
+            self._update_thumb_pos()
+            self.sig_changed()
+
+    def move_up(self):
+        """Move scrolling position up/left."""
+        if self._scroll_pos > 0:
+            self.scroll_pos = self._scroll_pos - 1
+        self._move = 'up'
+
+    def move_down(self):
+        """Move scrolling position down/right."""
+        if self._scroll_pos < self._scroll_max:
+            self.scroll_pos = self._scroll_pos + 1
+        self._move = 'down'
+
+    def drag(self, mouse_pos):
+        """Scroll using mouse drag on thumb.
+
+        Args:
+            mouse_pos: new position of mouse, in range 0 .. self._get_length()
+
+        """
+        new_pos = int(round((mouse_pos - 1) / (self._get_length() - 3) * self._scroll_max))
+        if new_pos < 0:
+            new_pos = 0
+        if new_pos > self._scroll_max:
+            new_pos = self._scroll_max
+        if self._scroll_pos != new_pos:
+            self.scroll_pos = new_pos
+
+    def _get_length(self):
+        """Return length of scrollbar.
+
+        This will be widget height for vertical scrollbar,
+        width for horizontal scrollbar.
+
+        """
+        raise NotImplementedError()
+
+    def _update_thumb_pos(self):
+        """Update value of internal variable _thumb_pos."""
+        self._thumb_pos = 0
+        if self._scroll_max and self._scroll_pos <= self._scroll_max:
+            self._thumb_pos = int(round(self._scroll_pos / self._scroll_max * (self._get_length() - 3)))
+        #self.redraw()
+
+    def _continuous_scroll(self):
+        if self._move == 'up':
+            self.move_up()
+        if self._move == 'down':
+            self.move_down()
+        self.add_timeout(self.scroll_interval, self._continuous_scroll)
+
+
+class VScrollbar(Scrollbar):
+    def __init__(self):
+        Scrollbar.__init__(self)
+        self.sizereq.update(1, 20)
+
+    def draw(self, buffer):
+        Widget.draw(self, buffer)
+        ug = ev.driver.unigraph
+        ev.driver.pushcolor('normal')
+        ev.driver.putch(ev.x, ev.y, ug.get_char('sb_up'))
+        for i in range(1, self.height - 1):
+            ev.driver.putch(ev.x, ev.y + i, ug.get_char('sb_vtrack'))
+        ev.driver.putch(ev.x, ev.y + 1 + self._thumb_pos, ug.get_char('sb_thumb'))
+        ev.driver.putch(ev.x, ev.y + self.height - 1, ug.get_char('sb_down'))
+        ev.driver.popcolor()
+
+    def on_mousedown(self, ev):
+        self._dragging = False
+        self._move = None
+        # arrow buttons
+        if ev.wy == 0 or ev.wy == self.height - 1:
+            if ev.wy == 0:
+                self.move_up()
+            else:
+                self.move_down()
+            self.add_timeout(self.scroll_delay, self._continuous_scroll)
+            return
+        # thumb bar
+        if ev.wy == 1 + self._thumb_pos:
+            self._dragging = True
+            return
+
+    def on_mousemove(self, ev):
+        if self._dragging:
+            self.drag(ev.wy)
+
+    def on_mouseup(self, ev):
+        if self._dragging:
+            self.drag(ev.wy)
+            self._dragging = False
+            return
+        if self._move:
+            self.remove_timeout(self._continuous_scroll)
+            self._move = None
+            return
+
+    def _get_length(self):
+        return self.height
+
+
+class HScrollbar(Scrollbar):
+    def __init__(self):
+        Scrollbar.__init__(self)
+        self.sizereq.update(20, 1)
+
+    def on_draw(self, ev):
+        ug = ev.driver.unigraph
+        ev.driver.pushcolor('normal')
+        ev.driver.putch(ev.x, ev.y, ug.get_char('sb_left'))
+        for i in range(1, self.width - 1):
+            ev.driver.putch(ev.x + i, ev.y, ug.get_char('sb_htrack'))
+        ev.driver.putch(ev.x + 1 + self._thumb_pos, ev.y, ug.get_char('sb_thumb'))
+        ev.driver.putch(ev.x + self.width - 1, ev.y, ug.get_char('sb_right'))
+        ev.driver.popcolor()
+
+    def on_mousedown(self, ev):
+        self._dragging = False
+        self._move = None
+        # arrow buttons
+        if ev.wx == 0 or ev.wx == self.width - 1:
+            if ev.wx == 0:
+                self.move_up()
+            else:
+                self.move_down()
+            self.add_timeout(self.scroll_delay, self._continuous_scroll)
+            return
+        # thumb bar
+        if ev.wx == 1 + self._thumb_pos:
+            self._dragging = True
+            return
+
+    def on_mousemove(self, ev):
+        if self._dragging:
+            self.drag(ev.wx)
+
+    def on_mouseup(self, ev):
+        if self._dragging:
+            self.drag(ev.wx)
+            self._dragging = False
+            return
+        if self._move:
+            self.remove_timeout(self._continuous_scroll)
+            self._move = None
+            return
+
+    def _get_length(self):
+        return self.width
+
--- a/tuikit/widgets/textbox.py	Mon Feb 16 21:17:43 2015 +0100
+++ b/tuikit/widgets/textbox.py	Sat Feb 21 12:01:57 2015 +0100
@@ -84,11 +84,11 @@
             self.move_right()
             return True
 
-    def on_mousedown(self, ev):
-        y = ev.wy
-        x = min(ev.wx, len(self._lines[y]))
+    def mousedown_event(self, ev):
+        y = ev.pos.y
+        x = min(ev.pos.x, len(self._lines[y]))
         self._cursor.update(x=x, y=y)
-        self.redraw()
+        #self.redraw()
 
     def on_mousewheel(self, ev):
         if ev.button == 4: