pyglet experimental gui

104 views
Skip to first unread message

dasacc22

unread,
Apr 21, 2009, 10:16:25 AM4/21/09
to pyglet-users
Hi

In regards to the layout.py in trunk/experimental/gui, is there any
other work related to this? I recently started work on a Widget class
and would like to use as much reference work as possible from trunk or
any branches.

Basically what Ive got going (or am going for) is a universal widget
that is used for each portion of making up a unit-of-work? or
something to that effect. A Widget without a parent is basically the
master widget and any widget attached to it is relative to that
widget. So for example, from this Ive written a class Select(Widget)
and class Option(Widget) that look like what I have appended to the
bottom of this message (where the majority of the code for creating
widgets focuses on events and maybe auto generating text labels or
whatever). I then pretty much instantiate it like

ni = Select(parent=sfe, position=(170, 185), options=['eth1', 'lo',
'eth0']) # network interface
window.push_handlers(ni)

where sfe is just some boring widget dialog container. The math for
layout is basically just static right now as I was focusing on the
Widget Class. Also I should note that I would like the Widget class to
be independant of sprites, so that was a particular reason I didn't
look to heavily into subclassing the sprite class which looked like it
had a lot of work into it for events and stuff.

Anyway, this code is like only three days old, so any feedback,
pointers, or "your doing it all wrong"'s are greatly appreciated.
(Also note that currently the Window is implied to be instantiated as
window, i had did some work on that front but deleted most of it as it
just didn't seem as important as other aspects right now).

A full working example can be seen here, http://python.pastebin.com/m6e50e68f
(minus the images of course)

===

class Option(Widget):
def __init__(self, parent, position, option):
super(Option, self).__init__(parent=parent, position=position)
self.image = pyglet.image.load('option.png')
self.sprite = pyglet.sprite.Sprite(self.image,
batch=self.batch, \
group=self.foreground)
x, y = self.position
self.label = pyglet.text.Label(text=option, bold=True, \
color=(0, 0, 0, 255), x=x+12, y=y+4, batch=self.batch, \
group=self.foreground)


def on_mouse_press(self, x, y, button, modifiers):
if self.hit_test(x, y):
self.parent.label = self.label.text
for child in self.parent.children[:]:
if isinstance(child, Option):
child.sprite.group = None
child.sprite.batch = None
child.label.delete()
child.label.group = None
child.label.batch = None
window.remove_handlers(child)
self.parent.children.remove(child)

def on_mouse_motion(self, x, y, dx, dy):
if self.hit_test(x, y):
self.sprite.color = (38, 39, 41)
else:
self.sprite.color = (255, 255, 255)


class Select(Widget):
_label = None

def __init__(self, parent, position, options):
super(Select, self).__init__(parent=parent, position=position)

self.options = options

self.image = pyglet.image.load('select2.png')
self.sprite = pyglet.sprite.Sprite(self.image,
batch=self.batch, \
group=self.background)

x, y = self.position
self.label = options[0]

def on_mouse_press(self, x, y, button, modifiers):
'''migrate to Widget class to prevent multiple callings
maybe?'''
if self.foreground not in self.batch.top_groups and
self.hit_test(x, y):
x, y = self.position
for i, option in enumerate(self.options):
o = Option(self, (-4, 5-(i*24)), option)
window.push_handlers(o)

def on_mouse_motion(self, x, y, dx, dy):
if self.foreground not in self.batch.top_groups and
self.hit_test(x, y):
self.sprite.color = (38, 39, 41)
else:
self.sprite.color = (255, 255, 255)

@property
def label(self):
return self._label

@label.setter
def label(self, text):
if self._label:
self._label.delete()
self._label = None
x, y = self.position
self._label = pyglet.text.Label(text=text, bold=True, \
color=(0, 0, 0, 255), x=x+8, y=y+8, batch=self.batch, \
group=self.background)

tazg

unread,
Apr 21, 2009, 2:56:06 PM4/21/09
to pyglet-users
how about this to save some typing:


def show(self):
for widget in [self] + self.children:
window.push_handlers(widget)
window.draw_handlers.append(self.draw)

def hide(self):
for widget in [self] + self.children:
window.remove_handlers(widget)
window.draw_handlers.remove(self.draw)
> A full working example can be seen here,http://python.pastebin.com/m6e50e68f

dasacc22

unread,
Apr 21, 2009, 5:54:14 PM4/21/09
to pyglet-users
yeah, the parent widget would probably need to register with a
particular window and then all widgets could reference the window via
self.window if need to push and pop handlers and have some automagic
for registering handlers by default if needed. Im just not sure if
this is at all the way to go about it. In practice so far it seems to
be working out but im expecting some gotchas

dasacc22

unread,
Apr 21, 2009, 10:14:49 PM4/21/09
to pyglet-users
Ok so actually, i was doing some thinking and so much of different
widgets can be factored out into such similar parts. A dropdown menu
with a list is nothing more then an option with a list of options. A
file menu is nothing more then a row of options with a list of
options. My Option class is really nothing more then a glorified
sprite class and my select class is really nothing more then a
glorified Option class. This really boils down to layout and event
handlers for typical actions, which is what was kind of begining to
permeate from my code.

I think ill try playing with the layout.py from experimental and see
where that goes

tazg

unread,
Apr 21, 2009, 10:44:20 PM4/21/09
to pyglet-users
I don't know if this helps but this is what I used for my menu class:

http://www.pyglet.org/doc/api/pyglet.text.layout.IncrementalTextLayout-class.html#get_point_from_position

The menu itself was basically a single document object that recorded
the start and end position of each option for hit-testing.

Lynx

unread,
Apr 22, 2009, 2:03:17 AM4/22/09
to pyglet-users
On Apr 21, 7:14 pm, dasacc22 <dasac...@gmail.com> wrote:
> Ok so actually, i was doing some thinking and so much of different
> widgets can be factored out into such similar parts. ...

Something I've got in my own GUI-in-progress is the concept of
anchoring widgets to each other at their corners, so for instance, you
can align one widget to the top of another widget. Based on
Blizzard's widget UI interface design.

So for instance, you might say, 'widget B's top left point is anchored
to widget A's bottom left point' to construct a left-aligned list, or
you might connect their top center and bottom center points to make a
center-aligned menu.

I mention this because I think it's a really good idea for GUIs, and
pretty easy to implement.

Lynx

unread,
May 5, 2009, 9:20:36 PM5/5/09
to pyglet-users
On Apr 21, 7:44 pm, tazg <TazG2...@gmail.com> wrote:
> I don't know if this helps but this is what I used for my menu class:
>
> http://www.pyglet.org/doc/api/pyglet.text.layout.IncrementalTextLayou...

*shakes fist* You just made me decide to poke at the text layout
stuff to see if it could be turned into a GUI manager! That really is
very clever reuse of code.

AbstractDocument supports inserting inline elements, so you could
theoretically have a document be presented as a GUI, placing buttons
and other controls within the text. So I envision a GuiDecoder,
similar to HtmlDecoder, that could create a control panel or game menu
for you, from a formatted text string...

Alex Holkner

unread,
May 5, 2009, 9:43:13 PM5/5/09
to pyglet...@googlegroups.com

This sounds like a really bad idea to me. After getting something
basic working (say, vertical or horizontal flow), the very next thing
you'll want to do is align elements -- say, placing check marks next
to menu items, or labels next to text boxes -- which is best done with
a tabular layout. The text layout module is really not set up for
tabular layout. It can do tab stops, but I think you'll find them
frustrating for this kind of thing.

Good luck with whichever path you choose -- GUI layout is a
surprisingly difficult problem (but one that can do with some
innovation!).

Alex.

dasacc22

unread,
May 6, 2009, 12:19:27 PM5/6/09
to pyglet-users
bah, i replied-to-author instead of replying to thread last nite. Lynx
was kind enough to forward me the email so I could repost my comments,
thanks :)

