movable window in panda3d using dgui

i’ve been playing around with dgui for a while and thought i might post this, maybe someone has some improvement idea’s.

it’s far from being completed, i’d like to put that into a class to make windows instanceable. another problem is that the 0/0 point inside the window is still at the left bottom point (scrolling problem)

changed:
07.09.2008: updated the code (is not instanceable, having some problems with button textures)

from pandac.PandaModules import TextNode, Vec3
from direct.gui.DirectGui import DirectFrame,DirectButton,DirectScrolledFrame,DGG

import direct.directbase.DirectStart

# a task that keeps a node at the position of the mouse-cursor
def mouseNodeTask( task ):
  if base.mouseWatcherNode.hasMouse():
    x=base.mouseWatcherNode.getMouseX()
    y=base.mouseWatcherNode.getMouseY()
    # the mouse position is read relative to render2d, so set it accordingly
    aspect2dMouseNode.setPos( render2d, x, 0, y )
  return task.cont
# maybe we should check if aspect2d doesnt already contain the aspect2dMouseNode
aspect2dMouseNode = aspect2d.attachNewNode( 'aspect2dMouseNode', sort = 999999 )
taskMgr.add( mouseNodeTask, 'mouseNodeTask' )

#egg-texture-cards -o title.egg -p 240,240 title.png title.png title.png title.png


#from direct.showbase.Loader import Loader
#title_tex = Loader('a').loadTexture( 'Bild 3.png' )
#title = loader.loadTexture( 'title.png' )
#DEFAULT_TITLE_GEOM = ( title_tex, title_tex, title_tex, title_tex )
title = loader.loadModel('title.egg').find('**/title')
DEFAULT_TITLE_GEOM =  ( title
                      , title
                      , title
                      , title )
#resize_tex = Loader('a').loadTexture( 'Bild 5.png' )
resize_tex = loader.loadTexture( 'resize.png' )
DEFAULT_RESIZE_GEOM = ( resize_tex, resize_tex, resize_tex, resize_tex )

class DirectWindow( DirectFrame ):
  def __init__( self
              , pos         = ( -.5, .5)
              , title       = 'Title'
              , backgroundColor  = ( 1, 1, 1, 1 )
              , buttonColor = ( .6, .6, .6, 1 )
              , minSize     = ( .5, .5 )
              , maxSize     = ( 1, 1 )
              , titleGeom   = None
              , resizeGeom  = None ):
    # the main window we want to move around
    self.windowPos = pos
    self.window = DirectFrame(
        parent      = aspect2d,
        pos         = ( self.windowPos[0], 0, self.windowPos[1] ),
        frameColor  = ( 0, 0, 0, 1 ),
        )
    
    # the title part of the window, drag around to move the window
    self.headerHeight = 0.1
    h = -self.headerHeight
    self.windowHeader = DirectButton(
        parent      = self.window,
        geom        = titleGeom,
        frameSize   = ( -.5, .5, -.5, .5 ),
        borderWidth = ( 0, 0 ),
        relief      = DGG.FLAT,
        #scale       = (1,1,1),
        frameColor  = buttonColor,
        )
    
    text = TextNode('WindowTitleTextNode')
    text.setText(title)
    text.setAlign(TextNode.ACenter)
    text.setTextColor( 1, 1, 1, 1)
    text.setShadow(0.05, 0.05)
    text.setShadowColor(0, 0, 0, 1)
    #cmr12 = loader.loadFont('cmr12.egg')
    #text.setFont(cmr12)
    self.textNodePath = self.window.attachNewNode(text)
    #textNodePath = self.windowHeader.attachNewNode(text)
    #self.textNodePath.setPos( )
    self.textNodePath.setScale(0.07)
    
    self.windowHeader.bind(DGG.B1PRESS,self.startWindowDrag)
    # this is not handled correctly, if a window is dragged which has been
    # created before another it will not be released
    # check the bugfixed startWindowDrag function
    #self.windowHeader.bind(DGG.B1RELEASE,self.stopWindowDrag)
    
    # the content part of the window, put stuff beneath
    # contentWindow.getCanvas() to put it into it
    self.maxVirtualSize = maxSize
    self.minVirtualSize = minSize
    self.contentWindow = DirectScrolledFrame(
        parent      = self.window,
        pos         = ( 0, 0, -self.headerHeight ),
        canvasSize  = ( 0, self.maxVirtualSize[0], 0, self.maxVirtualSize[1] ),
        frameColor  = buttonColor,
        relief      = DGG.RAISED,
        borderWidth = (0,0)
        )
    
    # TODO: BUG THIS IS INVISIBLE
    DirectFrame.__init__( self,
        parent      = self.contentWindow.getCanvas(),
        pos         = ( 0, 0, 0 ),
        frameSize   = ( 0, self.maxVirtualSize[0], 0, self.maxVirtualSize[1] ),
        frameColor  = backgroundColor,
        relief      = DGG.RIDGE,
        borderWidth = ( .01, .01),
        )
    # BUGFIX FOR ABOVE
    self.backgroundColor = DirectFrame(
        parent      = self,
        frameSize   = ( 0, self.maxVirtualSize[0], 0, self.maxVirtualSize[1] ),
        frameColor  = backgroundColor,
        relief      = DGG.FLAT,
        borderWidth = ( .01, .01),
        )
    
    # the resize button of the window
    resizeSize = 0.08
    self.windowResize = DirectButton(
        parent      = self.window,
        frameSize   = ( -resizeSize, 0, resizeSize, 0),
        frameColor  = buttonColor,
        borderWidth = ( .005, .005),
        relief      = DGG.FLAT,
        )
    self.windowResize.bind(DGG.B1PRESS,self.startResizeDrag)
    self.windowResize.bind(DGG.B1RELEASE,self.stopResizeDrag)
    
    # offset then clicking on the resize button from the mouse to the resizebutton
    # position, required to calculate the position / scaling
    self.offset = None
    self.taskName = "resizeTask-%s" % str(hash(self))
    
    # do sizing of the window (minimum)
    self.resize( Vec3(0,0,0), Vec3(0,0,0) )
    # maximum
    #self.resize( Vec3(100,0,-100), Vec3(0,0,0) )
  
  # dragging functions
  def startWindowDrag( self, param ):
    self.window.wrtReparentTo( aspect2dMouseNode )
    self.window.ignoreAll()
    self.window.accept( 'mouse1-up', self.stopWindowDrag )
  def stopWindowDrag( self, param=None ):
    # this is called 2 times (bug), so make sure it's not already parented to aspect2d
    if self.window.getParent() != aspect2d:
      self.window.wrtReparentTo( aspect2d )
  
  # resize functions
  def resize( self, mPos, offset ):
    mXPos = max( min( mPos.getX(), self.maxVirtualSize[0] ), self.minVirtualSize[0])
    mZPos = max( min( mPos.getZ(), -self.minVirtualSize[1] ), -self.maxVirtualSize[1]-self.headerHeight)
    self.windowResize.setPos( mXPos, 0, mZPos ) #['pos'] = (mXPos, 0, mZPos)
    self.window['frameSize'] = (0, mXPos, 0, mZPos)
    #self.window.setScale( mXPos, 1, -mZPos )
    self.windowHeader.setPos( mXPos/2., 0, -self.headerHeight/2. )
    self.windowHeader.setScale( mXPos, 1, -self.headerHeight )
    self.contentWindow['frameSize'] = ( 0, mXPos, mZPos+self.headerHeight, 0)
    self.textNodePath.setPos( mXPos/2., 0, -self.headerHeight/3.*2. )
  def resizeTask( self, task=None ):
    mPos = aspect2dMouseNode.getPos( self.window )+self.offset
    self.resize( mPos, self.offset )
    return task.cont
  def startResizeDrag( self, param ):
    self.offset  = self.windowResize.getPos( aspect2dMouseNode )
    taskMgr.remove( self.taskName )
    taskMgr.add( self.resizeTask, self.taskName )
  def stopResizeDrag( self, param ):
    taskMgr.remove( self.taskName )




