Add Widget.posreq. Add OffsetLayout.
--- 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)
+