things get complicated though when you factor in anything more
then
the most basic inline element. A progressbar for example, would need
alot more then the decoding of a text string. It needs hook points
for
updating its progress, it needs a callback for animation, anything
else you might think of as useful. The text layout is probably
going
to remain useful for just as it implies, laying out text, and not
to
much more. Of course im hardly authorative, just some slacker
writing
code.

At the moment Im using a portion of the original code I had posted
in
two seperate personal projects, VideoTalk (just as the name
implies,
http://www.dasa.cc/videotalk2.png) which has dropdowns, and
Thumpin (a
movie organizer with automatic imdb & poster downloads and filter-
as-
you-type search field on all major areas of a movie) which has
text
input based on example from source, http://www.dasa.cc/thumpin2.png,
and my progressbar (as it loads movie data and textures in a
seperate
thread on startup that the progressbar checks up on,
http://www.dasa.cc/thumpin.png).

Right now its getting the job done without any fuss, so i cant
complain to much, and im about to start working on a new project
using
it as well until i have a sack of stones hit me in the head and
set me
straight as to which way to go with this.

To the reader thats made it this far, and on a totally unrelated
subject, one of my soon to be next goals on my VideoTalk is to do
some
facial expression recognition on the webcam feed and translate a
model
appropriately (basically transmitting the translation data instead
of
an entire series of images) and provide sane controls to
manipulate
the voice feed on the fly, think space opera, your a big blobby
pizza
the hut, your voice reflects it, and when your ship is hailed, all
of
this gets reflected as your character. Certainly seems more
engaging
then this ol pesky keyboard...

in which he replies
"""
Note that you sent this message to just me, BTW, and not the list.
I've left the message in if you wish to repost it.

This is very true. There'd need to be a way for GuiDocument widgets
to receive events as well as dispatch them. This is just something
I'm exploring, to better understand 'deep Pyglet'.

Every GUI project I've seen has been abandoned because their owners
didn't think they were robust enough, or 'Pyglet' enough. I'm trying
to understand why that is.
"""

And I have to agree, Ive been exploring the reasonings into GUI
development that ive come across in various threads and works, and the
goal of having a robust system and an implementation that works well
along the lines of being pyglet-like in having a nice procedural
method of work too.

My gut tells me theres something close, perhaps theres some innovation
just around the corner for us all to find

Lynx

unread,
May 6, 2009, 12:27:49 PM5/6/09
to pyglet-users
On May 5, 6:43 pm, Alex Holkner <alex.holk...@gmail.com> wrote:
> This sounds like a really bad idea to me.  After getting something
> basic working (say, vertical or horizontal flow), the very next thing
> you'll want to do is align elements -- say, placing check marks next
> to menu items, or labels next to text boxes -- which is best done with
> a tabular layout.  The text layout module is really not set up for
> tabular layout.  It can do tab stops, but I think you'll find them
> frustrating for this kind of thing.

Oh hey! Thank you for pointing the tab stops out. It's amazing how
much stuff Pyglet has buried.

Here's my thinking on how I could hotwire pyglet.text:

- We can already attach arbitrary style properties using
AttributedText. I implemented a SimpleMenu last night using
{selection_id N} styles around menu options. Quick and efficient as
tazg noted.

- Using a decoder should let us insert widgets into a Document.
Widgets would be identified by name, and have additional properties.
We can control the appearance of the widgets by providing the decoder
a themed 'WidgetFactory' which supplies widgets.

- A GuiLayout could handle passing events into the embedded widgets,
and serve as the interception point for the user to pick up events
from those widgets.

I figure that 80-90% of basic GUI needs can be satisfied by menus,
scrollbars, buttons, text fields, sliders, and checkboxes/radio boxes.

dasacc22

unread,
May 7, 2009, 10:40:40 PM5/7/09
to pyglet-users
> I figure that 80-90% of basic GUI needs can be satisfied by menus,
> scrollbars, buttons, text fields, sliders, and checkboxes/radio boxes.

I guess Im the 10% weirdo that needs tiered rotational dials, multi-
layered drop boxes, and tool joints in my toolbox. Wish I had better
names for what I am talking about, but crap.. I dont.

Id still be curious to see a working example if you get around to
posting one.

Lynx

unread,
May 26, 2009, 8:39:55 PM5/26/09
to pyglet-users
Bad news on this front, my computer's power supply along with the hard
drive, losing my work on the GUI. It had been going all right up to
that point with a scrollable menu class that worked reasonably
speedily and a skinnable panel that would resize easily and looked
great on most backgrounds... But faced with recreating the work, I'm
not sure that overloading Document and IncrementalTextLayout are the
way to go. The main attraction of doing so was writing a GUI in a
very simple fashion, but I like the way Simplui does that more.

However, there were some interesting points of doing it in Document/
Layout fashion:

* Separates out the GUI into 'model' (the document) and 'view' (the
layout). That is, the document defines what widgets are present, how
large they need to be, and what properties they might have, along with
any intervening text. The layout is what actually calls the widgets
to set screen-specific vertex lists. I like this and will reuse it in
my next iteration.

* Text was very easy to control; however the default layout doesn't
allow you to define blocks and flow the text around them, i.e. you
can't have a picture in the upper right of the menu and have more than
one line of text to its left. I gave some thought to extending
IncrementalTextLayout to support 2-column and 3-column formats, but
it'd be a lot of work.

* It turns out that if you click to the right side of a character, it
actually treats it as 'just past the character', whereas clicking to
the left of a character selects the character itself. For GUI
purposes, when I was using the layout functions to determine what
widget was being interacted with, this meant that the right side of
each widget was unusable. This can be fixed by overriding the
respective function.

Moot now since I'm going to try a different path, but I thought I
would update in case anyone was wondering what happened to this
idea. :)

Tristam MacDonald

unread,
May 26, 2009, 9:08:53 PM5/26/09
to pyglet...@googlegroups.com
On Tue, May 26, 2009 at 8:39 PM, Lynx <cw....@gmail.com> wrote:

On May 7, 7:40 pm, dasacc22 <dasac...@gmail.com> wrote:
> > I figure that 80-90% of basic GUI needs can be satisfied by menus,
> > scrollbars, buttons, text fields, sliders, and checkboxes/radio boxes.
>
> I guess Im the 10% weirdo that needs tiered rotational dials, multi-
> layered drop boxes, and tool joints in my toolbox. Wish I had better
> names for what I am talking about, but crap.. I dont.
>
> Id still be curious to see a working example if you get around to
> posting one.

Bad news on this front, my computer's power supply along with the hard
drive, losing my work on the GUI.  It had been going all right up to
that point with a scrollable menu class that worked reasonably
speedily and a skinnable panel that would resize easily and looked
great on most backgrounds... But faced with recreating the work, I'm
not sure that overloading Document and IncrementalTextLayout are the
way to go.  The main attraction of doing so was writing a GUI in a
very simple fashion, but I like the way Simplui does that more.

On the flip side, I need to rewrite the simplui layout system from the ground up (shouldn't affect the public API), as my haphazard development left some conceptual issues, particularly with element resizing.

I am also busy trying to embed an Awesomium build (i.e the Chrome rendering engine) inside pyglet, to offer a full HTML/CSS/JavaScript solution ;)

--
Tristam MacDonald
http://swiftcoder.wordpress.com/

dasacc22

unread,
May 26, 2009, 11:23:48 PM5/26/09
to pyglet-users
Tristam,

Chrome in pyglet would be awesome, this is something i perused with
(from my rememberings of clutter-toolkit and how they implemented
webkit way back). No doubt id be pursuing this too if I had the time
(i had already been playing around with chromium builds and what not).

A saw your simplui examples, ive always eeked when seeing code like
that where its all one call. I can definately dig it, its just
something that urked me when i first started python for web
development and i saw these html helpers so can do things like HTML
(HEAD(TITLE()), BODY(P())) and "never write html again!"

Anyway, it looks interesting and im about to step away from my strange
controls im building and do some basic, standard interface stuff. Im
totally ok with what ive done since on my library but it needs some
extra work for layout and my hearts just not in document layouts. Ill
definately give this library a go and provide some feedback (glHint
(the above rant was like preuse feedback :))

On May 26, 9:08 pm, Tristam MacDonald <swiftco...@gmail.com> wrote:

Richard Jones

unread,
May 26, 2009, 11:41:57 PM5/26/09
to pyglet...@googlegroups.com
I encourage anyone looking into developing new guis to look at Shoes
for Ruby. Implementing Chrome may seem like a really neat idea but
really, when you get down to it, XHTML/CSS/Javascript (or even
Pythonscript) is really quite cumbersome and yuck :)