if __name__ == '__main__':
  bg1 = DirectButton(
      parent     = aspect2d,
      pos        = (.6,0,.6),
      #frameSize  = (0,0.2,0,0.2),
      frameSize  = (-.5,.5,-.5,.5),
      relief     = DGG.FLAT,
      frameColor = (0,1,1,1),
      geom       = DEFAULT_TITLE_GEOM,
      )
  bg2 = DirectButton(
      parent     = bg1,
      pos        = (.6,0,.6),
      #frameSize  = (0,0.2,0,0.2),
      frameSize  = (-.5,.5,-.5,.5),
      relief     = DGG.FLAT,
      frameColor = (0,1,1,1),
      geom       = DEFAULT_TITLE_GEOM,
      )
  
  # a first window
  window1 = DirectWindow( title='window1', pos = ( -.8, .8) )
  windowContent = DirectButton(
      parent     = window1,
      pos        = (.05,0,.05),
      frameSize  = (0,.9,0,.9),
      relief     = DGG.FLAT,
      frameColor = (0,1,0,1),
      )
  
  # a second window
  window2 = DirectWindow( title='window2', pos = ( -.4, .4), maxSize = (1.5,1.5) )
  windowContent = DirectButton(
      parent     = window2,
      pos        = (.6,0,.6),
      #frameSize  = (0,0.2,0,0.2),
      frameSize  = (-.5,.5,-.5,.5),
      relief     = DGG.FLAT,
      frameColor = (0,1,1,1),
      geom       = DEFAULT_TITLE_GEOM,
      )
  
  # a second window
  window3 = DirectWindow( title='window2', pos = ( 0, 0) )
  windowContent = DirectButton(
      parent     = window3,
      pos        = (.5,0,.5),
      frameSize  = (-.5,.5,-.5,.5),
      relief     = DGG.FLAT,
      frameColor = (0,1,1,1),
      #geom       = DEFAULT_TITLE_GEOM,
      scale      = 0.5,
      )
  
  run()

Sorry for the bit of thread necromancy, but one small improvement I threw in to the DirectWindow class:

def destroy(self):
    self.window.destroy()

edit: i lied, it causes an infinite loop. object.window.destroy has to be called directly

while thinking about doing another util using directGui i’ve digged out this old snipplet and made it a bit nicer:

http://www.nouser.org/WD/projects/sparetime/tools/directWindow/directWindow_2010_01_10_002.zip

  window = DirectWindow(
      # title
      title           = 'window1',
      # startup position
      pos             = ( -.8, .8 ),
      # maximum size of the window
      maxSize         = (1.5, 1.5),
      # minimum size of the window
      minSize         = (.25, .25),
      # startup size of the window
      curSize         = (1.5, .5),
      # you can guess ;-)
      backgroundColor = ( 1, 1, 1, 1),
      borderColor     = ( 1, 1, 1, 1),
      titleColor      = ( 1, 1, 1, 1),
      # should pressing the top-right corner close the window
      # also changes the visuals of it
      closeButton     = True,
      # size of the title
      titleSize       = 0.1,
      # size of the border (scrollbar's)
      borderSize      = 0.1,
    )
  # if you want to place something into the window
  # reparent to window.getCanvas()
  windowContent = DirectButton(
      parent     = window.getCanvas(),
      pos        = (.5,0,.5),
      frameSize  = (-.5,.5,-.5,.5),
      relief     = DGG.FLAT,
      frameColor = (0,1,1,1),
      scale      = 0.5,
    )
  # to destroy the window
  window.destroy()

wow. that’s cool!
thanks for sharing

very useful, thanks

if you have any requests what options or features could be added (as options), post them and i will look into it.

i am planning to create some other dgui-“wrappers” that make life easier when creating a panda3d gui.

Cool!
It was exactly what I search for.
I dl, and I will test soon.

Thanks!

One feature request from me, that the maxSize can be auto determined based on size of its contents. I plan on integrating chombee’s box packer (I’ll post the code when I get it working).

After an epiphany I figured out how to begin combining the boxing and this windowing system. I have a box parented to the directscrolledframe canvas and then some extra functions for packing the box. But when I packed the item for window1 I get

I’ve tried changing the position on the y acis of both the box and widget with no success. If I setcolor for the window to an alpha of 0 then the green button shows just fine and dandy. Any suggestions?

Code so far: croxis.dyndns.org/gui2.tar.gz

when using this you can see the buttons:

