Hi all

With all the activity around the design of pyglet user interfaces, I
tried to code a simple widget placement algorithm inspired by GTK hbox/
vbox. Currently, the class only handles horizontal placement but
vertical placement is straightforward.

Screenshot : http://www.loria.fr/~rougier/tmp/hbox.png
Code: http://www.loria.fr/~rougier/tmp/hbox.py

On the screenshot, there are 15 different placement using the exact
same 3 widgets.

The main idea is to use spacers (a dumb widget) that can be inserted
anywhere between widgets. Those spacers can be expandable or have a
fixed size. The same goes for any widget and here you have plenty of
placement freedom with a single line, both fully automatic placement &
sizing and/or manual placement & sizing with pixel precision.

Using both hbox and vbox should allow for easy placement of any kind
of widgets.


Nicolas


For the archive, I paste code below:



#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
------------------------------------------------------------------------------
# Copyright (c) 2009 Nicolas Rougier
# All rights reserved.
#
# Redistribution  and  use  in  source   and  binary  forms,  with
or  without
# modification, are permitted provided that the following conditions
are met:
#
#  * Redistributions  of source code  must retain  the above
copyright notice,
#    this list of conditions and the following disclaimer.
#  * Redistributions in binary form  must reproduce the above
copyright notice,
#    this list of conditions and  the following disclaimer in the
documentation
#    and/or other materials provided with the distribution.
#  * Neither the name  of pyglet nor the names of its  contributors
may be used
#    to endorse or promote products derived from this software without
specific
#    prior written permission.
#
# THIS SOFTWARE IS  PROVIDED BY THE COPYRIGHT HOLDERS  AND
CONTRIBUTORS "AS IS"
# AND ANY  EXPRESS OR  IMPLIED WARRANTIES, INCLUDING,  BUT NOT
LIMITED  TO, THE
# IMPLIED WARRANTIES  OF MERCHANTABILITY AND  FITNESS FOR A
PARTICULAR PURPOSE
# ARE  DISCLAIMED. IN NO  EVENT SHALL  THE COPYRIGHT  OWNER OR
CONTRIBUTORS BE
# LIABLE  FOR   ANY  DIRECT,  INDIRECT,  INCIDENTAL,   SPECIAL,
EXEMPLARY,  OR
# CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT   NOT  LIMITED  TO,
PROCUREMENT  OF
# SUBSTITUTE  GOODS OR SERVICES;  LOSS OF  USE, DATA,  OR PROFITS;  OR
BUSINESS
# INTERRUPTION)  HOWEVER CAUSED  AND ON  ANY  THEORY OF  LIABILITY,
WHETHER  IN
# CONTRACT,  STRICT  LIABILITY, OR  TORT  (INCLUDING  NEGLIGENCE OR
OTHERWISE)
# ARISING IN ANY  WAY OUT OF THE USE  OF THIS SOFTWARE, EVEN IF
ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
-----------------------------------------------------------------------------
import random
from operator import add


class Widget(object):
    def __init__(self, position=(0,0), size=(-1,-1)):
        self.position        = list(position)
        self.size_request    = list(size)
        self.size_allocation = list(size)

    def hexpand(self):
        return self.size_request[0] == -1

    def draw(self, x0=0, y0=0):
        x,y = x0+self.position[0]+.45, y0+self.position[1]+.45
        w,h = self.size_allocation
        gl.glBegin(gl.GL_LINE_LOOP)
        gl.glVertex2f(x,   y)
        gl.glVertex2f(x+w, y)
        gl.glVertex2f(x+w, y+h-1)
        gl.glVertex2f(x,   y+h-1)
        gl.glEnd()


class Spacer(Widget):
    ''' '''
    def __init__(self, position=(0,0), size=(-1,-1)):
        Widget.__init__(self, position, size)

    def draw(self, x0=0, y0=0):
        pass



class HBox(Widget):
    ''' '''
    def __init__(self, position=(0,0), size=(-1,-1), homogeneous=True,
spacing=3):
        ''' '''
        Widget.__init__(self, position, size)
        self.homogeneous = homogeneous
        self.spacing = spacing
        self.children = []


    def __call__(self,*args):
        self.children = []
        for child in args:
            if isinstance(child,Widget):
                self.children.append(child)
            if isinstance(child,tuple):
                child,size = child
                if isinstance(child,Widget):
                    child.size_request[0] = size
                    self.children.append(child)
            elif isinstance(child,int):
                self.children.append(Spacer(size=(child,-1)))
            elif isinstance(child,str):
                self.children.append(Spacer(size=(-1,-1)))
        self.update_layout()
        return self

    def update_layout(self):
        ''' '''

        width = self.size_allocation[0]

        # Minimum widget width (but spacers)
        minimum_width = 10

        # Reset size allocation for all children
        for child in self.children:
            child.size_allocation[0] = 0

        # All children
        all_children = self.children
        # Expandable children
        children_= [child for child in self.children if not isinstance
(child,Spacer) and child.hexpand()]
        # Non-expandable spacers
        spacers = [child for child in self.children if isinstance
(child,Spacer) and not child.hexpand()]
        # Non-expandable children
        children = [child for child in self.children if not isinstance
(child,Spacer) and not child.hexpand()]

        # Expandable spacers
        # If we have expandable children, they will eat-up remaining
space, no need of expandable spacers
        if len(children_) > 0:
            spacers_ = []
            all_children = [child for child in self.children if not
isinstance(child,Spacer) or not child.hexpand()]
        else:
            spacers_= [child for child in self.children if isinstance
(child,Spacer) and child.hexpand()]


        # Makes non-expandable children to have same size and