Compare Chrome code (or "line noise" as it's otherwise known") with:

Shoes.app { button "PUSH!" }

which (as the only code that's written) puts up a window with a button
labelled "PUSH!". It doesn't get much simpler than that. See also:

Shoes.app {
stack {
button "Mice"
button "Eagles"
button "Quail"
}
}

See http://shoooes.net/tutorial/ for some more examples.

"Simple is better than complex."


Richard

Tristam MacDonald

unread,
May 26, 2009, 11:50:19 PM5/26/09
to pyglet...@googlegroups.com
On Tue, May 26, 2009 at 11:23 PM, dasacc22 <dasa...@gmail.com> wrote:
A saw your simplui examples, ive always eeked when seeing code like
that where its all one call. I can definately dig it, its just
something that urked me when i first started python for web
development and i saw these html helpers so can do things like HTML
(HEAD(TITLE()), BODY(P())) and "never write html again!"

The library is carefully set up so that you can use it either way. I provided the single statement demonstration because a) it's cool, and b) I prefer to code like that, but I certainly can see where others may differ.

You can create all simplui elements individually, and add()/remove() them from the container elements. Every optional constructor parameter gets a (fairly) reasonable default value, and is exposed as an attribute.

There isn't a whole lot of support for custom layouts let - that will come with the layout rewrite I mentioned earlier.

Tristam MacDonald

unread,
May 26, 2009, 11:59:42 PM5/26/09
to pyglet...@googlegroups.com
On Tue, May 26, 2009 at 11:41 PM, Richard Jones <r1char...@gmail.com> wrote:

I encourage anyone looking into developing new guis to look at Shoes
for Ruby. Implementing Chrome may seem like a really neat idea but
really, when you get down to it, XHTML/CSS/Javascript (or even
Pythonscript) is really quite cumbersome and yuck :)