if __name__ == '__main__':
  # a first window
  window1 = DirectWindow(
      title           = 'window1',
      pos             = ( -.8, .8 ),
      backgroundColor = ( 0, 0, 1, .6),
      borderColor     = ( 1, 0, 0, .6),
      titleColor      = ( 0, 1, 0, .6),
      maxSize         = ( 2.0, 2.0),
      titleSize       = 0.1,
      borderSize      = 0.1,
    )
  
  from boxes import HBox, VBox, Box
  from random import random
  from pandac.PandaModules import *
  from direct.gui.DirectGui import *
  # Use the CardMaker to generate some nodepaths for flat,
  # card-like geometry.
  cm = CardMaker('cm')
  left,right,bottom,top = 0,2,0,-2
  width = right - left
  height = top - bottom
  cm.setFrame(left,right,bottom,top)

  hbox = HBox(margin=.05, parent=window1.getCanvas())
  hbox.setPos(0,0,2.0)
  for i in range(5):       
      vbox = VBox(margin=.05)
      for j in range(4):       
          np = aspect2d.attachNewNode(cm.generate())
          np.setScale(.2)
          np.setColor(random(),random(),random())
          another_vbox = VBox(margin=.05)
          dl = DirectLabel(text="I'm a label, look at me!",scale=.2)
          another_vbox.pack(dl)
          de = DirectEntry(initialText="I'm a text entry, write on me!",scale=.2,width=4,numLines=4)
          another_vbox.pack(de)
          db = DirectButton(text="I'm a button, click me!",scale=.2,relief=None)
          another_vbox.pack(db)
          another_vbox.reparentTo(np)
          vbox.pack(np)       
      hbox.pack(vbox)
       
  run() 

it’s just plaing using both modules on theyr own. the problem lies in the positioning of the buttons. hbox.setPos(0,0,2.0) maybe the canvasSize = ( 0, self.maxVirtualSize[0], 0, self.maxVirtualSize[1] ) in the window needs to be modifed.

for example:

        canvasSize                              = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0 ),

also background color needs to modified in this case:

        frameSize    = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0 ),

this way it doesnt need to have the position defined.

This makes no sense to me. Why would

box = VBox(parent = window.getCanvas())
box.pack(DirectButton)

work but

class Window:
    self.box = VBox(parent = self.getCanvas())

window.box.pack(DirectButton)

not work? Both are parented to the same thing, just one is stored as a variable in the class and the other is outside.

because i’ve changed the positioning of the inside canvas, nothing else.

Fixed the bug. Ended up I needed to make the box AFTER the background, not before. Ooops :blush:

Anyways. Here it is!

This requires boxes.py by chombee in the same directory.

To use simply create a window and add DirectGUI elements using the directwindow.addVertical or directwindow.addHorizontal functions. The window canvas will enlarge to incorporate all added items. An optional (and poorly named) directwindow.reset() function will resize the directwindow to show all items.

# -*- coding: utf-8 -*-
from pandac.PandaModules import TextNode, Vec3
from direct.gui.DirectGui import DirectFrame,DirectButton,DirectScrolledFrame,DGG,DirectLabel
import boxes
import direct.directbase.DirectStart

#from direct.gui.DirectGui import DirectLabel, DirectEntry
#from panda3d.core import Point3


# a task that keeps a node at the position of the mouse-cursor
def mouseNodeTask( task ):
  if base.mouseWatcherNode.hasMouse():
    x=base.mouseWatcherNode.getMouseX()
    y=base.mouseWatcherNode.getMouseY()
    # the mouse position is read relative to render2d, so set it accordingly
    aspect2dMouseNode.setPos( render2d, x, 0, y )
  return task.cont
# maybe we should check if aspect2d doesnt already contain the aspect2dMouseNode
aspect2dMouseNode = aspect2d.attachNewNode( 'aspect2dMouseNode', sort = 999999 )
taskMgr.add( mouseNodeTask, 'mouseNodeTask' )


DIRECTORY = 'windowBorders/'

DEFAULT_TITLE_GEOM_LEFT                 = loader.loadTexture( DIRECTORY+'titleLeft.png' )
DEFAULT_TITLE_GEOM_CENTER               = loader.loadTexture( DIRECTORY+'titleCenter.png' )
DEFAULT_TITLE_GEOM_RIGHT                = loader.loadTexture( DIRECTORY+'titleRight.png' )
DEFAULT_TITLE_GEOM_RIGHT_CLOSE          = loader.loadTexture( DIRECTORY+'titleRightClose.png' )
DEFAULT_RESIZE_GEOM                     = loader.loadTexture( DIRECTORY+'resize.png' )

VERTICALSCROLL_FRAMETEXTURE             = loader.loadTexture( DIRECTORY+'scrollVerticalBorder.png' )
VERTICALSCROLL_INCBUTTON_FRAMETEXTURE   = loader.loadTexture( DIRECTORY+'scrollVerticalDown.png' )
VERTICALSCROLL_DECBUTTON_FRAMETEXTURE   = loader.loadTexture( DIRECTORY+'scrollVerticalUp.png' )
VERTICALSCROLL_TUMB_FRAMETEXTURE        = loader.loadTexture( DIRECTORY+'scrollVerticalBar.png' )
HORIZONTALSCROLL_FRAMETEXTURE           = loader.loadTexture( DIRECTORY+'scrollHorizontalBorder.png' )
HORIZONTALSCROLL_INCBUTTON_FRAMETEXTURE = loader.loadTexture( DIRECTORY+'scrollHorizontalRight.png' )
HORIZONTALSCROLL_DECBUTTON_FRAMETEXTURE = loader.loadTexture( DIRECTORY+'scrollHorizontalLeft.png' )
HORIZONTALSCROLL_TUMB_FRAMETEXTURE      = loader.loadTexture( DIRECTORY+'scrollHorizontalBar.png' )

