Add Widget.posreq. Add OffsetLayout.
authorRadek Brich <radek.brich@devl.cz>
Sun, 15 Feb 2015 12:48:23 +0100
changeset 114 26c02bd94bd9
parent 113 6796adfdc7eb
child 115 b4ff7392003a
Add Widget.posreq. Add OffsetLayout.
demos/03_application.py
tuikit/core/container.py
tuikit/core/widget.py
tuikit/core/window.py
tuikit/layouts/anchor.py
tuikit/layouts/fixed.py
tuikit/layouts/grid.py
tuikit/layouts/layout.py
tuikit/layouts/offset.py
tuikit/widgets/scrollview.py
--- a/demos/03_application.py	Wed Sep 03 21:56:20 2014 +0200
+++ b/demos/03_application.py	Sun Feb 15 12:48:23 2015 +0100
@@ -8,15 +8,15 @@
 from tuikit.widgets.textfield import TextField
 
 label = Label('Hello there!')
-label.pos.update(20, 10)
+label.posreq.update(20, 10)
 
 button1 = Button()
-button1.pos.update(20, 20)
+button1.posreq.update(20, 20)
 button2 = Button()
-button2.pos.update(30, 20)
+button2.posreq.update(30, 20)
 
 field = TextField('text field')
-field.pos.update(20, 30)
+field.posreq.update(20, 30)
 
 app = Application()
 app.root_window.add(label)
--- a/tuikit/core/container.py	Wed Sep 03 21:56:20 2014 +0200
+++ b/tuikit/core/container.py	Sun Feb 15 12:48:23 2015 +0100
@@ -35,7 +35,7 @@
 
     def resize(self, w, h):
         Widget.resize(self, w, h)
-        self.layout.resize()
+        self.layout.update()
 
     def draw(self, buffer):
         """Draw child widgets."""
--- a/tuikit/core/widget.py	Wed Sep 03 21:56:20 2014 +0200
+++ b/tuikit/core/widget.py	Sun Feb 15 12:48:23 2015 +0100
@@ -29,6 +29,8 @@
         #: Actual size. Modified by layout manager.
         self._size = Size(10, 10)
 
+        #: Requested position. Layout manager will use this when placing the widget.
+        self.posreq = Point()
         #: Requested size. Layout manager will use this when placing the widget.
         self.sizereq = Size(1, 1)
         #: Minimal size of widget. Widget will never be sized smaller than this.
@@ -60,6 +62,10 @@
     def y(self):
         return self.pos.y
 
+    def move(self, x, y):
+        """Should be called only by layout manager."""
+        self.pos.update(x, y)
+
     @property
     def width(self):
         return self.size.w
@@ -73,6 +79,7 @@
         return self._size.immutable()
 
     def resize(self, w, h):
+        """Should be called only by layout manager."""
         self._size.update(w, h)
 
     @property
--- a/tuikit/core/window.py	Wed Sep 03 21:56:20 2014 +0200
+++ b/tuikit/core/window.py	Sun Feb 15 12:48:23 2015 +0100
@@ -44,8 +44,8 @@
     def redraw(self):
         self.buffer.reset_origin()
         Container.draw(self, self.buffer)
-        self.buffer.puts('{0.w} {0.h}'.format(self.size), 10, 5)
-        self.buffer.frame()
+#        self.buffer.puts('{0.w} {0.h}'.format(self.size), 10, 5)
+#        self.buffer.frame()
 
     def draw(self, buffer):
         """Draw this window into `buffer`."""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/layouts/anchor.py	Sun Feb 15 12:48:23 2015 +0100
