[Alpha] TreeGUI

hmm i probably rewrite treegui couple of times now. Ill try to make a new branch.

New branch of treegui is out. This is again a rewrite

Get it here:

code.launchpad.net/~starplant/treegui/treegui3

I’m a little confused by the example theme. /rtheme/twotone contains hundreds of little icons, but as far as I can tell, most aren’t referred to in /rtheme/init.py. Is there some other place that they’re used in the examples, or are they there because they’re from some larger app?

Additionally, here you mention the use of egg files to describe texture atlases, but I can’t find any egg file in the examples. Is there an example of how to do that out there? Is it required or not?

EDIT: Actually, I guess I figured out the latter question – it’s automatically built when the theme is loaded.

I noticed that font characters had a large amount of padding in the texture atlas, leading to a lot of wasted space. This patch fixes that:

=== modified file treegui/eggatlas.py
--- treegui/eggatlas.py	2009-09-08 17:31:46 +0000
+++ treegui/eggatlas.py	2009-09-25 19:34:44 +0000
@@ -225,7 +225,7 @@
             charName = name+str(i)
             #print i,charName,"'%s'"%char
             glyph = font.getGlyph(i)
-            w,h = glyph.getWidth(),glyph.getBottom()
+            w,h = glyph.getWidth(),glyph.getBottom()-glyph.getTop()
             image = PNMImage(w+PADDING*2,h+PADDING*2)
             image.addAlpha()
             image.alphaFill()
@@ -239,7 +239,7 @@
             #image.write(tmp+"/%i.png"%i)
             self.extents[charName] = (
                 glyph.getAdvance(),
-                glyph.getBottom()-glyph.getTop())
+                glyph.getBottom()-2*glyph.getTop())
             
             self.it.add(charName,image)

Note that there are some truly strange things going on in PNFTextGlyph’s placement of the baseline, so this is, at best, “bug-compatible” with what’s going on there.

Oh, and here’s another patch to get proper key-repeat from keyboard entry.

=== modified file treegui/keys.py
--- treegui/keys.py	2009-09-05 00:34:29 +0000
+++ treegui/keys.py	2009-09-25 20:16:57 +0000
@@ -31,11 +31,15 @@
             base.accept(mouseEvent,self.onKey,[mouseEvent])
         for letter in symbols+letters:
             base.accept(letter,self.onKey,[letter])
+            base.accept(letter+'-repeat',self.onKey,[letter])
             base.accept('shift-'+letter,self.onKey,[letter.upper()])
+            base.accept('shift-'+letter+'-repeat',self.onKey,[letter.upper()])
         for event in work:
             base.accept(event,self.onKey,[event])
+            base.accept(event+'-repeat',self.onKey,[event])
         for index,key in enumerate(symbols):
             base.accept('shift-'+key,self.onKey, [shifted[index]] )
+            base.accept('shift-'+key+'-repeat',self.onKey, [shifted[index]] )
         self.inputKeys = dict.fromkeys(list(letters+letters.upper()+symbols+shifted)+work)
 
         self.lastMouseTime = 0

I’m on a bit of a roll. Here’s better text editing, including caret drawing and left/right/home/end. (This patch subsumes the previous one.)

=== modified file treegui/drawer.py
--- treegui/drawer.py	2009-09-06 19:37:05 +0000
+++ treegui/drawer.py	2009-09-25 21:43:22 +0000
@@ -118,15 +118,18 @@
                 u,v,us,vs = rect
                 self.rectStreatch((realX,realY,us,vs),(u,v,us,vs))
             
-        if thing.text:
+        if thing.text is not None:
             # draw text stuff
             self.color = Vec4(0,0,0,1)
             self.drawText(
                 gui.theme.defineFont(thing.font),
                 thing.text,
                 realX,
-                realY)
+                realY,
+                selection = thing.text_selection,
+                caret_pos = thing.caret_pos)
             self.color = Vec4(1,1,1,1)
+
             
         if thing.children:
             for child in thing.children:
@@ -138,7 +141,7 @@
     
      
     