class DirectWindow( DirectFrame ):
  def __init__( self,
                pos              = ( -.5, .5),
                title            = 'Title',
                curSize          = ( 0.5, 0.5),
                maxSize          = ( 1, 1 ),
                minSize          = ( .5, .5 ),
                backgroundColor  = ( 1, 1, 1, 1 ),
                borderColor      = ( 1, 1, 1, 1 ),
                titleColor       = ( 1, 1, 1, 1 ),
                borderSize       = 0.04,
                titleSize        = 0.06,
                closeButton      = False,
              ):
    
    self.windowPos = pos
    DirectFrame.__init__( self,
        parent       = aspect2d,
        pos          = ( self.windowPos[0], 0, self.windowPos[1] ),
        frameColor  = ( 0, 0, 0, 0 ),
        frameTexture = loader.loadTexture( DIRECTORY+'transparent.png' )
      )
    self.setTransparency(True)
    
    # the title part of the window, drag around to move the window
    self.headerHeight = titleSize
    h = -self.headerHeight
    self.windowHeaderLeft = DirectButton(
        parent       = self,
        frameTexture = DEFAULT_TITLE_GEOM_LEFT,
        frameSize    = ( -.5, .5, -.5, .5 ),
        borderWidth  = ( 0, 0 ),
        relief       = DGG.FLAT,
        frameColor   = titleColor,
      )
    self.windowHeaderCenter = DirectButton(
        parent       = self,
        frameTexture = DEFAULT_TITLE_GEOM_CENTER,
        frameSize    = ( -.5, .5, -.5, .5 ),
        borderWidth  = ( 0, 0 ),
        relief       = DGG.FLAT,
        frameColor   = titleColor,
      )
    if closeButton:
      rightTitleGeom = DEFAULT_TITLE_GEOM_RIGHT_CLOSE
      command = self.destroy
    else:
      rightTitleGeom = DEFAULT_TITLE_GEOM_RIGHT
      command = None
    self.windowHeaderRight = DirectButton(
        parent       = self,
        frameTexture = rightTitleGeom,
        frameSize    = ( -.5, .5, -.5, .5 ),
        borderWidth  = ( 0, 0 ),
        relief       = DGG.FLAT,
        frameColor   = titleColor,
        command      = command
      )
    
    self.windowHeaderLeft.setTransparency(True)
    self.windowHeaderCenter.setTransparency(True)
    self.windowHeaderRight.setTransparency(True)
    
    self.windowHeaderLeft.bind( DGG.B1PRESS, self.startWindowDrag )
    self.windowHeaderCenter.bind( DGG.B1PRESS, self.startWindowDrag )
    self.windowHeaderRight.bind( DGG.B1PRESS, self.startWindowDrag )
    
    # this is not handled correctly, if a window is dragged which has been
    # created before another it will not be released
    # check the bugfixed startWindowDrag function
    #self.windowHeader.bind(DGG.B1RELEASE,self.stopWindowDrag)
    
    text = TextNode('WindowTitleTextNode')
    text.setText(title)
    text.setAlign(TextNode.ACenter)
    text.setTextColor( 0, 0, 0, 1 )
    text.setShadow(0.05, 0.05)
    text.setShadowColor( 1, 1, 1, 1 )
    self.textNodePath = self.attachNewNode(text)
    self.textNodePath.setScale(self.headerHeight*0.8)
    
    # the content part of the window, put stuff beneath
    # contentWindow.getCanvas() to put it into it
    self.maxVirtualSize = maxSize
    self.minVirtualSize = minSize
    self.resizeSize     = borderSize
    self.contentWindow = DirectScrolledFrame(
        parent                                  = self,
        pos                                     = ( 0, 0, -self.headerHeight ),
        canvasSize                              = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1],  0),
        frameColor                              = ( 0, 0, 0, 0), # defines the background color of the resize-button
        relief                                  = DGG.FLAT,
        borderWidth                             = (0, 0),
        verticalScroll_frameSize                = [0, self.resizeSize, 0, 1],
        horizontalScroll_frameSize              = [0, 1, 0, self.resizeSize],
        
        # resize the scrollbar according to window size
        verticalScroll_resizeThumb              = False,
        horizontalScroll_resizeThumb            = False,
        # define the textures for the scrollbars
        verticalScroll_frameTexture             = VERTICALSCROLL_FRAMETEXTURE,
        verticalScroll_incButton_frameTexture   = VERTICALSCROLL_INCBUTTON_FRAMETEXTURE,
        verticalScroll_decButton_frameTexture   = VERTICALSCROLL_DECBUTTON_FRAMETEXTURE,
        verticalScroll_thumb_frameTexture       = VERTICALSCROLL_TUMB_FRAMETEXTURE,
        horizontalScroll_frameTexture           = HORIZONTALSCROLL_FRAMETEXTURE,
        horizontalScroll_incButton_frameTexture = HORIZONTALSCROLL_INCBUTTON_FRAMETEXTURE,
        horizontalScroll_decButton_frameTexture = HORIZONTALSCROLL_DECBUTTON_FRAMETEXTURE,
        horizontalScroll_thumb_frameTexture     = HORIZONTALSCROLL_TUMB_FRAMETEXTURE,
        # make all flat, so the texture is as we want it
        verticalScroll_relief                   = DGG.FLAT,
        verticalScroll_thumb_relief             = DGG.FLAT,
        verticalScroll_decButton_relief         = DGG.FLAT,
        verticalScroll_incButton_relief         = DGG.FLAT,
        horizontalScroll_relief                 = DGG.FLAT,
        horizontalScroll_thumb_relief           = DGG.FLAT,
        horizontalScroll_decButton_relief       = DGG.FLAT,
        horizontalScroll_incButton_relief       = DGG.FLAT,
        # colors
        verticalScroll_frameColor               = borderColor,
        verticalScroll_incButton_frameColor     = borderColor,
        verticalScroll_decButton_frameColor     = borderColor,
        verticalScroll_thumb_frameColor         = borderColor,
        horizontalScroll_frameColor             = borderColor,
        horizontalScroll_incButton_frameColor   = borderColor,
        horizontalScroll_decButton_frameColor   = borderColor,
        horizontalScroll_thumb_frameColor       = borderColor,
      )
    self.contentWindow.setTransparency(True)
    
    # background color
    self.backgroundColor = DirectFrame(
        parent       = self.contentWindow.getCanvas(),
        frameSize    = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0 ),
        frameColor   = backgroundColor,
        relief       = DGG.FLAT,
        borderWidth  = ( .01, .01),
      )
    self.backgroundColor.setTransparency(True)
    
    # Add a box
    self.box = boxes.VBox(parent = self.getCanvas())
    
    # is needed for some nicer visuals of the resize button (background)
    self.windowResizeBackground = DirectButton(
        parent       = self,
        frameSize    = ( -.5, .5, -.5, .5 ),
        borderWidth  = ( 0, 0 ),
        scale        = ( self.resizeSize, 1, self.resizeSize ),
        relief       = DGG.FLAT,
        frameColor   = backgroundColor,
      )
    # the resize button of the window
    self.windowResize = DirectButton(
        parent       = self,
        frameSize    = ( -.5, .5, -.5, .5 ),
        borderWidth  = ( 0, 0 ),
        scale        = ( self.resizeSize, 1, self.resizeSize ),
        relief       = DGG.FLAT,
        frameTexture = DEFAULT_RESIZE_GEOM,
        frameColor   = borderColor,
      )
    self.windowResize.setTransparency(True)
    self.windowResize.bind(DGG.B1PRESS,self.startResizeDrag)
    self.windowResize.bind(DGG.B1RELEASE,self.stopResizeDrag)
    
    # offset then clicking on the resize button from the mouse to the resizebutton
    # position, required to calculate the position / scaling
    self.offset = None
    self.taskName = "resizeTask-%s" % str(hash(self))
    
    # do sizing of the window (minimum)
    #self.resize( Vec3(0,0,0), Vec3(0,0,0) )
    # maximum
    #self.resize( Vec3(100,0,-100), Vec3(0,0,0) )
    self.resize( Vec3(curSize[0], 0, -curSize[1]), Vec3(0,0,0))
  
  def getCanvas(self):
    return self.contentWindow.getCanvas()
  
  # dragging functions
  def startWindowDrag( self, param ):
    self.wrtReparentTo( aspect2dMouseNode )
    self.ignoreAll()
    self.accept( 'mouse1-up', self.stopWindowDrag )
  def stopWindowDrag( self, param=None ):
    # this is called 2 times (bug), so make sure it's not already parented to aspect2d
    if self.getParent() != aspect2d:
      self.wrtReparentTo( aspect2d )
  
  # resize functions
  def resize( self, mPos, offset ):
    mXPos = max( min( mPos.getX(), self.maxVirtualSize[0] ), self.minVirtualSize[0])
    mZPos = max( min( mPos.getZ(), -self.minVirtualSize[1] ), -self.maxVirtualSize[1]-self.headerHeight)
    self.windowResize.setPos( mXPos-self.resizeSize/2., 0, mZPos+self.resizeSize/2. )
    self.windowResizeBackground.setPos( mXPos-self.resizeSize/2., 0, mZPos+self.resizeSize/2. )
    self['frameSize'] = (0, mXPos, 0, mZPos)
    self.windowHeaderLeft.setPos( self.headerHeight/2., 0, -self.headerHeight/2. )
    self.windowHeaderLeft.setScale( self.headerHeight, 1, self.headerHeight )
    self.windowHeaderCenter.setPos( mXPos/2., 0, -self.headerHeight/2. )
    self.windowHeaderCenter.setScale( mXPos - self.headerHeight*2., 1, self.headerHeight )
    self.windowHeaderRight.setPos( mXPos-self.headerHeight/2., 0, -self.headerHeight/2. )
    self.windowHeaderRight.setScale( self.headerHeight, 1, self.headerHeight )
    self.contentWindow['frameSize'] = ( 0, mXPos, mZPos+self.headerHeight, 0)
    self.textNodePath.setPos( mXPos/2., 0, -self.headerHeight/3.*2. )
    # show and hide that small background for the window sizer
    if mXPos == self.maxVirtualSize[0] and \
       mZPos == -self.maxVirtualSize[1]-self.headerHeight:
      self.windowResizeBackground.hide()
      pass
    else:
      self.windowResizeBackground.show()
  
  def resizeTask( self, task=None ):
    mPos = aspect2dMouseNode.getPos( self )+self.offset
    self.resize( mPos, self.offset )
    return task.cont
  def startResizeDrag( self, param ):
    self.offset  = self.windowResize.getPos( aspect2dMouseNode )
    taskMgr.remove( self.taskName )
    taskMgr.add( self.resizeTask, self.taskName )
  def stopResizeDrag( self, param ):
    taskMgr.remove( self.taskName )
    # get the window to the front
    self.wrtReparentTo( aspect2d )

  def addHorizontal(self, widgets):
      """
      Accepts a list of directgui objects which are added to a horizontal box, which is then added to the vertical stack.
      """
      hbox = boxes.HBox()
      for widget in widgets:
          hbox.pack(widget)
      self.box.pack(hbox)
      self.updateMaxSize()
  
  def addVertical(self, widgets):
      """
      Accepts a list of directgui objects which are added to a vertical box, which is then added to the vertical stack.
      May cause funky layout results.
      """
      #vbox = boxes.VBox()
      for widget in widgets:
          self.box.pack(widget)
      self.updateMaxSize()
  
  def add(self, widgets):
      """Shortcut function for addVertical"""
      self.addVertical(widgets)
  
  def updateMaxSize(self):
      """Updates the max canvas size to include all items packed.
      Window is resized to show all contents."""
      bottomLeft, topRight = self.box.getTightBounds()
      self.maxVirtualSize = (topRight[0], -bottomLeft[2])
      self.contentWindow['canvasSize'] = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1],  0)
      self.backgroundColor['frameSize'] = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0 )
  
  def reset(self):
    """Poorly named function that resizes window to fit all contents"""
    
    self.resize( Vec3(self.maxVirtualSize[0], 0, -self.maxVirtualSize[1]), Vec3(0,0,0))