@@ -0,0 +1,83 @@
+from .layout import Layout
+
+
+class AnchorLayout(Layout):
+
+    """Anchor widgets to borders of container."""
+
+    def __init__(self):
+        Layout.__init__(self)
+        self.register_hints(
+            'halign', make_select('left', 'right', 'fill', 'center'),
+            'valign', make_select('top', 'bottom', 'fill', 'center'),
+            'margin', Borders,
+            )
+
+    def on_resize(self, ev):
+        for child in self._get_children():
+            reqw = max(child.sizereq.w, child.sizemin.w)
+            reqh = max(child.sizereq.h, child.sizemin.h)
+            ha = child.hint_value('halign')
+            va = child.hint_value('valign')
+            margin = child.hint_value('margin')
+            if ha == 'left':
+                x = margin.l
+                w = reqw
+            if ha == 'right':
+                x = self.width - margin.r - reqw
+                w = reqw
+            if ha == 'fill':
+                x = margin.l
+                w = self.width - margin.l - margin.r
+            if ha == 'center':
+                x = (self.width - reqw) // 2
+                w = reqw
+            if va == 'top':
+                y = margin.t
+                h = reqh
+            if va == 'bottom':
+                y = self.height - margin.b - reqh
+                h = reqh
+            if va == 'fill':
+                y = margin.t
+                h = self.height - margin.t - margin.b
+            if va == 'center':
+                y = (self.height - reqh) // 2
+                h = reqh
+            child._pos.update(x=x, y=y)
+            child._size.update(w=w, h=h)
+            child._view_size.update(w=w, h=h)
+
+    def move_child(self, child, x=None, y=None):
+        """Move child inside container by adjusting its margin.
+
+        Operation is supported only for one-side anchors:
+            left, right, top, bottom
+        No move on axis where align is set to
+            center, fill
+
+        """
+        if not child in self.children:
+            raise ValueError('AnchorLayout.move(): Cannot move foreign child.')
+        margin = child.hint_value('margin')
+        newx = None
+        if x is not None:
+            ha = child.hint_value('halign')
+            ofsx = x - child.x
+            if ha == 'left':
+                margin.l += ofsx
+                newx = margin.l
+            elif ha == 'right':
+                margin.r -= ofsx
+                newx = self.width - margin.r - child.sizereq.w
+        newy = None
+        if y is not None:
+            va = child.hint_value('valign')
+            ofsy = y - child.y
+            if va == 'top':
+                margin.t += ofsy
+                newy = margin.t
+            elif va == 'bottom':
+                margin.b -= ofsy
+                newy = self.height - margin.b - child.sizereq.h
+        child._pos.update(x=newx, y=newy)
--- a/tuikit/layouts/fixed.py	Wed Sep 03 21:56:20 2014 +0200
+++ b/tuikit/layouts/fixed.py	Sun Feb 15 12:48:23 2015 +0100
@@ -3,6 +3,7 @@
 
 class FixedLayout(Layout):
 
-    def resize(self):
+    def update(self):
         for widget in self._managed_widgets:
             widget.resize(*widget.sizereq)
+            widget.pos.update(*widget.posreq)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/layouts/grid.py	Sun Feb 15 12:48:23 2015 +0100
@@ -0,0 +1,24 @@
+from .layout import Layout
+
+
+class GridLayout(Layout):
+
+    """Lay out widgets in a grid.
+
+    Grid size is determined by each widget's specified column and row.
+
+    """
+
+    def __init__(self):
+        Layout.__init__(self)
+
+    def add(self, widget, row, column):
+        Layout.add(self, widget)
+
+
+
+
+    def update(self):
+        for widget in self._managed_widgets:
+            widget.resize(*widget.sizereq)
+            widget.pos.update(*(widget.posreq + self.offset))
--- a/tuikit/layouts/layout.py	Wed Sep 03 21:56:20 2014 +0200
+++ b/tuikit/layouts/layout.py	Sun Feb 15 12:48:23 2015 +0100
@@ -6,7 +6,7 @@
     def add(self, widget):
         self._managed_widgets.append(widget)
 