Chrome brings some wonderful features to the table, in particular the ability to drop pre-built modules right in, such as a syntax-aware, on-the-fly shader editor. It also allows you to trivially embed feedback forms, and all sorts of other web applications. However, the biggest argument for (from my point of view) it is that a graphic designer can build the entire game's GUI in a couple of hours, without specialized knowledge.

Take a look at what the guys over at wolfire have done with Chrome: http://blog.wolfire.com/2009/05/new-object-palette-window/

Compare Chrome code (or "line noise" as it's otherwise known") with:

    Shoes.app { button "PUSH!" }

which (as the only code that's written) puts up a window with a button
labelled "PUSH!". It doesn't get much simpler than that. See also:

Shoes.app {
    stack {
       button "Mice"
       button "Eagles"
       button "Quail"
     }
}

See http://shoooes.net/tutorial/ for some more examples.

"Simple is better than complex."

Shoes is fantastic as APIs go, but I am not quite as convince about the practicalities of the whole layout model. Regardless, I have gone for a Shoe-like feel in simplui, albeit without attempting to contort python's syntax ;)

Lynx

unread,
May 29, 2009, 11:53:11 AM5/29/09
to pyglet-users
On May 26, 6:08 pm, Tristam MacDonald <swiftco...@gmail.com> wrote:
> On the flip side, I need to rewrite the simplui layout system from the
> ground up (shouldn't affect the public API), as my haphazard development
> left some conceptual issues, particularly with element resizing.
>
> I am also busy trying to embed an Awesomium build (i.e the Chrome rendering
> engine) inside pyglet, to offer a full HTML/CSS/JavaScript solution ;)

That would indeed be impressive, but I'd be worried about how much
processing time it would suck away from the application. Would it be
a callout to a C++-based engine?

Anyway, here's how it's looking so far:

http://lagkitten.purrsia.com/kytten-gui-1.jpg

Code to generate this GUI:

# Set up a test dialog
dialog = gui.Dialog(batch=batch, group=fg_group,
anchor=gui.ANCHOR_CENTER,
color=(255, 235, 128),
content=gui.Panel(
gui.HorizontalLayout([
gui.Button("Play"),
gui.Button("Pause"),
gui.Button("Stop"),
])))
window.push_handlers(dialog)

Not really functional since all I have is the panel and the buttons,
but... It's worth noting here that the looks of the panel and the
buttons are not hardcoded but in fact, replacable bitmaps. I use code
that breaks the panel texture up into a grid of subtextures and
stretches the middle parts across the panel, same deal for the buttons.

Tristam MacDonald

unread,
May 29, 2009, 12:23:07 PM5/29/09
to pyglet...@googlegroups.com
On Fri, May 29, 2009 at 11:53 AM, Lynx <cw....@gmail.com> wrote:
On May 26, 6:08 pm, Tristam MacDonald <swiftco...@gmail.com> wrote:
> On the flip side, I need to rewrite the simplui layout system from the
> ground up (shouldn't affect the public API), as my haphazard development
> left some conceptual issues, particularly with element resizing.
>
> I am also busy trying to embed an Awesomium build (i.e the Chrome rendering
> engine) inside pyglet, to offer a full HTML/CSS/JavaScript solution ;)

That would indeed be impressive, but I'd be worried about how much
processing time it would suck away from the application.  Would it be
a callout to a C++-based engine?

Ja, we embed Chrome itself (the entire browser engine). Chrome is implemented is C++, so you make calls out to the C++ library, and it fills a texture for rendering. I have a working demo, but we are still figuring out the keyboard event injection. 

dasacc22

unread,
May 29, 2009, 7:11:06 PM5/29/09
to pyglet-users
Hey lynx, that subtexturing and stretching reminds me of png 9 patch.

http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch

Something I definately want to do is get an implementation of
android's 9 patch for use with my pyglet projects (among other
things). Im just assuming that your making carved-in-stone rules for
stretching the image. Maybe you could go into details of what your
doing? I really like the idea of the png 9 patch where the top and
left describe the stretchable area and the bottom and right describe
the padding area.

Anyway, im pretty curious on what you did in that area, specifics!
specifics!

Lynx

unread,
May 29, 2009, 7:58:27 PM5/29/09
to pyglet-users
On May 29, 4:11 pm, dasacc22 <dasac...@gmail.com> wrote:
> Hey lynx, that subtexturing and stretching reminds me of png 9 patch.
> http://developer.android.com/guide/topics/graphics/2d-graphics.html#n...

That's a nice way to do it, re: using lines along the side to define
the stretchable areas.

It's actually pretty close to what 9-patch is doing but instead of
lines on the side, I pass in parameters to say how far the left,
right, top and bottom margins of the stretchable region should be.
These parameters are hardcoded right now but my intent is to have it
read GUI skin information from an XML file.

The actual panel class is just a vertex list of GL_QUADS, each of
which corresponds to one of the 9 boxes. To resize it, all I have to
do is change the vertices, the tex coords don't change.

Let me know if you want sample code for some reason.

Joe Wreschnig

unread,
May 30, 2009, 11:55:47 PM5/30/09
to pyglet...@googlegroups.com
On Fri, May 29, 2009 at 4:11 PM, dasacc22<dasa...@gmail.com> wrote:
>
> Hey lynx, that subtexturing and stretching reminds me of png 9 patch.
>
> http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch

Wow, I hadn't seen NinePatch before. That's a really cool way of
encoding that information.

I wrote a basic pyglet-using implementation at
http://code.google.com/p/layer/source/browse/layer/layer/cirno.py,
which will cut up the image you give it based on NinePatch rules. You
can invoke it as a command line program with a filename and --write to
make it write out the cut PNGs, or just use it in your program and
pull the resulting images out of it. It's not very well tested, but it
works on a set of NinePatch tiles I made.

I'd like to also have some way to render it, but doing that in a
generally useful way is actually pretty tricky. You have to use an FBO
on a texture or a custom sprite class with independent X/Y scaling
(which is what I use). Or maybe someone else has a clever idea how to
do that without adding more dependencies.

Alex Holkner

unread,
May 31, 2009, 3:05:22 AM5/31/09
to pyglet...@googlegroups.com

Rendering a NinePatch is actually quite simple, you just draw 9 quads
with the appropriate texture coordinates. I hacked your NinePatch
code to demonstrate this:
http://code.google.com/p/pyglet/source/browse/trunk/experimental/ninepatch.py.
More work is needed to:
- Draw the image as part of a batch instead of in immediate mode
(very easy -- follow Sprite's example)
- Arbitrary number of stretchy regions, as allowed by Android
- Tiling the stretchy region instead of stretching it (I imagine this
would be more useful for decorative borders, etc).

Alex.

Alex Holkner

unread,
May 31, 2009, 3:08:25 AM5/31/09
to pyglet...@googlegroups.com

I should also have clarified that there's no need to save the patches
separately -- this method works by loading just the source image as a
texture. I had some trouble finding sample 9-patch images; there are
a couple here:

http://www.anddev.org/tutorial_buttons_with_niceley_stretched_background-t4369.html

Alex.

dasacc22

unread,
May 31, 2009, 5:50:25 AM5/31/09
to pyglet-users
Hey,

First off, what a pleasant surprise this morning! Wasn't expecting to
wake up at 4:30 this morning, stumbling down the stairs to find this
in my google reader.

The android sdk actually comes with a 9 patch tool written in java as
can be seen here, http://developer.android.com/guide/developing/tools/draw9patch.html.
Im not sure what the redistribution rights of the jar is so Ill just
suggest anyone interested download the sdk and look under the tools
folder after unzipping. The benefit of it is being able to see a live
preview of the stretching as you add the outline. The suck comes in if
your using a mousepad on your laptop trying to draw a 1 px border in
what is essential "paint".

Next off, theres a couple things that can also be read at the link i
provided above in regards to implementation. The 9 patch border is
actually 1px and padding is totally optional. Since padding is
optional, the draw_around method needs to account for padding still
being None. I was looking into this - this morning, and I think the
draw method probably needs to be changed a bit because taking the max
means that it doesn't shrink (which i could only imagine it ought to)
and when padding is None, the params passed to draw need to ?already
know the size of the texture in relation to the label? (to move the
x,y correctly for centering it).

Sorry i dont have a patch, my initial changes weren't anything to diff
about, but thought id leave my comments none the less

On May 31, 3:08 am, Alex Holkner <alex.holk...@gmail.com> wrote:
> On Sun, May 31, 2009 at 5:05 PM, Alex Holkner <alex.holk...@gmail.com> wrote:
> > On Sun, May 31, 2009 at 1:55 PM, Joe Wreschnig <joe.wresch...@gmail.com> wrote:
>
> >> On Fri, May 29, 2009 at 4:11 PM, dasacc22<dasac...@gmail.com> wrote:
>
> >>> Hey lynx, that subtexturing and stretching reminds me of png 9 patch.
>
> >>>http://developer.android.com/guide/topics/graphics/2d-graphics.html#n...
>
> >> Wow, I hadn't seen NinePatch before. That's a really cool way of
> >> encoding that information.
>
> >> I wrote a basic pyglet-using implementation at
> >>http://code.google.com/p/layer/source/browse/layer/layer/cirno.py,
> >> which will cut up the image you give it based on NinePatch rules. You
> >> can invoke it as a command line program with a filename and --write to
> >> make it write out the cut PNGs, or just use it in your program and
> >> pull the resulting images out of it. It's not very well tested, but it
> >> works on a set of NinePatch tiles I made.
>
> >> I'd like to also have some way to render it, but doing that in a
> >> generally useful way is actually pretty tricky. You have to use an FBO
> >> on a texture or a custom sprite class with independent X/Y scaling
> >> (which is what I use). Or maybe someone else has a clever idea how to
> >> do that without adding more dependencies.
>
> > Rendering a NinePatch is actually quite simple, you just draw 9 quads
> > with the appropriate texture coordinates.  I hacked your NinePatch
> > code to demonstrate this:
> >http://code.google.com/p/pyglet/source/browse/trunk/experimental/nine....
> >  More work is needed to:
> >  - Draw the image as part of a batch instead of in immediate mode
> > (very easy -- follow Sprite's example)
> >  - Arbitrary number of stretchy regions, as allowed by Android
> >  - Tiling the stretchy region instead of stretching it (I imagine this
> > would be more useful for decorative borders, etc).
>
> I should also have clarified that there's no need to save the patches
> separately -- this method works by loading just the source image as a
> texture.  I had some trouble finding sample 9-patch images; there are
> a couple here:
>
> http://www.anddev.org/tutorial_buttons_with_niceley_stretched_backgro...
>
> Alex.

Joe Wreschnig

unread,
May 31, 2009, 6:04:48 AM5/31/09
to pyglet...@googlegroups.com
On Sun, May 31, 2009 at 12:05 AM, Alex Holkner<alex.h...@gmail.com> wrote:
>
> On Sun, May 31, 2009 at 1:55 PM, Joe Wreschnig <joe.wr...@gmail.com> wrote:
>>
>> On Fri, May 29, 2009 at 4:11 PM, dasacc22<dasa...@gmail.com> wrote:
>>>
>>> Hey lynx, that subtexturing and stretching reminds me of png 9 patch.
>>>
>>> http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch
>>
>> Wow, I hadn't seen NinePatch before. That's a really cool way of
>> encoding that information.
>>
>> I wrote a basic pyglet-using implementation at
>> http://code.google.com/p/layer/source/browse/layer/layer/cirno.py,
>> which will cut up the image you give it based on NinePatch rules. You
>> can invoke it as a command line program with a filename and --write to
>> make it write out the cut PNGs, or just use it in your program and
>> pull the resulting images out of it. It's not very well tested, but it
>> works on a set of NinePatch tiles I made.
>>
>> I'd like to also have some way to render it, but doing that in a
>> generally useful way is actually pretty tricky. You have to use an FBO
>> on a texture or a custom sprite class with independent X/Y scaling
>> (which is what I use). Or maybe someone else has a clever idea how to
>> do that without adding more dependencies.
>
> Rendering a NinePatch is actually quite simple, you just draw 9 quads
> with the appropriate texture coordinates.  I hacked your NinePatch
> code to demonstrate this:
> http://code.google.com/p/pyglet/source/browse/trunk/experimental/ninepatch.py.

Ah, this is much smarter than what I was doing, and neatly solves the
drawing problem. Thanks! I'll see if I can find time to do the tiling
stuff tomorrow.

I saw the arbitrary number of stretchy regions was allowed by the
spec, but couldn't find any examples of it, and it's called
*nine*patch anyway. :)

Alex Holkner

unread,
May 31, 2009, 5:59:14 PM5/31/09
to pyglet...@googlegroups.com
On Sun, May 31, 2009 at 7:50 PM, dasacc22 <dasa...@gmail.com> wrote:
> Next off, theres a couple things that can also be read at the link i
> provided above in regards to implementation. The 9 patch border is
> actually 1px and padding is totally optional. Since padding is
> optional, the draw_around method needs to account for padding still
> being None. I was looking into this - this morning, and I think the

I took "padding is optional" to mean that there need not be any black
marker in the lower/right section; however I assume the 1-pixel border
is mandatory.

> draw method probably needs to be changed a bit because taking the max
> means that it doesn't shrink (which i could only imagine it ought to)

The max() is there to ensure the 9-patch is never rendered smaller
than the original source image. Doing so causes artifacts (see for
yourself by commenting out the max and choosing a tiny font size).

> and when padding is None, the params passed to draw need to ?already
> know the size of the texture in relation to the label? (to move the
> x,y correctly for centering it).

The current code makes no attempt to center things. The two methods
can be used as:

- draw(x, y, width, height): Draw the entire 9-patch to fit within the
given area
- draw_around(x, y, width, height): Draw the 9-patch so that it
encloses the given area + padding.

Alex.

Nicolas Rougier

unread,
Jun 1, 2009, 6:13:17 AM6/1/09
to pyglet-users

Hi all,

Here is another implementation of the nine patch algorithm. It takes
all stretchable parts into account and handle padding when it is
needed. The image by itself is rendered using indexed vertex list. You
will need a 'button.9.png' to run the example. Note that stretching
down may fail if the stretchable part is too small.

Hope that helps.

Nicolas



#!/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.
#
-----------------------------------------------------------------------------
''' Stretchable image.

Implementation of the nine patch algoritm as explained on:
http://developer.android.com/guide/topics/graphics/2d-graphics.html

A stretchable image is a stretchable bitmap image, which will
automatically
be resized according to the following rules:

The border is used to define the stretchable and static
areas of the
image. You indicate a stretchable section by drawing one
(or more)
1-pixel-wide black line(s) in the left and top part of the border.
(You can
have as many stretchable sections as you want.) The relative
size of the
stretchable sections stays the same, so the largest sections
always remain
the largest.

You can also define an optional drawable section of the image
(effectively,
the padding lines) by drawing a line on the right and bottom
lines. If the
padding lines are not included, Android uses the left and top
lines to
define this drawable area.

To clarify the difference between the different lines, the
left and top
lines define which pixels of the image are allowed to be
replicated in
order to strech the image. The bottom and right lines define the
relative
area within the image that the contents of the View are
allowed to lie
within.
'''
import pyglet
import pyglet.gl as gl
from operator import add

class StretchableImage(object):
''' Stretchable image class. '''

def __init__(self,image, x=0, y=0, width=None, height=None,
content_width=None, content_height=None):
''' Build a new stretchable image using default image size.

:Parameters:
`image` : a pyglet image
A nine patch image
`x` : int
X coordinate of image.
`y` : float
Y coordinate of image.
`width` : int
Width of the image.
`height` : int
Height of the image.
`content_width` : int
Width of the image content area.
`content_height` : int
Height of the image content area.
'''

image_data = image.get_image_data()
data = image_data.get_data("RGBA", image.width * 4)
self._x_stretch = [] # Black pixels of first line
self._y_stretch = [] # Black pixels of first column
self._x_padding = [] # Black pixels of last line
self._y_padding = [] # Black pixels of last column
self._no_x_padding = False
self._no_y_padding = False
self._image = image # Store image
self._texture = image.get_texture()
self._x = x
self._y = y
self._width = image.width
self._height = image.height

# Get x stretchable area
y = image.height-1
for x in range(image.width):
p = (y*image.width+x)*4
self._x_stretch.append(int(data[p:p+3] == '\x00\x00\x00'
and data[p+3] !='\x00'))

# Get y stretchable area
x = 0
for y in range(image.height):
p = (y*image.width+x)*4
self._y_stretch.append( int(data[p:p+3] == '\x00\x00\x00'
and data[p+3] !='\x00'))

# Get x padding box
y = 0
for x in range(image.width):
p = (y*image.width+x)*4
self._x_padding.append(int(data[p:p+3] == '\x00\x00\x00'
and data[p+3] !='\x00'))
# If there is no black pixels, there is no padding
if self._x_padding == [0,]*len(self._x_padding):
self._x_padding = self._x_stretched
self._no_x_padding = True

# Get y padding box
x = image.width-1
for y in range(image.height):
p = (y*image.width+x)*4
self._y_padding.append( int(data[p:p+3] == '\x00\x00\x00'
and data[p+3] !='\x00'))
# If there is no black pixels, there is no padding
if self._y_padding == [0,]*len(self._y_padding):
self._y_padding = self._y_stretched
self._no_y_padding = True

# Build vertex list
if content_width or content_height:
w = content_width or image.width
h = content_height or image.height
self.set_content_size(w,h)
else:
w = width or image.width
h = height or image.height
self.set_size(w,h)


def _get_x(self):
return self._x
def _set_x(self, x):
self._x = x
x = property(_get_x, _set_x,
doc='''X coordinate of the image.

:type: int
''')

def _get_y(self):
return self._y
def _set_y(self, y):
self._y = y
y = property(_get_y, _set_y,
doc='''Y coordinate of the image.

:type: int
''')


def _get_width(self):
return self._width
def _set_width(self, width):
self._width = width
self.set_size(self._width,self._height)
width = property(_get_width, _set_width,
doc='''Width of the image.

:type: int
''')

def _get_height(self):
return self._height
def _set_height(self, height):
self._height = height
self.set_size(self._width,self._height)
height = property(_get_height, _set_height,
doc='''Height of the image.

:type: int
''')

def _get_content_width(self):
return self._content_width
def _set_content_width(self, width):
self._content_width = content_width
self.set_content_size(self._content_width,
self._content_height)
content_width = property(_get_content_width, _set_content_width,
doc='''Width of the image content area.

:type: int
''')

def _get_content_height(self):
return self._content_height
def _set_content_height(self, height):
self._content_height = height
self.set_content_size(self._content_width,
self._content_height)
content_height = property(_get_content_height,
_set_content_height,
doc='''Height of the image content area.

:type: int
''')


def set_content_size(self, width, height):
''' Set a new content size

:Parameters:
`width` : int
Width of the content area

`height` : int
Height of the content area

:Note:
This method may fail if stretchable part cannot be
stretched down to
the size request.
'''

n = reduce(add,self._x_padding) # Original content width
w = width + (len(self._x_padding)-n) - 2 + self._no_x_padding
n = reduce(add,self._y_padding) # Original content height
h = height + (len(self._y_padding)-n) - 2 + self._no_y_padding
self.set_size(w,h)


def set_size(self, width, height):
''' Set a new size.

:Parameters:
`width` : int
Width of the image.

`height` : int
Height of the image.

:Note:
This method may fail if stretchable part cannot be
stretched down to
the size request.
'''

self._width = width
self._height = height
self._content_width = width - (len(self._x_padding)-reduce
(add,self._x_padding))
self._content_height = height - (len(self._y_padding)-reduce
(add,self._y_padding))
self.content_x = self._x_padding.index(1)
self.content_y = self._y_padding.index(1)

x_stretched = self.stretch(self._x_stretch, width)
y_stretched = self.stretch(self._y_stretch, height)

on,off = 1,0
m = x_stretched
X = [0,] \
+ [i for i in range(len(m)) if m[i-1:i+1]==[off,on]] \
+ [i for i in range(len(m)) if m[i:i+2]==[on,off]] \
+ [len(m)-1,]
X.sort()
m = y_stretched
Y = [0,] \
+ [i for i in range(len(m)) if m[i-1:i+1]==[off,on]] \
+ [i for i in range(len(m)) if m[i:i+2]==[on,off]] \
+ [len(m)-1,]
Y.sort()
m = self._x_stretch
U = [0,] \
+ [i/float(len(m)-1) for i in range(len(m)) if m[i-1:i+1]==
[off,on]] \
+ [i/float(len(m)-1) for i in range(len(m)) if m[i:i+2]==
[on,off]] \
+ [1,]
U.sort()
m = self._y_stretch
V = [0,] \
+ [i/float(len(m)-1) for i in range(len(m)) if m[i-1:i+1]==
[off,on]]\
+ [i/float(len(m)-1) for i in range(len(m)) if m[i:i+2]==
[on,off]] \
+ [1,]
V.sort()

# Those value corresponds to the whole image, including
stretchable
# and padding areas, we thus need to correct them to remove
those areas.
(tu1, tv1, _, _, _, _, tu2, tv2, _, _, _, _) =
self._texture.tex_coords
u_bias, u_scale = tu1, tu2-tu1
v_bias, v_scale = tv1, tv2-tv1

# Computes new bias and scale
x_pixel = u_scale/float(self._image.width)
y_pixel = v_scale/float(self._image.height)
u_bias = x_pixel
v_bias = y_pixel
u_scale -= (2-self._no_x_padding)*x_pixel
v_scale -= (2-self._no_y_padding)*y_pixel

# Build vertex list
n,m = len(X), len(Y)
indices = []
vertices = []
texcoords = []
for yi in range(m):
for xi in range(n):
x,y,u,v = X[xi], Y[yi], U[xi], V[yi]
vertices += [x, y]
texcoords += [u_bias+u*u_scale,v_bias+v*v_scale]
for yi in range(m-1):
for xi in range(n-1):
i = yi*n + xi
indices += [i,i+1,i+n+1,i+n]
self.list = pyglet.graphics.vertex_list_indexed(n*m, indices,
('v2f',
vertices),
('t2f',
texcoords))


def stretch(self, mask, size, on=1, off=0):
''' Stretch a mask to given size taking stretchable parts
into account. '''


# Mask cannot be strechted down enough
if size <= (len(mask)-reduce(add,mask)):
raise ValueError, 'mask cannot be streched down enough to
fit size'

# First, look for all stretchable parts
starts = [i for i in range(len(mask)) if mask[i-1:i+1]==
[off,on]]
if mask[0] == on:
starts = [0,] + starts
stops = [i for i in range(len(mask)) if mask[i:i+2]==[on,off]]
if mask[-1] == on:
stops.append(len(mask)-1)

# Computes how much we need to stretch stretchable parts
n1 = reduce(add,mask) # total size of stretchable
parts
if n1 < 1:
raise ValueError, 'mask does not appear to have
stretchable parts'
n0 = len(mask) - reduce(add,mask) # total size of non-
stretchable parts
scale = (size-n0)/float(n1) # scale for stretchable
parts

index = 0 # Keep track of translated indices
length = 0 # Keep track of total stretched length so far
parts = []
for i in range(len(starts)):
start,stop = starts[i],stops[i]
if i == (len(starts)-1): # Last stretchable part
l = int((size-n0)-length)
else:
l = int(round((stop-start+1)*scale))
dt = l - (stop-start+1)
start = start+index
stop = start+l-1
index += dt
length += l
parts.append( (start,stop) )
# Build stretched mask
stretched_mask = [0,] * size
for i in parts:
start,stop = i
stretched_mask[start:stop+1] = (1,)*(stop+1-start)
return stretched_mask


def draw(self):
''' Draw image. '''

gl.glPushAttrib(gl.GL_ENABLE_BIT)
gl.glEnable(gl.GL_BLEND)
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
gl.glEnable(self._texture.target)
gl.glBindTexture(self._texture.target, self._texture.id)
gl.glPushMatrix()
gl.glTranslatef(self._x, self._y, 0)
self.list.draw(gl.GL_QUADS)
gl.glPopMatrix()
gl.glPopAttrib()



#
-----------------------------------------------------------------------------
if __name__ == '__main__':
window = pyglet.window.Window(width=400, height=400)

x,y = window.width/2, window.height/2
label = pyglet.text.Label('Hello pyglet !',
font_size=24, color=(0,0,0,255),
anchor_x='left', anchor_y='bottom')
image = StretchableImage(pyglet.image.load('button.9.png'),
content_width = label.content_width,
content_height = label.content_height)
x = x-image.width/2
y = y-image.height/2
image.x, image.y = x, y
label.x, label.y = x+image.content_x, y+image.content_y


@window.event
def on_draw():
gl.glClearColor(1,1,1,1)
window.clear()
image.draw()
label.draw()
pyglet.app.run()
Reply all
Reply to author
Forward
0 new messages