if __name__ == '__main__':
  # a first window
  window1 = DirectWindow(
      title           = 'window1',
      pos             = ( -.8, .8 ),
      backgroundColor = ( 0, 0, 1, .6),
      borderColor     = ( 1, 0, 0, .6),
      titleColor      = ( 0, 1, 0, .6),
      titleSize       = 0.1,
      borderSize      = 0.1,
    )
  windowContent = DirectButton(text = "Button1", scale=0.5, relief     = DGG.FLAT, frameColor = (0,1,0,1),)
  windowContent2 = DirectButton(text = "B2", scale=0.5, relief     = DGG.FLAT, frameColor = (0,1,0,1),)
  window1.addVertical([windowContent, windowContent2])
  
  # a second window
  window2 = DirectWindow(
      title            = 'window2',
      pos              = ( -.4, .4),
      backgroundColor  = (1,0,0,1),
    )
  windowContent = DirectLabel(text = 'Label1   ', frameColor = (0,1,1,1), scale=0.1)
  windowContent2 = DirectLabel(text = 'L2', frameColor = (0,1,1,1), scale=0.1)
  window2.addHorizontal([windowContent, windowContent2])
  
  # a third window with a close button featuring autoresize
  window3 = DirectWindow(
      title='window3',
      pos = ( 0, 0),
      closeButton=True
    )
  windowContent = DirectButton(
      text = "Press 1 to close me",
      relief     = DGG.FLAT,
      frameColor = (0,1,1,1),
      scale      = 0.05,
    )
  window3.add([windowContent])
  window3.reset()
  
  base.accept('1', window3.destroy)
  
  run()

[/code]

Just a couple modifications.

  1. Windows can now be parented to a node other than aspect2d though they still default to aspect2d. This lets you parent them all to a node under aspect2d to keep things organized and to show, hide, colorize, etc a logical group.
  2. Reset now works correctly, just a minor bugfix.
  3. After new items are added the window automatically resizes.
  4. Windows can optionally be prevented from being dragged off-screen irretrievably. Also optional is whether the entire window or just a part of the title bar must be preserved on the screen.

