Update FixedLayout. Add demo launcher.
--- a/.hgignore Sun Feb 15 17:50:24 2015 +0100
+++ b/.hgignore Mon Feb 16 21:17:43 2015 +0100
@@ -2,13 +2,12 @@
^tuikit/.*\.pyc$
^tuikit\.conf$
^docs/_build
-tuikit\.log
+^tuikit\.log$
^build
^sdlterm/build
^sdlterm/font
^sdlterm/cython/sdlterm.cpp$
^sdlterm/test_
-^sdlterm\..*\.so$
^(.*/)?\.c?project$
^(.*/)?\.pydevproject$
^\.settings
--- a/INSPIRATION Sun Feb 15 17:50:24 2015 +0100
+++ b/INSPIRATION Mon Feb 16 21:17:43 2015 +0100
@@ -22,8 +22,8 @@
http://developer.gnome.org/gtk3/stable/
http://python-gtk-3-tutorial.readthedocs.org/en/latest/layout.html
-Anchor Layout in QML:
- http://harmattan-dev.nokia.com/docs/library/html/qt4/qml-anchor-layout.html
+Qt Layout:
+ http://qt-project.org/doc/qt-4.8/layout.html
PDCurses:
http://pdcurses.sourceforge.net/doc/PDCurses.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile Mon Feb 16 21:17:43 2015 +0100
@@ -0,0 +1,4 @@
+.PHONY: build_ext
+
+build_ext:
+ python3 setup.py build_ext --inplace
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/demo.py Mon Feb 16 21:17:43 2015 +0100
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+
+from demos.base import setup
+import sys
+import os
+import re
+
+
+def list_demos():
+ return sorted(fname for fname in os.listdir('demos')
+ if re.match(r'[0-9]+_.*\.py', fname))
+
+
+def print_usage(names):
+ print("Usage: %s <name>\n" % sys.argv[0])
+ print("Where <name> is one of (or part of):")
+ print('\n'.join([' '+name for name in names]))
+
+
+if __name__ == '__main__':
+ setup()
+ names = list_demos()
+
+ if len(sys.argv) != 2:
+ print_usage(names)
+ sys.exit(0)
+
+ arg = sys.argv[1]
+ name = None
+ for name in names:
+ if arg in name:
+ break
+ else:
+ print("Demo '%s' not found." % arg)
+ sys.exit(1)
+
+ print("Executing %s..." % name)
+ with open('demos/' + name, 'r') as f:
+ exec(f.read())
--- a/demos/01_buffer.py Sun Feb 15 17:50:24 2015 +0100
+++ b/demos/01_buffer.py Mon Feb 16 21:17:43 2015 +0100
@@ -1,7 +1,5 @@
#!/usr/bin/env python3
-import demobase
-
from tuikit.core.buffer import Buffer
from tuikit.driver.driver import Driver
--- a/demos/02_curses.py Sun Feb 15 17:50:24 2015 +0100
+++ b/demos/02_curses.py Mon Feb 16 21:17:43 2015 +0100
@@ -1,7 +1,5 @@
#!/usr/bin/env python3
-import demobase
-
from tuikit.core.buffer import Buffer
from tuikit.driver.curses import CursesDriver
--- a/demos/03_application.py Sun Feb 15 17:50:24 2015 +0100
+++ b/demos/03_application.py Mon Feb 16 21:17:43 2015 +0100
@@ -1,28 +1,20 @@
#!/usr/bin/env python3
-import demobase
-
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!')
-label.posreq.update(20, 10)
-
button1 = Button()
-button1.posreq.update(20, 20)
button2 = Button()
-button2.posreq.update(30, 20)
-
field = TextField('text field')
-field.posreq.update(20, 30)
app = Application()
-app.root_window.add(label)
-app.root_window.add(button1)
-app.root_window.add(button2)
-app.root_window.add(field)
+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):
--- a/demos/04_texteditor.py Sun Feb 15 17:50:24 2015 +0100
+++ b/demos/04_texteditor.py Mon Feb 16 21:17:43 2015 +0100
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-import demobase
+import sys
from tuikit.core.application import Application
#from tuikit.scrollview import ScrollView
@@ -14,7 +14,7 @@
self.window_manager.sig_keypress.connect(self.on_wm_keypress)
#self.top.add_handler('keypress', self.on_top_keypress)
- t = open('../tuikit/core/widget.py').read()
+ t = open(sys.argv[0]).read()
editbox = TextBox(t)
#scroll = ScrollView()
@@ -28,7 +28,6 @@
self.stop()
return True
-
if __name__ == '__main__':
app = MyApplication()
app.start()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/05_fixedlayout.py Mon Feb 16 21:17:43 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()
--- a/demos/05_gridlayout.py Sun Feb 15 17:50:24 2015 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-#!/usr/bin/env python3
-
-import demobase
-
-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/06_gridlayout.py Mon Feb 16 21:17:43 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/base.py Mon Feb 16 21:17:43 2015 +0100
@@ -0,0 +1,22 @@
+# Set system locale (needed for ncurses)
+import locale
+locale.setlocale(locale.LC_ALL, '')
+
+# Setup logging
+import logging
+logger = logging.getLogger('tuikit')
+logger.setLevel(logging.DEBUG)
+handler = logging.FileHandler(filename='tuikit.log')
+handler.setLevel(logging.DEBUG)
+formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
+handler.setFormatter(formatter)
+logger.addHandler(handler)
+
+# Escape key code is also used for escape sequences. After escape code,
+# terminal waits for rest of sequence. This delay is 1 second by default.
+# Let's hope that our terminal is fast enough to handle the sequences in 200ms.
+import os
+os.environ['ESCDELAY'] = '200'
+
+def setup():
+ pass
--- a/demos/demobase.py Sun Feb 15 17:50:24 2015 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-# Path to root directory containing tuikit package
-import sys
-sys.path.append('..')
-
-# Set system locale (needed for ncurses)
-import locale
-locale.setlocale(locale.LC_ALL, '')
-
-# Setup logging
-import logging
-logger = logging.getLogger('tuikit')
-logger.setLevel(logging.DEBUG)
-handler = logging.FileHandler(filename='tuikit.log')
-handler.setLevel(logging.DEBUG)
-formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
-handler.setFormatter(formatter)
-logger.addHandler(handler)
-
-# Escape key code is also used for escape sequences. After escape code,
-# terminal waits for rest of sequence. This delay is 1 second by default.
-# Let's hope that our terminal is fast enough to handle the sequences in 200ms.
-import os
-os.environ['ESCDELAY'] = '200'
--- a/tuikit/core/application.py Sun Feb 15 17:50:24 2015 +0100
+++ b/tuikit/core/application.py Mon Feb 16 21:17:43 2015 +0100
@@ -29,6 +29,8 @@
self.set_driver(driver)
self.set_theme(default_theme)
self.window_manager.add(self.root_window)
+ # Convenience method
+ self.add = self.root_window.add
def set_driver(self, driver_name):
"""Select driver to be used for rendering and input.
--- a/tuikit/core/widget.py Sun Feb 15 17:50:24 2015 +0100
+++ b/tuikit/core/widget.py Mon Feb 16 21:17:43 2015 +0100
@@ -29,8 +29,6 @@
#: 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.
--- a/tuikit/driver/driver.py Sun Feb 15 17:50:24 2015 +0100
+++ b/tuikit/driver/driver.py Mon Feb 16 21:17:43 2015 +0100
@@ -27,12 +27,6 @@
"""Clean up the screen etc."""
pass
- def __enter__(self):
- self.init()
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self.close()
-
## drawing ##
def erase(self):
@@ -56,14 +50,6 @@
"""Set attribute to be used for subsequent draw operations."""
pass
- def _parse_attr_desc(self, attr_desc):
- parts = attr_desc.split(',')
- fgbg = parts[0].split(' on ', 1)
- fg = fgbg[0].strip().lower()
- bg = fgbg[1:] and fgbg[1].strip().lower() or 'default'
- attrs = (part.strip().lower() for part in parts[1:])
- return fg, bg, attrs
-
## cursor ##
def showcursor(self, x, y):
@@ -84,3 +70,22 @@
"""
return []
+
+ ## convenience implementations ##
+
+ def _parse_attr_desc(self, attr_desc):
+ """Convenience implementation of attribute parsing. Not part of API."""
+ parts = attr_desc.split(',')
+ fgbg = parts[0].split(' on ', 1)
+ fg = fgbg[0].strip().lower()
+ bg = fgbg[1:] and fgbg[1].strip().lower() or 'default'
+ attrs = (part.strip().lower() for part in parts[1:])
+ return fg, bg, attrs
+
+ ## with statement support ##
+
+ def __enter__(self):
+ self.init()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.close()
--- a/tuikit/layouts/fixed.py Sun Feb 15 17:50:24 2015 +0100
+++ b/tuikit/layouts/fixed.py Mon Feb 16 21:17:43 2015 +0100
@@ -3,38 +3,74 @@
class FixedLayout(Layout):
- """Widgets are placed on fixed position as specified in `sizereq`.
+ """Widgets are placed on fixed position as specified in hints.
- Align hints (kwargs to :meth:`add`):
- * halign = 'left' (default) | 'right' | 'fill' | 'center'
- * valign = 'top' (default) | 'bottom' | 'fill' | 'center'
+ Position can be relative to any side or center of parent widget.
"""
def __init__(self):
Layout.__init__(self)
- self._widget_hints = {} # Widget : (halign, valign)
+ self._widget_hints = {} # Widget : (left, top, right, bottom, xrel, yrel)
+
+ def add(self, widget, left=None, top=None, right=None, bottom=None,
+ center=None, fill=None):
+ """Add widget to layout.
- def add(self, widget, *args, **kwargs):
+ Place hints:
+ * left, right, top, bottom = None | <num> ; <num> >= 0
+ - Fix Widget position to parent sides.
+ - If both left and right (or top and bottom) are set, the widget
+ will be stretched to fill full area minus specified space.
+ * center, fill = None | 'x' | 'y' | 'xy'
+ - Center widget in x, y or both axes.
+ - Fill is shortcut for setting both positions in same axis.
+
+ """
Layout.add(self, widget)
- assert len(args) == 0, \
- "FixedLayout does not support positional hint args: %s" % args
- assert all(key in ('halign', 'valign') for key in kwargs.keys()), \
- "Unsupported hints: %s" % tuple(kwargs.keys())
- halign = kwargs.get('halign', 'left')
- valign = kwargs.get('valign', 'top')
- self._widget_hints[widget] = (halign, valign)
+ # Internally, coordinate relation is marked as:
+ # '+': from left or top
+ # '-': from right or bottom
+ # 'C': from center
+ # 'F': from both sides (fill)
+ xrel, yrel = '+', '+'
+ fill, center = fill or '', center or ''
+ if left is None and right is not None:
+ xrel = '-'
+ if top is None and bottom is not None:
+ yrel = '-'
+ if 'x' in center:
+ xrel = 'C'
+ if 'y' in center:
+ yrel = 'C'
+ if 'x' in fill:
+ xrel = 'F'
+ if 'y' in fill:
+ yrel = 'F'
+ self._widget_hints[widget] = (left or 0, top or 0,
+ right or 0, bottom or 0,
+ xrel, yrel)
def update(self, w, h):
for widget in self._managed_widgets:
- halign, valign = self._widget_hints[widget]
- px, py = widget.posreq
+ left, top, right, bottom, xrel, yrel = self._widget_hints[widget]
sw, sh = widget.sizereq
- if halign == 'right': px = w - px
- if valign == 'bottom': py = h - py
- if halign == 'fill': px = 0; sw = w
- if valign == 'fill': py = 0; sh = h
- if halign == 'center': px = (w - sw) // 2
- if valign == 'center': py = (h - sh) // 2
+ ox, oy = 0, 0 # origin
+ if xrel == '-':
+ ox = w - sw
+ if yrel == '-':
+ oy = h - sh
+ if xrel == 'C':
+ ox = (w - sw) // 2
+ if yrel == 'C':
+ oy = (h - sh) // 2
+ px = ox + left - right
+ py = oy + top - bottom
+ if xrel == 'F':
+ px = left
+ sw = w - left - right
+ if yrel == 'F':
+ py = top
+ sh = h - top - bottom
widget.resize(sw, sh)
widget.pos.update(px, py)
--- a/tuikit/layouts/offset.py Sun Feb 15 17:50:24 2015 +0100
+++ b/tuikit/layouts/offset.py Mon Feb 16 21:17:43 2015 +0100
@@ -9,13 +9,18 @@
def __init__(self):
Layout.__init__(self)
self._offset = Point()
+ self._widget_pos = {}
@property
def offset(self):
"""Offset of child widgets."""
return self._offset
+ def add(self, widget, x=0, y=0):
+ Layout.add(self, widget)
+ self._widget_pos[widget] = (x, y)
+
def update(self, _w, _h):
for widget in self._managed_widgets:
widget.resize(*widget.sizereq)
- widget.pos.update(*(widget.posreq + self.offset))
+ widget.pos.update(*(self.offset + self._widget_pos[widget]))
--- a/tuikit/widgets/scrollview.py Sun Feb 15 17:50:24 2015 +0100
+++ b/tuikit/widgets/scrollview.py Mon Feb 16 21:17:43 2015 +0100
@@ -21,11 +21,6 @@
Container.__init__(self, OffsetLayout)
-class Scrollview(Container):
-
- def __init__(self):
- Container.__init__(self)
-
class Scrolling:
def __init__(self):