-    def resize(self):
+    def update(self):
+        """Rearrange managed widgets."""
         pass
 
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/layouts/offset.py	Sun Feb 15 12:48:23 2015 +0100
@@ -0,0 +1,21 @@
+from .layout import Layout
+from tuikit.core.coords import Point
+
+
+class OffsetLayout(Layout):
+
+    """Offsets widget position. Used by Viewport and ScrollView."""
+
+    def __init__(self):
+        Layout.__init__(self)
+        self._offset = Point()
+
+    @property
+    def offset(self):
+        """Offset of child widgets."""
+        return self._offset
+
+    def update(self):
+        for widget in self._managed_widgets:
+            widget.resize(*widget.sizereq)
+            widget.pos.update(*(widget.posreq + self.offset))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/widgets/scrollview.py	Sun Feb 15 12:48:23 2015 +0100
@@ -0,0 +1,134 @@
+from tuikit.core.container import Container
+from tuikit.layouts.offset import OffsetLayout
+
+#from tuikit.layout import AnchorLayout, OffsetLayout
+#from tuikit.scrollbar import VScrollbar, HScrollbar
+#from tuikit.common import Borders
+
+
+class Viewport(Container):
+
+    """Viewport for partial view on widgets.
+
+    Viewport is special type of Container, which shows partial view
+    on its managed widgets. This view can be moved to show different part
+    and thus allow "scrolling" over the content. Complete view with scrollbars
+    is implemented in ScrollView.
+
+    """
+
+    def __init__(self):
+        Container.__init__(self, OffsetLayout)
+
+
+class Scrollview(Container):
+
+    def __init__(self):
+        Container.__init__(self)
+
+
+class Scrolling:
+    def __init__(self):
+        self.vscroll = VScrollbar()
+        self.vscroll.add_handler('change', self._on_vscroll_change)
+        AnchorLayout.add(self, self.vscroll, halign='right', valign='fill')
+
+        self.hscroll = HScrollbar()
+        self.hscroll.add_handler('change', self._on_hscroll_change)
+        AnchorLayout.add(self, self.hscroll, halign='fill', valign='bottom')
+
+    def on_resize(self, ev):
+        self._update_scroll_max()
+
+    def _connect_child(self, widget):
+        widget.add_handler('sizereq', self._on_child_sizereq)
+        widget.add_handler('spotmove', self._on_child_spotmove)
+        widget.add_handler('scrollreq', self._on_child_scrollreq)
+
+    def _on_child_sizereq(self, ev):
+        self._update_scroll_max()
+
+    def _on_child_spotmove(self, ev):
+        child = ev.originator
+        # x
+        spot_x = child.x - self._inner.offset.x + child.spot.x
+        if spot_x < self.hscroll.scroll_pos:
+            self.hscroll.scroll_pos = spot_x
+        if spot_x > (self._inner.width - 1) + self.hscroll.scroll_pos:
+            self.hscroll.scroll_pos = spot_x - (self._inner.width - 1)
+        # y
+        spot_y = child.y - self._inner.offset.y + child.spot.y
+        if spot_y < self.vscroll.scroll_pos:
+            self.vscroll.scroll_pos = spot_y
+        if spot_y > (self._inner.height - 1) + self.vscroll.scroll_pos:
+            self.vscroll.scroll_pos = spot_y - (self._inner.height - 1)
+
+    def _on_child_scrollreq(self, ev):
+        new_scroll_pos = self.vscroll.scroll_pos + ev.data
+        if new_scroll_pos > self.vscroll.scroll_max:
+            self.vscroll.scroll_pos = self.vscroll.scroll_max
+        elif new_scroll_pos < 0:
+            self.vscroll.scroll_pos = 0
+        else:
+            self.vscroll.scroll_pos = new_scroll_pos
+
+    def _on_vscroll_change(self, ev):
+        self._inner.offset.y = - self.vscroll.scroll_pos
+
+    def _on_hscroll_change(self, ev):
+        self._inner.offset.x = - self.hscroll.scroll_pos
+
+    def _update_scroll_max(self):
+        max_width = 0
+        max_height = 0
+        for child in self._inner.children:
+            posx, posy = child.hint_value('position')
+            child_width = posx + child.sizereq.w
+            if child_width > max_width:
+                max_width = child_width
+            child_height = posy + child.sizereq.h
+            if child_height > max_height:
+                max_height = child_height
+        max_width += 1
+        self.hscroll.scroll_max = max_width - self._inner.width
+        self.vscroll.scroll_max = max_height - self._inner.height
+        hscroll_hide = max_width < self._inner.width
+        vscroll_hide = max_height < self._inner.height
+        self._toggle_scrollers(hscroll_hide, vscroll_hide)
+
+    def _toggle_scrollers(self, hscroll_hide, vscroll_hide):
+        if hscroll_hide:
+            self.hscroll.hide()
+        else:
+            self.hscroll.show()
+        if vscroll_hide:
+            self.vscroll.hide()
+        else:
+            self.vscroll.show()
+        # self._inner.need_resize()
+
+
+class ScrollView(OffsetView, Scrolling):
+    """Scrolling view
+
+    Shows scrollbars when needed.
+    Scrolling area is determined according to child widget's size request.
+
+    """
+
+    def __init__(self):
+        OffsetView.__init__(self)
+        Scrolling.__init__(self)
+
+    def add(self, widget, **kwargs):
+        OffsetView.add(self, widget, **kwargs)
+        self._connect_child(widget)
+
+    def _toggle_scrollers(self, hscroll_hide, vscroll_hide):
+        bpad = int(not hscroll_hide)
+        self.vscroll.update_hint('margin', b=bpad)
+        rpad = int(not vscroll_hide)
+        self.hscroll.update_hint('margin', r=rpad)
+        self._inner.update_hint('margin', b=bpad, r=rpad)
+        Scrolling._toggle_scrollers(self, hscroll_hide, vscroll_hide)
+