TODO: Better mouse picking, i.e. you shouldn’t be able to hit a button that’s covered by a window.



# original code by Hypnos, also includes code by Croixis
# https://discourse.panda3d.org/viewtopic.php?t=4861
# requires boxes.py by chombee
# https://discourse.panda3d.org/viewtopic.php?p=22420#22420
# Stephen Lujan, March 08 2010

from pandac.PandaModules import TextNode, Vec3
from direct.gui.DirectGui import DirectFrame,DirectButton,DirectScrolledFrame,DGG,DirectLabel 

import boxes
from direct.directbase.DirectStart import *

# a task that keeps a node at the position of the mouse-cursor
def mouseNodeTask( task ):
  if base.mouseWatcherNode.hasMouse():
    x=base.mouseWatcherNode.getMouseX()
    y=base.mouseWatcherNode.getMouseY()
    # the mouse position is read relative to render2d, so set it accordingly
    aspect2dMouseNode.setPos( render2d, x, 0, y )
    #render2dMouseNode.setPos( render2d, x, 0, y )
  return task.cont
# maybe we should check if aspect2d doesnt already contain the aspect2dMouseNode
aspect2dMouseNode = aspect2d.attachNewNode( 'aspect2dMouseNode', sort = 999999 )
#render2dMouseNode = render2d.attachNewNode( 'render2dMouseNode', sort = 999999 )
taskMgr.add( mouseNodeTask, 'mouseNodeTask' )


DIRECTORY = 'windowBorders/'

DEFAULT_TITLE_GEOM_LEFT                 = loader.loadTexture( DIRECTORY+'titleLeft.png' )
DEFAULT_TITLE_GEOM_CENTER               = loader.loadTexture( DIRECTORY+'titleCenter.png' )
DEFAULT_TITLE_GEOM_RIGHT                = loader.loadTexture( DIRECTORY+'titleRight.png' )
DEFAULT_TITLE_GEOM_RIGHT_CLOSE          = loader.loadTexture( DIRECTORY+'titleRightClose.png' )
DEFAULT_RESIZE_GEOM                     = loader.loadTexture( DIRECTORY+'resize.png' )

VERTICALSCROLL_FRAMETEXTURE             = loader.loadTexture( DIRECTORY+'scrollVerticalBorder.png' )
VERTICALSCROLL_INCBUTTON_FRAMETEXTURE   = loader.loadTexture( DIRECTORY+'scrollVerticalDown.png' )
VERTICALSCROLL_DECBUTTON_FRAMETEXTURE   = loader.loadTexture( DIRECTORY+'scrollVerticalUp.png' )
VERTICALSCROLL_TUMB_FRAMETEXTURE        = loader.loadTexture( DIRECTORY+'scrollVerticalBar.png' )
HORIZONTALSCROLL_FRAMETEXTURE           = loader.loadTexture( DIRECTORY+'scrollHorizontalBorder.png' )
HORIZONTALSCROLL_INCBUTTON_FRAMETEXTURE = loader.loadTexture( DIRECTORY+'scrollHorizontalRight.png' )
HORIZONTALSCROLL_DECBUTTON_FRAMETEXTURE = loader.loadTexture( DIRECTORY+'scrollHorizontalLeft.png' )
HORIZONTALSCROLL_TUMB_FRAMETEXTURE      = loader.loadTexture( DIRECTORY+'scrollHorizontalBar.png' )