allocates
        # remaining space equally among expandable children
        if self.homogeneous:
            # Computes common size for non-expandable children
            free_space   = width - (len(all_children)-1)*self.spacing
            free_space  -= reduce(add,[child.size_request[0] for child
in spacers] or [0])
            free_space  -= len(children_)*minimum_width
            common_size = max([child.size_request[0] for child in
children] or [0])
            if common_size*len(children) > free_space:
                common_size = free_space/len(children)
            # Assign common size to non-expandable children
            for child in children:
                child.size_allocation[0] = common_size
        else:
            # Computes size for non-expandable children
            free_space   = width - (len(all_children)-1)*self.spacing
            free_space  -= reduce(add,[child.size_request[0] for child
in spacers] or [0])
            children_size = max([child.size_request[0] for child in
children] or [0])
            scale = 1
            if children_size > free_space:
                scale = (free_space)/float(children_size)
            for child in children:
                child.size_allocation[0] = int(child.size_request[0]
*scale)

        # Assign fixed size to non-expandable spacers
        for child in spacers:
            child.size_allocation[0] = child.size_request[0]
        # Assign remaining space to expandable children if any or
expandable spacers
        free_space = width - (len(all_children)-1)*self.spacing
        free_space  -= reduce(add,[child.size_allocation[0] for child
in spacers] or [0])
        free_space  -= reduce(add,[child.size_allocation[0] for child
in children] or [0])
        if len(children_):
            for child in children_:
                child.size_allocation[0] = int(free_space/len
(children_))
        elif len(spacers_):
            for child in spacers_:
                child.size_allocation[0] = int(free_space/len
(spacers_))

        # Assign position to children
        x = 0
        for child in all_children:
            child.position[0] = x
            x += child.size_allocation[0] + self.spacing


    def draw(self, x0=0, y0=0):
        x,y = x0+self.position[0], y0+self.position[1]
        w,h = self.size_allocation
        gl.glColor4f(0.75, 0.75, 0.75, 1.00)
        gl.glBegin(gl.GL_QUADS)
        gl.glVertex2i(x,   y)
        gl.glVertex2i(x+w, y)
        gl.glVertex2i(x+w, y+h)
        gl.glVertex2i(x,   y+h)
        gl.glEnd()
        gl.glColor4f(0.00, 0.00, 0.00, 1.00)
        for child in self.children:
            child.draw(x0, y0)



#
-----------------------------------------------------------------------------
if __name__ == '__main__':
    import pyglet
    import pyglet.gl as gl

    window = pyglet.window.Window(width=700, height=460)

    def text(text, x, y):
        label = pyglet.text.Label(text,x=x, y=y,
                                  anchor_x = 'left',
                                  anchor_y = 'center',
                                  font_size=10, color=(0,0,0,255))
        label.draw()

    @window.event
    def on_draw():
        gl.glClearColor(1,1,1,1)
        window.clear()

        b1 = Widget(size=(100,20))
        b2 = Widget(size=(50,20))
        b3 = Widget(size=(30,20))

        x = 10
        y = 10
        box = HBox((0,0),(400,20),False,3)         # Non homogeneous
        box(' ',b1,b2,b3).draw(x, y+0*30),         text('Right
aligned',       x+410, y+0*30+10)
        box(b1,b2,b3,' ').draw(x, y+1*30),         text('Left
aligned',        x+410, y+1*30+10)
        box(' ',b1,b2,b3,' ').draw(x,y+2*30),      text
('Centered',            x+410, y+2*30+10)
        box(b1,b2,' ',b3).draw(x,y+3*30),          text('Splitted (2
groups)', x+410, y+3*30+10)
        box(' ',b1,b2,' ',b3,' ').draw(x,y+4*30),  text('Splitted (3
groups)', x+410, y+4*30+10)

        y = 10 + 5*30
        box = HBox((0,0),(400,20),True,3)          # Homogeneous
        box(' ',b1,b2,b3).draw(x, y+0*30),         text('Homogeneous &
right aligned',       x+410, y+0*30+10)
        box(b1,b2,b3,' ').draw(x, y+1*30),         text('Homogeneous &
left aligned',        x+410, y+1*30+10)
        box(' ',b1,b2,b3,' ').draw(x,y+2*30),      text('Homogeneous &
centered',            x+410, y+2*30+10)
        box(b1,b2,' ',b3).draw(x,y+3*30),          text('Homogeneous &
splitted (2 groups)', x+410, y+3*30+10)
        box(' ',b1,b2,' ',b3,' ').draw(x,y+4*30),  text('Homogeneous &
splitted (3 groups)', x+410, y+4*30+10)

        y = 10 + 10*30
        box = HBox((0,0),(400,20),False,0)        # Manual placement
        box(' ', b1,2,b2,2,b3).draw(x, y+0*30),   text('Manual
separation & right aligned',  x+410, y+0*30+10)
        box(b1,2,b2,2,b3,' ').draw(x, y+1*30),    text('Manual
separation & left aligned',   x+410, y+1*30+10)
        box(' ',b1,2,b2,2,b3,' ').draw(x, y+2*30),text('Manual
separation & centered',       x+410, y+2*30+10)


        y = 10 + 13*30
        box = HBox((0,0),(400,20),True,3)         # Expandable widget
with homogeneous size for others
        box(b1,(b2,-1),b3).draw(x, y+0*30),       text('Homogenous
with one expandable widget',      x+410, y+0*30+10)

        box = HBox((0,0),(400,20),False,3)        # Expandable widget
with homogeneous size for others
        box(b1,(b2,-1),b3).draw(x, y+1*30),       text('Non
homogeneous with one expandable widget', x+410, y+1*30+10)



    pyglet.app.run()



--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"pyglet-users" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/pyglet-users?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to