# HG changeset patch # User Radek Brich # Date 1424000903 -3600 # Node ID 26c02bd94bd98e553901a2bf73b25020392b6801 # Parent 6796adfdc7eb95ac61b4816c0da7613530fdb67f Add Widget.posreq. Add OffsetLayout. diff -r 6796adfdc7eb -r 26c02bd94bd9 demos/03_application.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) diff -r 6796adfdc7eb -r 26c02bd94bd9 tuikit/core/container.py --- 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.""" diff -r 6796adfdc7eb -r 26c02bd94bd9 tuikit/core/widget.py --- 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 diff -r 6796adfdc7eb -r 26c02bd94bd9 tuikit/core/window.py --- 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`.""" diff -r 6796adfdc7eb -r 26c02bd94bd9 tuikit/layouts/anchor.py --- /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) diff -r 6796adfdc7eb -r 26c02bd94bd9 tuikit/layouts/fixed.py --- 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) diff -r 6796adfdc7eb -r 26c02bd94bd9 tuikit/layouts/grid.py --- /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)) diff -r 6796adfdc7eb -r 26c02bd94bd9 tuikit/layouts/layout.py --- 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 - diff -r 6796adfdc7eb -r 26c02bd94bd9 tuikit/layouts/offset.py --- /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)) diff -r 6796adfdc7eb -r 26c02bd94bd9 tuikit/widgets/scrollview.py --- /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) +