class DirectWindow( DirectFrame ):
  def __init__( self,
                pos              = ( -.5, .5),
                title            = 'Title',
                curSize          = ( 1, 1),
                maxSize          = ( 1, 1 ),
                minSize          = ( .5, .5 ),
                backgroundColor  = ( 1, 1, 1, 1 ),
                borderColor      = ( 1, 1, 1, 1 ),
                titleColor       = ( 1, 1, 1, 1 ),
                borderSize       = 0.04,
                titleSize        = 0.06,
                closeButton      = False,
                windowParent     = aspect2d,
                preserve         = True,
                preserveWhole      = True,
              ):
    self.preserve = preserve
    self.preserveWhole = preserveWhole
    self.windowParent = windowParent
    self.windowPos = pos
    DirectFrame.__init__( self,
        parent       = windowParent,
        pos          = ( self.windowPos[0], 0, self.windowPos[1] ),
        frameColor  = ( 0, 0, 0, 0 ),
        frameTexture = loader.loadTexture( DIRECTORY+'transparent.png' )
      )
    self.setTransparency(True)
    
    # the title part of the window, drag around to move the window
    self.headerHeight = titleSize
    h = -self.headerHeight
    self.windowHeaderLeft = DirectButton(
        parent       = self,
        frameTexture = DEFAULT_TITLE_GEOM_LEFT,
        frameSize    = ( -.5, .5, -.5, .5 ),
        borderWidth  = ( 0, 0 ),
        relief       = DGG.FLAT,
        frameColor   = titleColor,
      )
    self.windowHeaderCenter = DirectButton(
        parent       = self,
        frameTexture = DEFAULT_TITLE_GEOM_CENTER,
        frameSize    = ( -.5, .5, -.5, .5 ),
        borderWidth  = ( 0, 0 ),
        relief       = DGG.FLAT,
        frameColor   = titleColor,
      )
    if closeButton:
      rightTitleGeom = DEFAULT_TITLE_GEOM_RIGHT_CLOSE
      command = self.destroy
    else:
      rightTitleGeom = DEFAULT_TITLE_GEOM_RIGHT
      command = None
    self.windowHeaderRight = DirectButton(
        parent       = self,
        frameTexture = rightTitleGeom,
        frameSize    = ( -.5, .5, -.5, .5 ),
        borderWidth  = ( 0, 0 ),
        relief       = DGG.FLAT,
        frameColor   = titleColor,
        command      = command
      )
    
    self.windowHeaderLeft.setTransparency(True)
    self.windowHeaderCenter.setTransparency(True)
    self.windowHeaderRight.setTransparency(True)
    
    self.windowHeaderLeft.bind( DGG.B1PRESS, self.startWindowDrag )
    self.windowHeaderCenter.bind( DGG.B1PRESS, self.startWindowDrag )
    self.windowHeaderRight.bind( DGG.B1PRESS, self.startWindowDrag )
    
    # this is not handled correctly, if a window is dragged which has been
    # created before another it will not be released
    # check the bugfixed startWindowDrag function
    #self.windowHeader.bind(DGG.B1RELEASE,self.stopWindowDrag)
    
    text = TextNode('WindowTitleTextNode')
    text.setText(title)
    text.setAlign(TextNode.ACenter)
    text.setTextColor( 0, 0, 0, 1 )
    text.setShadow(0.05, 0.05)
    text.setShadowColor( 1, 1, 1, 1 )
    self.textNodePath = self.attachNewNode(text)
    self.textNodePath.setScale(self.headerHeight*0.8)
    
    # the content part of the window, put stuff beneath
    # contentWindow.getCanvas() to put it into it
    self.maxVirtualSize = maxSize
    self.minVirtualSize = minSize
    self.resizeSize     = borderSize
    self.contentWindow = DirectScrolledFrame(
        parent                                  = self,
        pos                                     = ( 0, 0, -self.headerHeight ),
        canvasSize                              = ( 0, self.maxVirtualSize[0], 0, self.maxVirtualSize[1] ),
        frameColor                              = ( 0, 0, 0, 0), # defines the background color of the resize-button
        relief                                  = DGG.FLAT,
        borderWidth                             = (0, 0),
        verticalScroll_frameSize                = [0, self.resizeSize, 0, 1],
        horizontalScroll_frameSize              = [0, 1, 0, self.resizeSize],
        
        # resize the scrollbar according to window size
        verticalScroll_resizeThumb              = False,
        horizontalScroll_resizeThumb            = False,
        # define the textures for the scrollbars
        verticalScroll_frameTexture             = VERTICALSCROLL_FRAMETEXTURE,
        verticalScroll_incButton_frameTexture   = VERTICALSCROLL_INCBUTTON_FRAMETEXTURE,
        verticalScroll_decButton_frameTexture   = VERTICALSCROLL_DECBUTTON_FRAMETEXTURE,
        verticalScroll_thumb_frameTexture       = VERTICALSCROLL_TUMB_FRAMETEXTURE,
        horizontalScroll_frameTexture           = HORIZONTALSCROLL_FRAMETEXTURE,
        horizontalScroll_incButton_frameTexture = HORIZONTALSCROLL_INCBUTTON_FRAMETEXTURE,
        horizontalScroll_decButton_frameTexture = HORIZONTALSCROLL_DECBUTTON_FRAMETEXTURE,
        horizontalScroll_thumb_frameTexture     = HORIZONTALSCROLL_TUMB_FRAMETEXTURE,
        # make all flat, so the texture is as we want it
        verticalScroll_relief                   = DGG.FLAT,
        verticalScroll_thumb_relief             = DGG.FLAT,
        verticalScroll_decButton_relief         = DGG.FLAT,
        verticalScroll_incButton_relief         = DGG.FLAT,
        horizontalScroll_relief                 = DGG.FLAT,
        horizontalScroll_thumb_relief           = DGG.FLAT,
        horizontalScroll_decButton_relief       = DGG.FLAT,
        horizontalScroll_incButton_relief       = DGG.FLAT,
        # colors
        verticalScroll_frameColor               = borderColor,
        verticalScroll_incButton_frameColor     = borderColor,
        verticalScroll_decButton_frameColor     = borderColor,
        verticalScroll_thumb_frameColor         = borderColor,
        horizontalScroll_frameColor             = borderColor,
        horizontalScroll_incButton_frameColor   = borderColor,
        horizontalScroll_decButton_frameColor   = borderColor,
        horizontalScroll_thumb_frameColor       = borderColor,
      )
    self.contentWindow.setTransparency(True)


    # background color
    self.backgroundColor = DirectFrame(
        parent       = self.contentWindow.getCanvas(),
        frameSize    = ( 0, self.maxVirtualSize[0], 0, self.maxVirtualSize[1] ),
        frameColor   = backgroundColor,
        relief       = DGG.FLAT,
        borderWidth  = ( .01, .01),
      )
    self.backgroundColor.setTransparency(True)

    # Add a box
    self.box = boxes.VBox(parent = self.getCanvas())

    
    # is needed for some nicer visuals of the resize button (background)
    self.windowResizeBackground = DirectButton(
        parent       = self,
        frameSize    = ( -.5, .5, -.5, .5 ),
        borderWidth  = ( 0, 0 ),
        scale        = ( self.resizeSize, 1, self.resizeSize ),
        relief       = DGG.FLAT,
        frameColor   = backgroundColor,
      )

    # the resize button of the window
    self.windowResize = DirectButton(
        parent       = self,
        frameSize    = ( -.5, .5, -.5, .5 ),
        borderWidth  = ( 0, 0 ),
        scale        = ( self.resizeSize, 1, self.resizeSize ),
        relief       = DGG.FLAT,
        frameTexture = DEFAULT_RESIZE_GEOM,
        frameColor   = borderColor,
      )
    self.windowResize.setTransparency(True)
    self.windowResize.bind(DGG.B1PRESS,self.startResizeDrag)
    self.windowResize.bind(DGG.B1RELEASE,self.stopResizeDrag)
    
    # offset then clicking on the resize button from the mouse to the resizebutton
    # position, required to calculate the position / scaling
    self.offset = None
    self.taskName = "resizeTask-%s" % str(hash(self))
    
    # do sizing of the window (minimum)
    #self.resize( Vec3(0,0,0), Vec3(0,0,0) )
    # maximum
    #self.resize( Vec3(100,0,-100), Vec3(0,0,0) )
    self.resize( Vec3(curSize[0], 0, -curSize[1]), Vec3(0,0,0))
  
  def getCanvas(self):
    return self.contentWindow.getCanvas()
  
  # dragging functions
  def startWindowDrag( self, param ):
    self.wrtReparentTo( aspect2dMouseNode )
    self.ignoreAll()
    self.accept( 'mouse1-up', self.stopWindowDrag )
  def stopWindowDrag( self, param=None ):
    # this is called 2 times (bug), so make sure it's not already parented to aspect2d
    if self.getParent() != self.windowParent:
      self.wrtReparentTo( self.windowParent )
    if self.preserve:
        if self.preserveWhole:
            if self.getZ() > 1:
                self.setZ(1)
            elif self.getZ() < -1 - self.getHeight():
                self.setZ(-1 - self.getHeight())
            if self.getX() > base.a2dRight - self.getWidth():
                self.setX(base.a2dRight - self.getWidth())
            elif self.getX() < base.a2dLeft:
                self.setX(base.a2dLeft)
        else:
            if self.getZ() > 1:
                self.setZ(1)
            elif self.getZ() < -1 + self.headerHeight:
                self.setZ(-1 + self.headerHeight)
            if self.getX() > base.a2dRight - self.headerHeight:
                self.setX(base.a2dRight - self.headerHeight)
            elif self.getX() < base.a2dLeft + self.headerHeight - self.getWidth():
                self.setX(base.a2dLeft + self.headerHeight - self.getWidth())
    #else: #Window moved beyond reach. Destroy window?
  # resize functions
  def resize( self, mPos, offset ):
    mXPos = max( min( mPos.getX(), self.maxVirtualSize[0] ), self.minVirtualSize[0])
    mZPos = max( min( mPos.getZ(), -self.minVirtualSize[1] ), -self.maxVirtualSize[1]-self.headerHeight)
    self.windowResize.setPos( mXPos-self.resizeSize/2., 0, mZPos+self.resizeSize/2. )
    self.windowResizeBackground.setPos( mXPos-self.resizeSize/2., 0, mZPos+self.resizeSize/2. )
    self['frameSize'] = (0, mXPos, 0, mZPos)
    self.windowHeaderLeft.setPos( self.headerHeight/2., 0, -self.headerHeight/2. )
    self.windowHeaderLeft.setScale( self.headerHeight, 1, self.headerHeight )
    self.windowHeaderCenter.setPos( mXPos/2., 0, -self.headerHeight/2. )
    self.windowHeaderCenter.setScale( mXPos - self.headerHeight*2., 1, self.headerHeight )
    self.windowHeaderRight.setPos( mXPos-self.headerHeight/2., 0, -self.headerHeight/2. )
    self.windowHeaderRight.setScale( self.headerHeight, 1, self.headerHeight )
    self.contentWindow['frameSize'] = ( 0, mXPos, mZPos+self.headerHeight, 0)
    self.textNodePath.setPos( mXPos/2., 0, -self.headerHeight/3.*2. )
    # show and hide that small background for the window sizer
    if mXPos == self.maxVirtualSize[0] and \
       mZPos == -self.maxVirtualSize[1]-self.headerHeight:
      self.windowResizeBackground.hide()
    else:
      self.windowResizeBackground.show()
  
  def resizeTask( self, task=None ):
    mPos = aspect2dMouseNode.getPos( self )+self.offset
    self.resize( mPos, self.offset )
    return task.cont
  def startResizeDrag( self, param ):
    self.offset  = self.windowResize.getPos( aspect2dMouseNode )
    taskMgr.remove( self.taskName )
    taskMgr.add( self.resizeTask, self.taskName )
  def stopResizeDrag( self, param ):
    taskMgr.remove( self.taskName )
    # get the window to the front
    self.wrtReparentTo( self.windowParent )
  def addHorizontal(self, widgets):
      """
      Accepts a list of directgui objects which are added to a horizontal box, which is then added to the vertical stack.
      """
      hbox = boxes.HBox()
      for widget in widgets:
          hbox.pack(widget)
      self.box.pack(hbox)
      self.updateMaxSize()
 
  def addVertical(self, widgets):
      """
      Accepts a list of directgui objects which are added to a vertical box, which is then added to the vertical stack.
      May cause funky layout results.
      """
      #vbox = boxes.VBox()
      for widget in widgets:
          self.box.pack(widget)
      self.updateMaxSize()
 
  def add(self, widgets):
      """Shortcut function for addVertical"""
      self.addVertical(widgets)
 
  def updateMaxSize(self):
      """Updates the max canvas size to include all items packed.
      Window is resized to show all contents."""
      bottomLeft, topRight = self.box.getTightBounds()
      self.maxVirtualSize = (topRight[0], -bottomLeft[2])
      self.contentWindow['canvasSize'] = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1],  0)
      self.backgroundColor['frameSize'] = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0 )

      #perhaps this should be optional -- automatically resize for new elements
      self.reset()
 
  def reset(self):
    """Poorly named function that resizes window to fit all contents"""
    self.resize( Vec3(self.maxVirtualSize[0], 0, -self.maxVirtualSize[1]-self.headerHeight), Vec3(0,0,0))

