class GuiFrame(DirectObject, HasKeybinds): #should be able to show/hide, do conditional show hide #position where you want #parent to other frames TEXT_MAGIC_NUMBER = .833333333334 #5/6 ?!? DRAW_ORDER={ 'frame':('unsorted',0), 'frame_bg':('unsorted', 0), 'items':('unsorted', 0), 'title':('unsorted', 0), 'border':('unsorted', 0), } def __init__(self, title, shortcut = None, # XXX obsolete, but needs a non deco replacement x = 0, y = .1, width = .2, height = .8, #scale = .05, # there is some black magic here :/ bdr_thickness = 2, bdr_color = (.1, .1, .1, 1), bg_color = (.7, .7, .7, .5), text_color = (0, 0, 0, 1), text_font = TextNode.getDefaultFont(), #text_h = .05, # do not use directly text_height_mm = 4, items = tuple(), ): #item_w_pad = 1 #item_h_pad = 1 self.title = title self.do_xywh(x, y, width, height) self.bdr_thickness = bdr_thickness # FIXME ?? self.bdr_color = bdr_color self.bg_color = bg_color self.text_color = text_color self.text_font = text_font self.text_height_mm = text_height_mm #set up variables self.__winx__ = base.win.getXSize() self.__winy__ = base.win.getYSize() self.__ar__ = base.camLens.getAspectRatio() self.__was_dragging__ = False self.__first_item__ = None self.__add_head__ = None self.items = OrderedDict() # ordered dict to allow sequential addition #self.BT = buttonThrower if buttonThrower else base.buttonThrowers[0].node() self.BT = base.buttonThrowers[0].node() # get our aspect ratio, and pixels per mm self.pixels_per_mm = render.getPythonTag('system_data')['max_ppmm'] self.getWindowData() self.accept('window-event', self.getWindowData) #set the text height using the above data self.setTextHeight() # get the root for all frames in the scene self.frameRoot = aspect2d.find('frameRoot') if not self.frameRoot: self.frameRoot = aspect2d.attachNewNode('frameRoot') # create the parent node for this frame #parent = self.frameRoot.find('frame-*') #if not parent: #parent = self.frameRoot self.frame = self.frameRoot.attachNewNode('frame-%s-%s'%(title, id(self))) self.frame.setBin(*self.DRAW_ORDER['frame']) # background l,r,b,t = 0, self.width, 0, self.height self.frame_bg = DirectFrame(parent=self.frame, frameColor=self.bg_color, pos=LVecBase3f(self.x, 0, self.y), frameSize=(l,r,b,t), state=DGG.NORMAL, # FIXME framesize is >_< suppressMouse=1) self.frame_bg.setBin(*self.DRAW_ORDER['frame_bg']) # border self.__make_border__(self.frame_bg, self.bdr_thickness, self.bdr_color, l, r, b, t) # setup for items self.itemsParent = self.frame_bg.attachNewNode('items parent') # title self.title_button = self.__create_item__(title, self.title_toggle_vis) # add any items that we got for item in items: self.__create_item__(*item) # FIXME when we call frame adjust we will loose the record of any data items # dragging self.title_button.bind(DGG.B1PRESS, self.__startDrag) self.title_button.bind(DGG.B1RELEASE, self.__stopDrag) # raise if we click the frame background self.frame_bg.bind(DGG.B1PRESS, self.raise_) #self.frame_bg.bind(DGG.B1RELEASE, self.__stopDrag) # this can cause problems w/ was dragging # toggle vis if shortcut: self.accept(shortcut, self.toggle_vis) # adjust the frame self.frame_adjust() @property def text_s(self): return self.text_h * self.TEXT_MAGIC_NUMBER def setTextHeight(self): h_units = 2 * base.a2dTop units_per_pixel = h_units / self.__winy__ text_h = self.text_height_mm * self.pixels_per_mm * units_per_pixel self.text_h = text_h def do_xywh(self, x, y, w, h): """ makes negative wneg xidths and heights work as well as negative x and y (bottom right is 0) """ if x < 0: x = 1 + x if y < 0: y = 1 + y if w < 0: x, w = x + w, -w if h < 0: y, h = y + h, -h self.x = self.fix_x(x) # for top left self.y = self.fix_y(y) # for top left self.width = self.fix_w(w) self.height = self.fix_h(h) def getWindowData(self, window=None): x = base.win.getXSize() y = base.win.getYSize() if x != self.__winx__ or y != self.__winy__: self.__ar__ = base.camLens.getAspectRatio() # w/h self.__winx__ = x self.__winy__ = y self.frame_adjust() def raise_(self, *args): """ function that raises windows call FIRST inside any function that should raise """ self.frame.reparentTo(self.frameRoot) # self.frame doesn't move so no wrt def frame_adjust(self): # FIXME sometimes this fails to call, also calls too often at startup self.setTextHeight() MI = self.getMaxItems() # does not count title >_< LI = len(self.items) DI = MI - LI if DI >= 0: for i in range(DI+1): self.__create_item__(' blank') else: for i in range(-(DI+1)): k,v = self.items.popitem() # remove the last nodes in order v.removeNode() # FIXME consider keeping these around? for k,b in self.items.items(): if k == 'title': if self.frame_bg.isHidden(): x, y, z = self.frame_bg.getPos() self.title_button.setPos(LVecBase3f(x, y , z-self.text_h)) else: self.title_button.setPos(LVecBase3f(0, 0, -self.text_h)) elif k == self.__first_item__: b.setPos(LVecBase3f(0, 0, -(self.text_h * 2))) else: b.setPos(LVecBase3f(0, 0, -self.text_h)) b['frameSize'] = 0, self.width, 0, self.text_h b['text_scale'] = self.text_s, self.text_s b['text_pos'] = 0, self.text_h - self.TEXT_MAGIC_NUMBER * self.text_s def getWindowSize(self, event=None): # TODO see if we really need this self.__winx__ = base.win.getXSize() self.__winy__ = base.win.getYSize() m = max(self.__winx__, self.__winy__) self.__xscale__ = self.__winx__ / m self.__yscale__ = self.__winy__ / m # put origin in top left and positive down and right @staticmethod def fix_x(x): return (x - .5) * 2 # TODO * base.a2dLeft? @staticmethod def fix_y(y): return (y - .5) * -2 # TODO * base.a2dTop? @staticmethod def fix_w(n): return n * 2 @staticmethod def fix_h(n): return -n * 2 def add_item(self, text, command = None, args = tuple()): args = list(args) if text[0] != ' ': text = ' '+text items = list(self.items) last_slot = len(self.items) if self.__add_head__ == last_slot: print('all slots are full, cannot add item to %s'%self) return None button = self.items[items[self.__add_head__]] button['text'] = text button['command'] = command button['extraArgs'] = args + button['extraArgs'] # blank buttons always have [self,id] self.__add_head__ += 1 def __create_item__(self, text, command = None, args = tuple()): args = list(args) #if not len(self.items): #parent = self.frame if len(self.items) <= 1: parent = self.itemsParent #everyone else parents off 2nd text else: parent = list(self.items.values())[-1] if command != None: def cmd(*args): """ any item should raise """ self.raise_() command(*args) else: cmd = self.raise_ b = DirectButton( parent=parent, frameColor=(1,1,1,.0), # a = 0 => no border overlap frameSize=(0, self.width, 0, self.text_h), text=' '+text, # hack to keep spacing from border text_font=self.text_font, text_fg=self.text_color, text_scale=self.text_s, text_pos=(0, self.text_h - self.TEXT_MAGIC_NUMBER * self.text_s), command=cmd, relief=DGG.FLAT, text_align=TextNode.ALeft, ) b.setPos(LVecBase3f(0, 0, -self.text_h)) b.setName('DirectButton-'+text) if not len(self.items): self.items['title'] = b b.setBin(*self.DRAW_ORDER['title']) else: b['extraArgs'] = args+[self, id(b)] b.node().setPythonTag('id', id(b)) b.setBin(*self.DRAW_ORDER['items']) if len(self.items) is 1: # the first item that is not the title b.setPos(LVecBase3f(0, 0, -(self.text_h * 2))) self.__first_item__ = id(b) self.items[id(b)] = b if text == ' blank': if self.__add_head__ is None: self.__add_head__ = 1 return b def del_all(self): if self.__first_item__ != None: for id_, button in self.items.items(): if id_ != 'title': button['text'] = ' blank' button['command'] = None button['extraArgs'] = [self, id_] self.__add_head__ = 1 def del_item(self, text): # FIXME uniqueness problems #d = self.itemsParent.find('*%s*'%text) if text[0] != ' ': text = ' '+text d = [i for i in self.items.values() if i.getName().count(text)] try: self.__del_item__(d[0].getPythonTag('id')) except IndexError: print('that item does not seem to exist') # if we have a name then there shouldn't be key errors def __del_item__(self, index): """ I have no idea how this is going to work """ out = self.items[index] p = out.getParent() if out.getNumChildren(): # avoid the printing of the AssertionError :/ c = out.getChild(0) c.reparentTo(p) if index == self.__first_item__: # XXX is fails, ints from id != c.setPos(LVecBase3f(out.getPos())) id_ = c.getPythonTag('id') self.__first_item__ = id_ out.setPos(LVecBase3f(0, 0, -self.text_h)) self.items.pop(index) parent = list(self.items.values())[-1] out['text'] = ' del blank' #out['command'] = None out['extraArgs'] = [self, index] out.reparentTo(parent) self.items[index] = out if self.__add_head__ > 1: # title is always at 0 self.__add_head__ -= 1 @classmethod def __make_border__(cls, parent, thickness, color, l, r , b, t): moveto_drawto = ( ((l,0,t), (l,0,b)), ((r,0,t), (r,0,b)), ((l,0,b), (r,0,b)), ((l,0,t), (r,0,t)), ) for moveto, drawto in moveto_drawto: Border = LineSegs() Border.setThickness(thickness) Border.setColor(*color) Border.moveTo(*moveto) Border.drawTo(*drawto) b = parent.attachNewNode(Border.create()) b.setBin(*cls.DRAW_ORDER['border']) def getMaxItems(self): return int(abs(self.height / self.text_h) - 1) @event_callback def toggle_vis(self): if self.frame_bg.isHidden(): self.frame_bg.show() self.raise_() else: self.frame_bg.hide() def title_toggle_vis(self): if not self.__was_dragging__: self.toggle_vis() if self.frame_bg.isHidden(): self.title_button.wrtReparentTo(self.frame) self.title_button['frameColor'] = (1, 1, 1, .5) # TODO else: self.title_button.wrtReparentTo(self.frame_bg) self.title_button['frameColor'] = (1, 1, 1, 0) # TODO else: self.__was_dragging__ = False def __startDrag(self, crap): self.raise_() self._ox, self._oy = base.mouseWatcherNode.getMouse() taskMgr.add(self.__drag,'dragging %s'%self.title) self.origBTprefix=self.BT.getPrefix() self.BT.setPrefix('dragging frame') def __drag(self, task): if base.mouseWatcherNode.hasMouse(): x, y = base.mouseWatcherNode.getMouse() if x != self._ox or y != self._oy: m_old = aspect2d.getRelativePoint(render2d, Point3(self._ox, self._oy, 0)) m_new = aspect2d.getRelativePoint(render2d, Point3(x, y, 0)) dx, dy, _ = m_new - m_old self.setPos(self.x + dx, self.y + dy) self._ox = x self._oy = y self.__was_dragging__ = True return task.cont def __stopDrag(self,crap): taskMgr.remove('dragging %s'%self.title) self.BT.setPrefix(self.origBTprefix) def setPos(self, x, y): """ actually sets the title button position since it is really the parent node """ self.x = x self.y = y #- self.text_h # FIXME is hard :/ self.frame_bg.setPos(LVecBase3f(x, 0, y)) if self.frame_bg.isHidden(): self.title_button.setPos(LVecBase3f(x, 0, y - self.text_h)) def __enter__(self): #load the position #load other saved state pass def __exit__(self): #save the position! #save other state pass
class GuiFrame(DirectObject, HasKeybinds): #should be able to show/hide, do conditional show hide #position where you want #parent to other frames TEXT_MAGIC_NUMBER = .833333333334 #5/6 ?!? DRAW_ORDER = { 'frame': ('unsorted', 0), 'frame_bg': ('unsorted', 0), 'items': ('unsorted', 0), 'title': ('unsorted', 0), 'border': ('unsorted', 0), } def __init__( self, title, shortcut=None, # XXX obsolete, but needs a non deco replacement x=0, y=.1, width=.2, height=.8, #scale = .05, # there is some black magic here :/ bdr_thickness=2, bdr_color=(.1, .1, .1, 1), bg_color=(.7, .7, .7, .5), text_color=(0, 0, 0, 1), text_font=TextNode.getDefaultFont(), #text_h = .05, # do not use directly text_height_mm=4, items=tuple(), ): #item_w_pad = 1 #item_h_pad = 1 self.title = title self.do_xywh(x, y, width, height) self.bdr_thickness = bdr_thickness # FIXME ?? self.bdr_color = bdr_color self.bg_color = bg_color self.text_color = text_color self.text_font = text_font self.text_height_mm = text_height_mm #set up variables self.__winx__ = base.win.getXSize() self.__winy__ = base.win.getYSize() self.__ar__ = base.camLens.getAspectRatio() self.__was_dragging__ = False self.__first_item__ = None self.__add_head__ = None self.items = OrderedDict() # ordered dict to allow sequential addition #self.BT = buttonThrower if buttonThrower else base.buttonThrowers[0].node() self.BT = base.buttonThrowers[0].node() # get our aspect ratio, and pixels per mm self.pixels_per_mm = render.getPythonTag('system_data')['max_ppmm'] self.getWindowData() self.accept('window-event', self.getWindowData) #set the text height using the above data self.setTextHeight() # get the root for all frames in the scene self.frameRoot = aspect2d.find('frameRoot') if not self.frameRoot: self.frameRoot = aspect2d.attachNewNode('frameRoot') # create the parent node for this frame #parent = self.frameRoot.find('frame-*') #if not parent: #parent = self.frameRoot self.frame = self.frameRoot.attachNewNode('frame-%s-%s' % (title, id(self))) self.frame.setBin(*self.DRAW_ORDER['frame']) # background l, r, b, t = 0, self.width, 0, self.height self.frame_bg = DirectFrame( parent=self.frame, frameColor=self.bg_color, pos=LVecBase3f(self.x, 0, self.y), frameSize=(l, r, b, t), state=DGG.NORMAL, # FIXME framesize is >_< suppressMouse=1) self.frame_bg.setBin(*self.DRAW_ORDER['frame_bg']) # border self.__make_border__(self.frame_bg, self.bdr_thickness, self.bdr_color, l, r, b, t) # setup for items self.itemsParent = self.frame_bg.attachNewNode('items parent') # title self.title_button = self.__create_item__(title, self.title_toggle_vis) # add any items that we got for item in items: self.__create_item__( *item ) # FIXME when we call frame adjust we will loose the record of any data items # dragging self.title_button.bind(DGG.B1PRESS, self.__startDrag) self.title_button.bind(DGG.B1RELEASE, self.__stopDrag) # raise if we click the frame background self.frame_bg.bind(DGG.B1PRESS, self.raise_) #self.frame_bg.bind(DGG.B1RELEASE, self.__stopDrag) # this can cause problems w/ was dragging # toggle vis if shortcut: self.accept(shortcut, self.toggle_vis) # adjust the frame self.frame_adjust() @property def text_s(self): return self.text_h * self.TEXT_MAGIC_NUMBER def setTextHeight(self): h_units = 2 * base.a2dTop units_per_pixel = h_units / self.__winy__ text_h = self.text_height_mm * self.pixels_per_mm * units_per_pixel self.text_h = text_h def do_xywh(self, x, y, w, h): """ makes negative wneg xidths and heights work as well as negative x and y (bottom right is 0) """ if x < 0: x = 1 + x if y < 0: y = 1 + y if w < 0: x, w = x + w, -w if h < 0: y, h = y + h, -h self.x = self.fix_x(x) # for top left self.y = self.fix_y(y) # for top left self.width = self.fix_w(w) self.height = self.fix_h(h) def getWindowData(self, window=None): x = base.win.getXSize() y = base.win.getYSize() if x != self.__winx__ or y != self.__winy__: self.__ar__ = base.camLens.getAspectRatio() # w/h self.__winx__ = x self.__winy__ = y self.frame_adjust() def raise_(self, *args): """ function that raises windows call FIRST inside any function that should raise """ self.frame.reparentTo( self.frameRoot) # self.frame doesn't move so no wrt def frame_adjust( self ): # FIXME sometimes this fails to call, also calls too often at startup self.setTextHeight() MI = self.getMaxItems() # does not count title >_< LI = len(self.items) DI = MI - LI if DI >= 0: for i in range(DI + 1): self.__create_item__(' blank') else: for i in range(-(DI + 1)): k, v = self.items.popitem() # remove the last nodes in order v.removeNode() # FIXME consider keeping these around? for k, b in self.items.items(): if k == 'title': if self.frame_bg.isHidden(): x, y, z = self.frame_bg.getPos() self.title_button.setPos(LVecBase3f(x, y, z - self.text_h)) else: self.title_button.setPos(LVecBase3f(0, 0, -self.text_h)) elif k == self.__first_item__: b.setPos(LVecBase3f(0, 0, -(self.text_h * 2))) else: b.setPos(LVecBase3f(0, 0, -self.text_h)) b['frameSize'] = 0, self.width, 0, self.text_h b['text_scale'] = self.text_s, self.text_s b['text_pos'] = 0, self.text_h - self.TEXT_MAGIC_NUMBER * self.text_s def getWindowSize(self, event=None): # TODO see if we really need this self.__winx__ = base.win.getXSize() self.__winy__ = base.win.getYSize() m = max(self.__winx__, self.__winy__) self.__xscale__ = self.__winx__ / m self.__yscale__ = self.__winy__ / m # put origin in top left and positive down and right @staticmethod def fix_x(x): return (x - .5) * 2 # TODO * base.a2dLeft? @staticmethod def fix_y(y): return (y - .5) * -2 # TODO * base.a2dTop? @staticmethod def fix_w(n): return n * 2 @staticmethod def fix_h(n): return -n * 2 def add_item(self, text, command=None, args=tuple()): args = list(args) if text[0] != ' ': text = ' ' + text items = list(self.items) last_slot = len(self.items) if self.__add_head__ == last_slot: print('all slots are full, cannot add item to %s' % self) return None button = self.items[items[self.__add_head__]] button['text'] = text button['command'] = command button['extraArgs'] = args + button[ 'extraArgs'] # blank buttons always have [self,id] self.__add_head__ += 1 def __create_item__(self, text, command=None, args=tuple()): args = list(args) #if not len(self.items): #parent = self.frame if len(self.items) <= 1: parent = self.itemsParent #everyone else parents off 2nd text else: parent = list(self.items.values())[-1] if command != None: def cmd(*args): """ any item should raise """ self.raise_() command(*args) else: cmd = self.raise_ b = DirectButton( parent=parent, frameColor=(1, 1, 1, .0), # a = 0 => no border overlap frameSize=(0, self.width, 0, self.text_h), text=' ' + text, # hack to keep spacing from border text_font=self.text_font, text_fg=self.text_color, text_scale=self.text_s, text_pos=(0, self.text_h - self.TEXT_MAGIC_NUMBER * self.text_s), command=cmd, relief=DGG.FLAT, text_align=TextNode.ALeft, ) b.setPos(LVecBase3f(0, 0, -self.text_h)) b.setName('DirectButton-' + text) if not len(self.items): self.items['title'] = b b.setBin(*self.DRAW_ORDER['title']) else: b['extraArgs'] = args + [self, id(b)] b.node().setPythonTag('id', id(b)) b.setBin(*self.DRAW_ORDER['items']) if len(self.items) is 1: # the first item that is not the title b.setPos(LVecBase3f(0, 0, -(self.text_h * 2))) self.__first_item__ = id(b) self.items[id(b)] = b if text == ' blank': if self.__add_head__ is None: self.__add_head__ = 1 return b def del_all(self): if self.__first_item__ != None: for id_, button in self.items.items(): if id_ != 'title': button['text'] = ' blank' button['command'] = None button['extraArgs'] = [self, id_] self.__add_head__ = 1 def del_item(self, text): # FIXME uniqueness problems #d = self.itemsParent.find('*%s*'%text) if text[0] != ' ': text = ' ' + text d = [i for i in self.items.values() if i.getName().count(text)] try: self.__del_item__(d[0].getPythonTag('id')) except IndexError: print('that item does not seem to exist') # if we have a name then there shouldn't be key errors def __del_item__(self, index): """ I have no idea how this is going to work """ out = self.items[index] p = out.getParent() if out.getNumChildren(): # avoid the printing of the AssertionError :/ c = out.getChild(0) c.reparentTo(p) if index == self.__first_item__: # XXX is fails, ints from id != c.setPos(LVecBase3f(out.getPos())) id_ = c.getPythonTag('id') self.__first_item__ = id_ out.setPos(LVecBase3f(0, 0, -self.text_h)) self.items.pop(index) parent = list(self.items.values())[-1] out['text'] = ' del blank' #out['command'] = None out['extraArgs'] = [self, index] out.reparentTo(parent) self.items[index] = out if self.__add_head__ > 1: # title is always at 0 self.__add_head__ -= 1 @classmethod def __make_border__(cls, parent, thickness, color, l, r, b, t): moveto_drawto = ( ((l, 0, t), (l, 0, b)), ((r, 0, t), (r, 0, b)), ((l, 0, b), (r, 0, b)), ((l, 0, t), (r, 0, t)), ) for moveto, drawto in moveto_drawto: Border = LineSegs() Border.setThickness(thickness) Border.setColor(*color) Border.moveTo(*moveto) Border.drawTo(*drawto) b = parent.attachNewNode(Border.create()) b.setBin(*cls.DRAW_ORDER['border']) def getMaxItems(self): return int(abs(self.height / self.text_h) - 1) @event_callback def toggle_vis(self): if self.frame_bg.isHidden(): self.frame_bg.show() self.raise_() else: self.frame_bg.hide() def title_toggle_vis(self): if not self.__was_dragging__: self.toggle_vis() if self.frame_bg.isHidden(): self.title_button.wrtReparentTo(self.frame) self.title_button['frameColor'] = (1, 1, 1, .5) # TODO else: self.title_button.wrtReparentTo(self.frame_bg) self.title_button['frameColor'] = (1, 1, 1, 0) # TODO else: self.__was_dragging__ = False def __startDrag(self, crap): self.raise_() self._ox, self._oy = base.mouseWatcherNode.getMouse() taskMgr.add(self.__drag, 'dragging %s' % self.title) self.origBTprefix = self.BT.getPrefix() self.BT.setPrefix('dragging frame') def __drag(self, task): if base.mouseWatcherNode.hasMouse(): x, y = base.mouseWatcherNode.getMouse() if x != self._ox or y != self._oy: m_old = aspect2d.getRelativePoint( render2d, Point3(self._ox, self._oy, 0)) m_new = aspect2d.getRelativePoint(render2d, Point3(x, y, 0)) dx, dy, _ = m_new - m_old self.setPos(self.x + dx, self.y + dy) self._ox = x self._oy = y self.__was_dragging__ = True return task.cont def __stopDrag(self, crap): taskMgr.remove('dragging %s' % self.title) self.BT.setPrefix(self.origBTprefix) def setPos(self, x, y): """ actually sets the title button position since it is really the parent node """ self.x = x self.y = y #- self.text_h # FIXME is hard :/ self.frame_bg.setPos(LVecBase3f(x, 0, y)) if self.frame_bg.isHidden(): self.title_button.setPos(LVecBase3f(x, 0, y - self.text_h)) def __enter__(self): #load the position #load other saved state pass def __exit__(self): #save the position! #save other state pass
class Window(): texture = None def __init__(self, title_text, scale, parent=None, child=None, transparent=False, owner=None): self.title_text = title_text self.scale = scale self.title_size = settings.ui_font_size self.owner = owner self.child = None self.last_pos = None self.title_color = (1, 1, 1, 1) self.title_pad = tuple(self.scale * 2) if parent is None: parent = aspect2d self.parent = parent if transparent: frameColor = (0, 0, 0, 0) else: frameColor = (0.5, 0.5, 0.5, 1) self.pad = 0 self.event_handler = DirectObject() self.button_thrower = base.buttonThrowers[0].node() self.event_handler.accept("wheel_up-up", self.mouse_wheel_event, extraArgs = [-1]) self.event_handler.accept("wheel_down-up", self.mouse_wheel_event, extraArgs = [1]) self.scrollers = [] # if Window.texture is None: # Window.texture = loader.loadTexture('textures/futureui1.png') # image_scale = (scale[0] * Window.texture.get_x_size(), 1, scale[1] * Window.texture.get_y_size()) self.frame = DirectFrame(parent=parent, state=DGG.NORMAL, frameColor=frameColor)#, image=self.texture, image_scale=image_scale) self.title_frame = DirectFrame(parent=self.frame, state=DGG.NORMAL, frameColor=(.5, .5, .5, 1)) self.title = OnscreenText(text=self.title_text, style=Plain, fg=self.title_color, scale=tuple(self.scale * self.title_size), parent=self.title_frame, pos=(0, 0), align=TextNode.ALeft, font=None, mayChange=True) bounds = self.title.getTightBounds() self.title_frame['frameSize'] = [0, bounds[1][0] - bounds[0][0] + self.title_pad[0] * 2, 0, bounds[1][2] - bounds[0][2] + self.title_pad[1] * 2] self.title.setPos( -bounds[0][0] + self.title_pad[0], -bounds[0][2] + self.title_pad[1]) self.close_frame = DirectFrame(parent=self.frame, state=DGG.NORMAL, frameColor=(.5, .5, .5, 1)) self.close = OnscreenText(text='X', style=Plain, fg=self.title_color, scale=tuple(self.scale * self.title_size), parent=self.close_frame, pos=(0, 0), align=TextNode.ACenter, font=None, mayChange=True) bounds = self.close.getTightBounds() self.close_frame['frameSize'] = [0, bounds[1][0] - bounds[0][0] + self.title_pad[0] * 2, self.title_frame['frameSize'][2], self.title_frame['frameSize'][3]] self.close.setPos( -bounds[0][0] + self.title_pad[0], -bounds[0][2] + self.title_pad[1]) self.frame.setPos(0, 0, 0) self.title_frame.bind(DGG.B1PRESS, self.start_drag) self.title_frame.bind(DGG.B1RELEASE, self.stop_drag) self.close_frame.bind(DGG.B1PRESS, self.close_window) self.set_child(child) def set_child(self, child): if child is not None: self.child = child child.reparent_to(self.frame) self.update() def update(self): if self.child is not None: frame_size = list(self.child.frame['frameSize']) if frame_size is not None: frame_size[0] -= self.pad frame_size[1] += self.pad frame_size[2] += self.pad frame_size[3] -= self.pad self.frame['frameSize'] = frame_size if self.frame['frameSize'] is not None: width = self.frame['frameSize'][1] - self.frame['frameSize'][0] title_size = self.title_frame['frameSize'] title_size[0] = 0 title_size[1] = width self.title_frame['frameSize'] = title_size self.close_frame.setPos(width - self.close_frame['frameSize'][1], 0, 0) def register_scroller(self, scroller): self.scrollers.append(scroller) def mouse_wheel_event(self, dir): # If the user is scrolling a scroll-bar, don't try to scroll the scrolled-frame too. region = base.mouseWatcherNode.getOverRegion() if region is not None: widget = base.render2d.find("**/*{0}".format(region.name)) if widget.is_empty() or isinstance(widget.node(), PGSliderBar) or isinstance(widget.getParent().node(), PGSliderBar): return # Get the mouse-position if not base.mouseWatcherNode.hasMouse(): return mouse_pos = base.mouseWatcherNode.getMouse() found_scroller = None # Determine whether any of the scrolled-frames are under the mouse-pointer for scroller in self.scrollers: bounds = scroller['frameSize'] pos = scroller.get_relative_point(base.render2d, Point3(mouse_pos.get_x() ,0, mouse_pos.get_y())) if pos.x > bounds[0] and pos.x < bounds[1] and \ pos.z > bounds[2] and pos.z < bounds[3]: found_scroller = scroller break if found_scroller is not None: if not found_scroller.verticalScroll.isHidden(): self.do_mouse_scroll(found_scroller.verticalScroll, dir, None) else: self.do_mouse_scroll(found_scroller.horizontalScroll, dir, None) def do_mouse_scroll(self, obj, dir, data): if isinstance(obj, DirectSlider) or isinstance(obj, DirectScrollBar): obj.setValue(obj.getValue() + dir * obj["pageSize"] * 0.1) def start_drag(self, event): if base.mouseWatcherNode.has_mouse(): mpos = base.mouseWatcherNode.get_mouse() self.drag_start = self.frame.parent.get_relative_point(render2d, Point3(mpos.get_x() ,0, mpos.get_y())) - self.frame.getPos() taskMgr.add(self.drag, "drag", -1) def drag(self, task): if base.mouseWatcherNode.has_mouse(): mpos = base.mouseWatcherNode.get_mouse() current_pos = self.frame.parent.get_relative_point(render2d, Point3(mpos.get_x() ,0, mpos.get_y())) self.frame.set_pos(current_pos - self.drag_start) return task.again def close_window(self, event=None): if self.owner is not None: self.owner.window_closed(self) self.destroy() def stop_drag(self, event): taskMgr.remove("drag") self.last_pos = self.frame.getPos() def destroy(self): if self.frame is not None: self.frame.destroy() self.frame = None self.scrollers = [] self.event_handler.ignore_all() def getPos(self): return self.frame.getPos() def setPos(self, pos): self.frame.setPos(pos)
class Window(): texture = None def __init__(self, title, scale, parent=None, child=None, transparent=False, owner=None): self.scale = scale self.owner = owner self.last_pos = None self.title_text = title self.title_color = (1, 1, 1, 1) self.title_pad = tuple(self.scale * 2) if parent is None: parent = aspect2d self.parent = parent if transparent: frameColor = (0, 0, 0, 0) else: frameColor = (0, 0, 0, 1) self.pad = 0 # if Window.texture is None: # Window.texture = loader.loadTexture('textures/futureui1.png') # image_scale = (scale[0] * Window.texture.get_x_size(), 1, scale[1] * Window.texture.get_y_size()) self.frame = DirectFrame( parent=parent, state=DGG.NORMAL, frameColor=frameColor ) #, image=self.texture, image_scale=image_scale) self.title_frame = DirectFrame(parent=self.frame, state=DGG.NORMAL, frameColor=(.5, .5, .5, 1)) self.title = OnscreenText(text=self.title_text, style=Plain, fg=self.title_color, scale=tuple(self.scale * 14), parent=self.title_frame, pos=(0, 0), align=TextNode.ALeft, font=None, mayChange=True) bounds = self.title.getTightBounds() self.title_frame['frameSize'] = [ 0, bounds[1][0] - bounds[0][0] + self.title_pad[0] * 2, 0, bounds[1][2] - bounds[0][2] + self.title_pad[1] * 2 ] self.title.setPos(-bounds[0][0] + self.title_pad[0], -bounds[0][2] + self.title_pad[1]) self.close_frame = DirectFrame(parent=self.frame, state=DGG.NORMAL, frameColor=(.5, .5, .5, 1)) self.close = OnscreenText(text='X', style=Plain, fg=self.title_color, scale=tuple(self.scale * 14), parent=self.close_frame, pos=(0, 0), align=TextNode.ACenter, font=None, mayChange=True) bounds = self.close.getTightBounds() self.close_frame['frameSize'] = [ 0, bounds[1][0] - bounds[0][0] + self.title_pad[0] * 2, self.title_frame['frameSize'][2], self.title_frame['frameSize'][3] ] self.close.setPos(-bounds[0][0] + self.title_pad[0], -bounds[0][2] + self.title_pad[1]) self.frame.setPos(0, 0, 0) self.title_frame.bind(DGG.B1PRESS, self.start_drag) self.title_frame.bind(DGG.B1RELEASE, self.stop_drag) self.close_frame.bind(DGG.B1PRESS, self.close_window) self.set_child(child) def set_child(self, child): if child is not None: self.child = child child.reparent_to(self.frame) self.update() def update(self): if self.child is not None: frame_size = self.child.frame['frameSize'] if frame_size is not None: frame_size[0] -= self.pad frame_size[1] += self.pad frame_size[2] += self.pad frame_size[3] -= self.pad self.frame['frameSize'] = frame_size if self.frame['frameSize'] is not None: width = self.frame['frameSize'][1] - self.frame['frameSize'][0] title_size = self.title_frame['frameSize'] title_size[0] = 0 title_size[1] = width self.title_frame['frameSize'] = title_size self.close_frame.setPos(width - self.close_frame['frameSize'][1], 0, 0) def start_drag(self, event): if base.mouseWatcherNode.has_mouse(): mpos = base.mouseWatcherNode.get_mouse() self.drag_start = self.frame.parent.get_relative_point( render2d, Point3(mpos.get_x(), 0, mpos.get_y())) - self.frame.getPos() taskMgr.add(self.drag, "drag", -1) def drag(self, task): if base.mouseWatcherNode.has_mouse(): mpos = base.mouseWatcherNode.get_mouse() current_pos = self.frame.parent.get_relative_point( render2d, Point3(mpos.get_x(), 0, mpos.get_y())) self.frame.set_pos(current_pos - self.drag_start) return task.again def close_window(self, event=None): if self.owner is not None: self.owner.window_closed(self) self.destroy() def stop_drag(self, event): taskMgr.remove("drag") self.last_pos = self.frame.getPos() def destroy(self): if self.frame is not None: self.frame.destroy() self.frame = None def getPos(self): return self.frame.getPos() def setPos(self, pos): self.frame.setPos(pos)
class DirectWindow( DirectFrame ): def __init__( self, pos = ( -.5, .5), title = 'Title', curSize = (1, 1), maxSize = ( 1, 1 ), minSize = ( .5, .5 ), backgroundColor = ( 0, 0, 1, .6), borderColor = ( 1, 1, 1, 1 ), titleColor = ( 1, 1, 1, 1 ), borderSize = 0.04, titleSize = 0.06, closeButton = False, resizeButton = True, windowParent = aspect2d, preserve = True, preserveWhole = True, ): self.preserve = preserve self.preserveWhole = preserveWhole self.windowParent = windowParent 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], 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 if resizeButton: 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) else: self.windowResize = DirectFrame() # 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)) self.widgets = [] 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: widget.setScale(0.05) hbox.pack(widget) self.widgets.append(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. """ for widget in widgets: widget.setScale(0.05) self.box.pack(widget) self.widgets.append(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]) if self.minVirtualSize[0] > self.maxVirtualSize[0]: self.minVirtualSize = (self.maxVirtualSize[0], self.minVirtualSize[1]) if self.minVirtualSize[1] > self.maxVirtualSize[1]: self.minVirtualSize = (self.minVirtualSize[0], self.maxVirtualSize[1]+self.headerHeight) self.contentWindow['canvasSize'] = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0) self.backgroundColor['frameSize'] = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0 ) # For CityMania Onlue self.reset() self.center() 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 center(self): """Centers window on screen""" self.setPos(-self.maxVirtualSize[0]/2.0, 0, (self.maxVirtualSize[1]+self.headerHeight)/2.0) def getEntries(self): """Returns the fields for any entires""" entries = [] for widget in self.widgets: if isinstance(widget, DirectEntry): entries.append(widget.get()) return entries def addScrolledList(self, items, numItemsVisible = 4, itemHeight = 1): '''Adds a list of items into a scrolled list''' scrolled_list = DirectScrolledList( decButton_pos= (0.35, 0, 5), decButton_text = "Up", decButton_borderWidth = (0.005, 0.005), incButton_pos= (0.35, 0, -5), incButton_text = "Down", incButton_borderWidth = (0.005, 0.005), frameSize = (-5, 5, -5, 5), frameColor = (1,0,1,0.5), pos = (-1, 0, 0), items = items, numItemsVisible = numItemsVisible, forceHeight = itemHeight, itemFrame_frameSize = (-5, 5, -5, 5), itemFrame_pos = (0.35, 0, 0.4), scale = (0.05), #parent = (aspect2d), ) scrolled_list.updateFrameStyle() self.add([scrolled_list])
class DirectWindow(DirectFrame): def __init__( self, pos=(-.5, .5), title='Title', curSize=(1, 1), maxSize=(1, 1), minSize=(.5, .5), backgroundColor=(0, 0, 1, .6), borderColor=(1, 1, 1, 1), titleColor=(1, 1, 1, 1), borderSize=0.04, titleSize=0.06, closeButton=False, resizeButton=True, windowParent=aspect2d, preserve=True, preserveWhole=True, ): self.preserve = preserve self.preserveWhole = preserveWhole self.windowParent = windowParent 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], 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 if resizeButton: 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) else: self.windowResize = DirectFrame() # 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)) self.widgets = [] 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: widget.setScale(0.05) hbox.pack(widget) self.widgets.append(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. """ for widget in widgets: widget.setScale(0.05) self.box.pack(widget) self.widgets.append(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]) if self.minVirtualSize[0] > self.maxVirtualSize[0]: self.minVirtualSize = (self.maxVirtualSize[0], self.minVirtualSize[1]) if self.minVirtualSize[1] > self.maxVirtualSize[1]: self.minVirtualSize = (self.minVirtualSize[0], self.maxVirtualSize[1] + self.headerHeight) self.contentWindow['canvasSize'] = (0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0) self.backgroundColor['frameSize'] = (0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0) # For CityMania Onlue self.reset() self.center() 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 center(self): """Centers window on screen""" self.setPos(-self.maxVirtualSize[0] / 2.0, 0, (self.maxVirtualSize[1] + self.headerHeight) / 2.0) def getEntries(self): """Returns the fields for any entires""" entries = [] for widget in self.widgets: if isinstance(widget, DirectEntry): entries.append(widget.get()) return entries def addScrolledList(self, items, numItemsVisible=4, itemHeight=1): '''Adds a list of items into a scrolled list''' scrolled_list = DirectScrolledList( decButton_pos=(0.35, 0, 5), decButton_text="Up", decButton_borderWidth=(0.005, 0.005), incButton_pos=(0.35, 0, -5), incButton_text="Down", incButton_borderWidth=(0.005, 0.005), frameSize=(-5, 5, -5, 5), frameColor=(1, 0, 1, 0.5), pos=(-1, 0, 0), items=items, numItemsVisible=numItemsVisible, forceHeight=itemHeight, itemFrame_frameSize=(-5, 5, -5, 5), itemFrame_pos=(0.35, 0, 0.4), scale=(0.05), #parent = (aspect2d), ) scrolled_list.updateFrameStyle() self.add([scrolled_list])