-    def drawText(self, font, text, x, y):
+    def drawText(self, font, text, x, y, selection=(0,0), caret_pos=-1):
         """ 
             draws the text
             needs more work! 
@@ -148,12 +151,17 @@
         ox = x
         baseLetter = self.atlas.getChar(name + str(ord("T")))
         omaxh = baseLetter[3] - baseLetter[4][1]
-
+        char_count = 0
+            
         for line in text.split("\n"):
             build = []
             maxh = omaxh  
-                
+
             for c in line:
+                if char_count == caret_pos:
+                    u,v,w,h,e = self.atlas.getChar(name + str(ord('|')))
+                    build.append((x-w/2,y+e[1],u,v,w,h))
+                char_count += 1
                 code = ord(c)            
                 if code <= 32:
                     u,v,w,h,e = self.atlas.getChar(name + str(77))
@@ -163,6 +171,13 @@
                 build.append((x,y+e[1],u,v,w,h))
                 x += e[0]
                 maxh = max(maxh,h-e[1])
+
+            else:
+                if char_count == caret_pos:
+                    u,v,w,h,e = self.atlas.getChar(name + str(ord('|')))
+                    build.append((x-w/2,y+e[1],u,v,w,h))
+
+            char_count += 1
                  
             for x,y,u,v,w,h in build:
                 self.rectStreatch((x,y+maxh-h,w,h),(u,v,w,h))
@@ -170,7 +185,6 @@
             x = ox     
             y += maxh
         
-        
     def rect(self,(x,y,xs,ys),(u,v)):
         """ draw a rectangle """
         us = xs

=== modified file treegui/keys.py
--- treegui/keys.py	2009-09-05 00:34:29 +0000
+++ treegui/keys.py	2009-09-25 21:30:46 +0000
@@ -25,17 +25,21 @@
         letters = "abcdefghijklmnopqrstuvwxyz"     
         symbols = "1234567890-=,./[]\\;'`"
         shifted = "!@#$%^&*()_+<>?{}|:\"~"
-        work = ['enter','backspace','space','tab']        
+        work = ['enter','backspace','space','tab','delete','home','end','arrow_left','arrow_up','arrow_down','arrow_right']        
         self.mouseEvents = dict.fromkeys(["mouse1", "mouse2", "mouse3"])
         for mouseEvent in ["mouse1", "mouse2", "mouse3","mouse1-up","mouse2-up","mouse3-up","shift-mouse1"]:
             base.accept(mouseEvent,self.onKey,[mouseEvent])
         for letter in symbols+letters:
             base.accept(letter,self.onKey,[letter])
+            base.accept(letter+'-repeat',self.onKey,[letter])
             base.accept('shift-'+letter,self.onKey,[letter.upper()])
+            base.accept('shift-'+letter+'-repeat',self.onKey,[letter.upper()])
         for event in work:
             base.accept(event,self.onKey,[event])
+            base.accept(event+'-repeat',self.onKey,[event])
         for index,key in enumerate(symbols):
             base.accept('shift-'+key,self.onKey, [shifted[index]] )
+            base.accept('shift-'+key+'-repeat',self.onKey, [shifted[index]] )
         self.inputKeys = dict.fromkeys(list(letters+letters.upper()+symbols+shifted)+work)
 
         self.lastMouseTime = 0

=== modified file treegui/widgets.py
--- treegui/widgets.py	2009-09-08 17:31:46 +0000
+++ treegui/widgets.py	2009-09-25 21:35:52 +0000
@@ -26,7 +26,10 @@
 
 
     text = None     
-    font = "default_font"     
+    font = "default_font"
+    text_selection = (0,0)
+    caret_pos = -1
+         
     icon = None
 
     visable = True       # can we see
@@ -159,7 +162,7 @@
     style = "entry"
     text = ""
     control = True
-    
+    caret_pos = -1
     textLimit = None
     
     def onClick(self):
@@ -167,17 +170,44 @@
         return True
     
     def onKey(self,char):
+        print "char:",char
         if len(char) == 1:
-            self.text += char
+            self.text = self.text[:self.caret_pos]+char+self.text[self.caret_pos:]
+            self.caret_pos += 1
         elif char == "backspace":
-            self.text = self.text[:-1]
+            if self.caret_pos > 0:
+                self.text = self.text[:self.caret_pos-1]+self.text[self.caret_pos:]
+                self.caret_pos -= 1
+        elif char == "delete":
+            if self.caret_pos < len(self.text):
+                self.text = self.text[:self.caret_pos]+self.text[self.caret_pos+1:]
         elif char == "space":
-            self.text += " "
+            self.text = self.text[:self.caret_pos]+" "+self.text[self.caret_pos:]
+            self.caret_pos += 1
+        elif char == "arrow_left":
+            if self.caret_pos > 0:
+                self.caret_pos -= 1
+        elif char == "arrow_right":
+            if self.caret_pos < len(self.text):
+                self.caret_pos += 1
+        elif char == "home":
+            if self.caret_pos > 0:
+                self.caret_pos = 0
+        elif char == "end":
+            if self.caret_pos > 0:
+                self.caret_pos = len(self.text)
+                
         else:
             print char,"undefined for tree text entry yet"
             
         if self.textLimit:
             self.text = self.text[:self.textLimit]
+            
+    def onFocus(self):
+        self.caret_pos = len(self.text)
+        
+    def onUnfocus(self):
+        self.caret_pos = -1
     
 class PasswordEntry(Entry):
     """ single line entry that types as stars "*" """

I’ve added support for themed mouse cursors to treegui. Let me know if you’d be interested in the patch for that or not… I’m not sure if it fits with your vision for treegui.

sneftel, wow you are doing alot!
Yes your patches are most welcome! I am really excited here.

I will plan out some time to review them soon.

Yes the font rendering is still a bit foobar.
Actually i been working on fixing the fonts privately. I will try to merge your and my glyph code changes. I feel there is a bug on how True Type interfaces with panda3d for True Type fonts are never pixel perfect.

two tone icons are just icons for the theme to use. There isn’t a huge app the uses them right now. I just wanted to extend the sample to use the icons.

Here’s the patch for mouse cursors. Ensuring that the cursor is offset properly is left to the theme.

=== modified file treegui/core.py
--- treegui/core.py	2009-09-07 16:30:30 +0000
+++ treegui/core.py	2009-10-02 16:31:54 +0000
@@ -127,6 +127,7 @@
         self.parent = False
         self.children = []
         self.idToFrame = {}
+        self.cursor = None
         
         self.pos = Vec2(0,0)
         self.windowsize = 800,600
@@ -170,8 +171,11 @@
         
     def _draw(self):
         """ prods drawer to do its thing """
-        self.drawer.draw(self.children)
-    
+        if self.cursor is not None:
+            self.drawer.draw(self.children + [self.cursor])
+        else:
+            self.drawer.draw(self.children)
+            
     def _reSize(self):
         """ resize the window via panda3d internal events"""
         self.windowsize = base.win.getXSize(),base.win.getYSize()
@@ -201,7 +205,10 @@
             if gui.hoveringOver and gui.hoveringOver.onOut:
                 gui.hoveringOver.onOut()
             gui.hoveringOver = None
-        
+        if self.cursor is not None:
+            self.cursor._x = self.mouseX
+            self.cursor._y = self.mouseY
+            self.cursor.visable = self.mouseInWindow        
         
     def drag(self,widget,dragSnap=False):
         """ drags a widget """ 
@@ -269,3 +276,11 @@
             self.node.show()
         else:
             self.node.hide()      
+
+    def setCursorStyle(self, style):
+        if style is not None:
+            if self.cursor is None:
+                self.cursor = Cursor()
+            self.cursor.style = style
+        else:
+            self.cursor = None
\ No newline at end of file

=== modified file treegui/widgets.py
--- treegui/widgets.py	2009-09-08 17:31:46 +0000
+++ treegui/widgets.py	2009-10-02 16:31:34 +0000
@@ -120,7 +120,12 @@
     def __init__(self, icon, **placement):
         self.doPlacement(placement)    
         self.icon = icon
-        
+
+class Cursor(Widget):
+    """ a theme-driven image used for the mouse pointer """
+    clips = False
+    style = None
+    
 class Label(Widget):
     """ display a string of text in the ui """
     clips = False

Any more motion on this? I’ve done some more work, but I don’t want my copy to get too out of sync with the main distribution… it’s getting difficult to keep track of the diffs.

Sorry man had to move houses and stuff. Can you start your own bzr branch on LP? Then i can easier port your new changes. I will look into this more later today.

i have added some stuff to the tree GUI today.

  • multi line text edit with up and down arrows!
  • Click expands - making small thing easer to click
  • Scroll x and Scroll Y controls
  • Scroll Pane
  • adjustment to the % layouts, so you can say things like width=“100%-40” and it will come out how you want it.

Now contains 8 samples:
tut_browser.py
tut_complexlayout.py
tut_fonts.py
tut_form.py
tut_hud.py
tut_icons.py
tut_loginform.py
tut_texter.py

fixed many bugs.

just images, see 1st thread

It seems to have issues with .svn folders and their contents. While I was not able to find a good way to skip ‘.’ prefixed folders, I did fix it to skip ‘.’ prefixed files, and also fixed the file extension detection so it only will detect file extensions at the end of the file name.

eggatlas.py
In doFolders(self,folder)
extension=f.split(’.’)[-1]
if (extension==“png” or extension==“rgb”):
There are ways to make that cleaner, but that works fine.

An the main issue:
Also in eggatlas.py
in walkdir(dir)
it should skip folders and files starting with ‘.’

I also had at least once instance of a malformed atlas (produced while I was debugging) causing it to crash. I’m not sure what the best way to deal with that is

Craig,

You can use the following to get around the .svn folder.

def walkdir(dir):
“”" walks a dir and returns file names “”"
for path, dirs, files in os.walk(dir):
if ‘.svn’ in dirs:
dirs.remove(’.svn’)

    for file in files:
        yield  path+"/"+file

The above is working great for me.

i have pushed the new version to LP which addresses this problem.

In all the examples where you extend the Form class you will get errors. It seems you’ve changed the constructor of Form to (self,title,*args,**kwargs), which means you need atleast a string in those examples.

Yes i did sorry, i will fix that.

do you have any examples on how to use scrollpane as a select list? or is that feature being developed? I had created a single select list type on the original treegui you released - however too much of the code has changed since then.

Also, in my code I changed the Icon widget to be the following(as of Revision 28 you couldn’t set onClick for an Icon):

class Icon(Widget):
    """ a simple image that can act as a button"""
    clips = False
    style = None
    def __init__(self, icon, onClick, **placement):
        self.doPlacement(placement)    
        self.icon = icon
        self.onClick = onClick

Yes i should create dedicated select list and a drop down controls.
Not all icons are clickable. I will look into that.
I think you can add an onClick on any widget if you want to get onClick events even if you don’t pass it in constructor.

i = Icon(...)
i.onClick = myFunct