def windowDemo():
    WindowSet = aspect2d.attachNewNode("Gui Set 1")
    #WindowSet = render2d.attachNewNode("Gui Set 1")


    # a first window
    window1 = DirectWindow(
      title           = 'window1',
      pos             = ( -.8, .8 ),
      backgroundColor = ( 0.1, 0.1, 0.1, .6),
      borderColor     = ( 0.5, 0.5, 0.5, .7),
      titleColor      = ( 0.5, 0.5, 0.5, .6),
      titleSize       = 0.1,
      borderSize      = 0.06,
      windowParent    = WindowSet,
    )
    windowContent = DirectButton(text = "Button1", scale=0.5, relief     = DGG.FLAT, frameColor = (0,1,0,1),)
    windowContent2 = DirectButton(text = "B2", scale=0.5, relief     = DGG.FLAT, frameColor = (0,1,0,1),)
    window1.addVertical([windowContent, windowContent2])

    # a second window
    window2 = DirectWindow(
      title            = 'window2',
      pos              = ( -.4, .4),
      backgroundColor  = (1,0,0,0.5),
      windowParent     = WindowSet,
    )
    string1 = "Press tab to\ntoggle showing/hiding\nof all windows."
    windowContent = DirectLabel(text = string1, frameColor = (0,1,1,1), scale=0.1)
    windowContent2 = DirectLabel(text = 'L2', frameColor = (0,1,1,1), scale=0.8)
    window2.addHorizontal([windowContent, windowContent2])

    # a third window with a close button featuring autoresize
    window3 = DirectWindow(
      title='window3',
      pos = ( 0, 0),
      closeButton=True,
      windowParent= WindowSet,
    )
    windowContent = DirectButton(
      text = "Press 1 to close me",
      relief     = DGG.FLAT,
      frameColor = (0,1,1,1),
      scale      = 0.05,
    )
    window3.add([windowContent])

    base.accept('1', window3.destroy)

    def toggleShow():
        if WindowSet.isHidden():
            WindowSet.show()
        else:
            WindowSet.hide()
    base.accept('tab', toggleShow)

# small scale test
windowDemo()
run()

edit - I was stupid never mind

Hi,

Im not sure if i should write that here or in another thread. Please move it if its wrong.

How can i delete the whole window content without deleting the window it self?

i’d suggest you add a DirectFrame to the window, and attach all your own objects to that directFrame. when you delete it, all objects attached to it should be gone, while the window stays intact.