def add_item(self, image=None, image_scale=0.15, image_pos=(0, 0, 0), title=None, title_pos=(0, 0, 0), text=None, text_pos=(0, 0, 0), value=None): '''Appends an item to the end of the list. It can hold an image, title and text. Image: Panda3d path eg. 'models/picture.jpg'. Value: The item has function 'get/set_value()' to work with individual values of an activated element. Value gets set to the item on adding it.''' item_nr = len(self.item_list) + 1 item_pos = self.i_start - (self.i_y + self.item_v_padding) * item_nr item = DirectFrame( parent=self.canvas, text=str(item_nr), # Abused as an ID tag text_fg=(0, 0, 0, 0), frameSize=self.i_size, frameColor=self.item_background, borderWidth=(0.01, 0.01), pos=(0, 0, item_pos), relief=DGG.FLAT, state=DGG.NORMAL, enableEdit=0, suppressMouse=0) item.bind(DGG.B1RELEASE, self._switch_active_item, [item]) if image is not None: OnscreenImage( # Add an Image image=image, pos=image_pos, scale=(1 * image_scale, 1, 1 * image_scale), parent=(item)) DirectLabel( parent=item, # Add a Title text=title, text_scale=self.i_y / 4, text_fg=(1, 1, 1, 1), text_align=TextNode.ALeft, frameColor=(0, 0, 0, 0), pos=title_pos) DirectLabel( parent=item, # Add a Text text=text, text_scale=self.i_y / 5, text_fg=(1, 1, 1, 1), text_align=TextNode.ALeft, frameColor=(0, 0, 0, 0), pos=text_pos) self.item_list.append(item) self.value_list.append(value)
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
def add_button(self, text, label_id, pos_x, pos_y, width=0.0, hight=0.1): if width == 0.0: for c in range(len(text) / 2): width += 0.08 ls = LineSegs("lines") ls.setColor(0, 1, 0, 1) ls.drawTo(-width / 2, 0, hight / 2) ls.drawTo(width / 2, 0, hight / 2) ls.drawTo(width / 2, 0, -hight / 2) ls.drawTo(-width / 2, 0, -hight / 2) ls.drawTo(-width / 2, 0, hight / 2) border = ls.create(False) border.setTag('back_ground', '1') array = GeomVertexArrayFormat() array.addColumn("vertex", 4, Geom.NTFloat32, Geom.CPoint) arr_format = GeomVertexFormat() arr_format.addArray(array) arr_format = GeomVertexFormat.registerFormat(arr_format) vdata = GeomVertexData('fill', arr_format, Geom.UHStatic) vdata.setNumRows(4) vertex = GeomVertexWriter(vdata, 'vertex') vertex.addData3f(-width / 2, 0, hight / 2) vertex.addData3f(width / 2, 0, hight / 2) vertex.addData3f(-width / 2, 0, -hight / 2) vertex.addData3f(width / 2, 0, -hight / 2) prim = GeomTristrips(Geom.UHStatic) prim.addVertex(0) prim.addVertex(1) prim.addVertex(2) prim.addVertex(3) geom = Geom(vdata) geom.addPrimitive(prim) node = GeomNode('gnode') node.addGeom(geom) nodePath = NodePath("button") nodePath.attachNewNode(node) nodePath.setPos(0, 0, 0) nodePath.setTag('button', '1') nodePath.setBin("unsorted", 0) nodePath.setDepthTest(False) nodePath.setColor(0, 0, 0, 1) nodePath.attachNewNode(border) nodePath1 = NodePath("button") nodePath1.attachNewNode(node) nodePath1.setPos(0, 0, 0) nodePath1.setTag('button1', '1') nodePath1.setBin("unsorted", 0) nodePath1.setDepthTest(False) nodePath1.setColor(0, 1, 0, 1) nodePath1.attachNewNode(border) button = DirectFrame(enableEdit=1, text=text, geom=nodePath, text_scale=0.05, text_fg=(0, 1, 0, 1), borderWidth=(1, 1), relief=None, text_pos=(0, -0.01, 0), textMayChange=1, state=DGG.NORMAL, parent=aspect2d) button.setPos(pos_x, 0, pos_y) button.bind(DGG.B1PRESS, button_click, [button]) button.bind(DGG.WITHIN, button_hover, [button]) button.bind(DGG.WITHOUT, button_no_hover, [button]) # button.resetFrameSize() # self.button.bind(DGG.WITHIN, self.onMouseHoverInFunction, [button, some_value1]) defines.ENTITIES[defines.ENTITY_ID] = { 'CATEGORY': 'button', 'BUTTON': button, 'NODE': nodePath, 'LABEL': label_id, 'STATUS': 0 } defines.ENTITY_ID += 1
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 ChoiceWidget(DirectFrame): notify = directNotify.newCategory("ChoiceWidget") def __init__(self, parent, options, pos=(0, 0, 0), command=None, widgetName="", choiceTextScale=0.08, desc="", settingKeyName=None, mode=AUTO, requirement=None): """ Generates an ordered choice widget with the specified parameters. Parameters: parent: Pretty much self-explanatory, this is the parent of the widget. If an object with a `book` attribute is passed in, it will use that instead. options: A list of options that the user can select with the GUI. pos: Pretty much self-explanatory. command: Function that should be executed whenever a game setting is updated. The newly saved choice is passed to the specified function. widgetName: The label shown to the left of the widget identifying what the widget is for. choiceTextScale: The scale of the text which displays which option the user has currently selected. desc: Optional description of what the choices displayed by this widget are for. settingKeyName: The name of the key inside of the game settings map that this choice widget works with. This MUST be set if trying to simulate a game setting changer widget. mode: This is the kind of widget this is going to be. Use one of the following: - AUTO: - The system will attempt to figure out what the type of choices are available. * 2 options automatically looks like a true/false widget * - MULTICHOICE: - This overrides the system in case there are two options but true/false functionality isn't wanted. - DEGREE: - This means that the choice widget deals with x in front of some sort of degree value that should - be stripped away when selecting choices. This is used for the antialiasing choice widget. """ self.requirement = requirement self.options = options self.command = command self.currentChoiceIndex = 0 self.origChoice = None self.userChoice = None self.settingKeyName = settingKeyName self.mode = mode # Let's update the options if we specified a setting key name. if self.settingKeyName and len(self.settingKeyName) > 0: settingsMgr = CIGlobals.getSettingsMgr() settingInst = settingsMgr.getSetting(self.settingKeyName) if not settingInst: raise ValueError("Setting \"{0}\" could not be found!".format( self.settingKeyName)) else: self.options = settingInst.getOptions() desc = settingInst.getDescription() widgetParent = parent if hasattr(parent, 'book'): widgetParent = parent.book DirectFrame.__init__(self, parent=widgetParent, pos=pos) bg = loader.loadModel('phase_3/models/gui/ChatPanel.bam') self.selFrame = DirectFrame(pos=(0.4, 0, 0), frameColor=(1.0, 1.0, 1.0, 1.0), image=bg, relief=None, image_scale=(0.22, 0.11, 0.11), image_pos=(-0.107, 0.062, 0.062), parent=self) self.choiceText = OnscreenText(text="Hello!", align=TextNode.ACenter, parent=self.selFrame, pos=(0, -0.01), scale=choiceTextScale) self.fwdBtn = CIGlobals.makeDirectionalBtn(1, self.selFrame, pos=(0.2, 0, 0), command=self.__goFwd) self.bckBtn = CIGlobals.makeDirectionalBtn(0, self.selFrame, pos=(-0.2, 0, 0), command=self.__goBck) self.lbl = OnscreenText(text=widgetName + ":", pos=(-0.7, 0, 0), align=TextNode.ALeft, parent=self) if len(desc) > 0: self.desc = OnscreenText(text=desc, pos=(0.0, -0.1, 0.0), parent=self.selFrame, scale=0.05, bg=DESC_BACKGROUND_COLOR, mayChange=False) self.desc.setBin('gui-popup', 40) self.desc.hide() # Let's bind our events on the selection frame for the description. self.selFrame['state'] = DGG.NORMAL self.selFrame.bind(DGG.ENTER, self.__setDescVisible, extraArgs=[True]) self.selFrame.bind(DGG.EXIT, self.__setDescVisible, extraArgs=[False]) self.initialiseoptions(ChoiceWidget) self.reset() bg.detachNode() del bg def reset(self): """ Resets the selected choice to the very first option, or, if representing choices for a game setting, resets the widget to the currently saved setting. """ # The index of the original display choice. destIndex = 0 if self.settingKeyName: # This widget is supposed to be used to change game settings. Let's lookup the currently saved game setting. self.origChoice = self.__getCurrentSetting().getValue() try: if self.mode == DEGREE and not self.origChoice == 0: destIndex = self.options.index('x{0}'.format( str(self.origChoice))) elif (self.mode == AUTO and len(self.options) == 2) or isinstance( self.origChoice, (int, long)): destIndex = int(self.origChoice) elif isinstance(self.origChoice, (list, tuple)): destIndex = self.options.index('{0}x{1}'.format( str(self.origChoice[0]), str(self.origChoice[1]))) elif self.origChoice in self.options: destIndex = self.options.index(self.origChoice) elif self.origChoice.title() in self.options: destIndex = self.options.index(self.origChoice.title()) except: # We couldn't determine the right index for the original choice. Let's ignore any errors and default # to the very first choice when we reset. pass else: # If this widget is not being used to simulate changing game settings, let's use the first value in options as the original choice. self.origChoice = self.options[0] self.userChoice = self.origChoice self.goto(destIndex) def saveSetting(self): """ If `settingKeyName` was set, this updates the game setting key with the choice selected with the widget. However, if `settingKeyName` was not set, it will send the command specified with the current user choice. """ willUpdateChoice = (self.userChoice != self.origChoice) if self.settingKeyName and willUpdateChoice: settingInst = CIGlobals.getSettingsMgr().getSetting( self.settingKeyName) if settingInst: settingInst.setValue(self.userChoice) self.reset() if self.command and willUpdateChoice: # Let's send the command with the newly saved choice. self.command(self.userChoice) def __getCurrentSetting(self): return CIGlobals.getSettingsMgr().getSetting(self.settingKeyName) def __setDescVisible(self, visible, _): if visible: CIGlobals.getRolloverSound().play() self.desc.show() else: self.desc.hide() def cleanup(self): if hasattr(self, 'choiceText'): self.choiceText.destroy() del self.choiceText if hasattr(self, 'fwdBtn'): self.fwdBtn.destroy() del self.fwdBtn if hasattr(self, 'bckBtn'): self.bckBtn.destroy() del self.bckBtn if hasattr(self, 'lbl'): self.lbl.destroy() del self.lbl if hasattr(self, 'selFrame'): self.selFrame.destroy() del self.selFrame if hasattr(self, 'desc'): self.desc.destroy() del self.desc del self.options del self.command del self.currentChoiceIndex del self.settingKeyName del self.origChoice del self.userChoice del self.mode del self.requirement self.destroy() def goto(self, index): self.currentChoiceIndex = index self.updateDirectionalBtns() self.__setCurrentData(False) def __setCurrentData(self, doCmd=True): self.choiceText.setText(self.options[self.currentChoiceIndex]) if (doCmd): # Let's update the internal user choice. if self.mode == AUTO and len(self.options) == 2: # If we only have two options, we must be working with on/off choices. self.userChoice = bool(self.currentChoiceIndex) elif self.mode == INDEX: self.userChoice = self.currentChoiceIndex elif self.mode == DEGREE or self.mode == RESOLUTION: # We're working with either a degree based option or a resolution option. data = self.options[self.currentChoiceIndex].split('x') if CIGlobals.isEmptyString(data[0]): # This is a degree-based option. if self.currentChoiceIndex != 0: self.userChoice = int(data[1]) else: self.userChoice = 0 else: # This is a screen resolution option. self.userChoice = [int(data[0]), int(data[1])] else: self.userChoice = self.options[self.currentChoiceIndex] def updateDirectionalBtns(self): if self.requirement and callable(self.requirement): if not self.requirement(): # The requirement to modify this choice widget was not met. for btn in [self.fwdBtn, self.bckBtn]: btn['state'] = DGG.DISABLED btn.setColorScale(DISABLED_COLOR) return self.fwdBtn['state'] = DGG.NORMAL self.bckBtn['state'] = DGG.NORMAL self.fwdBtn.setColorScale(1, 1, 1, 1) self.bckBtn.setColorScale(1, 1, 1, 1) if self.currentChoiceIndex == 0: self.bckBtn['state'] = DGG.DISABLED self.bckBtn.setColorScale(DISABLED_COLOR) elif self.currentChoiceIndex == len(self.options) - 1: self.fwdBtn['state'] = DGG.DISABLED self.fwdBtn.setColorScale(DISABLED_COLOR) def __goFwd(self): if self.currentChoiceIndex < len(self.options) - 1: self.currentChoiceIndex += 1 self.__setCurrentData() self.updateDirectionalBtns() def __goBck(self): if self.currentChoiceIndex > 0: self.currentChoiceIndex -= 1 self.__setCurrentData() self.updateDirectionalBtns()
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 Window(): __base: Final[ShowBase] __name: Final[str] __mouse1_press_callbacks: Final[List[Callable[[], None]]] __frame: Final[DirectFrame] __mouse_node: Final[NodePath] __zoom: float __visible: bool def __init__(self, base: ShowBase, name: str, mouse1_press_callbacks: List[Callable[[], None]], *args, **kwargs): self.__base = base self.__name = name self.__mouse1_press_callbacks = mouse1_press_callbacks self.__visible = True self.__zoom = 1 / 5 if 'frameSize' not in kwargs: kwargs['frameSize'] = (-.8, .8, -1., 1.) self.__frame = DirectFrame(*args, parent=self.__base.aspect2d, **kwargs) self.__frame['state'] = DGG.NORMAL self.__frame.bind(DGG.B1PRESS, self.__start_drag) self.__frame.bind(DGG.B1RELEASE, self.__stop_drag) self.__frame.set_scale(self.zoom) # set sort value to ensure when window is reparented to mouse node it is on top # of everything else. self.__mouse_node = self.__base.aspect2d.attach_new_node( name + '_mouse_node', sort=100000000) self.__base.taskMgr.add(self.__mouse_node_task, name + '_mouse_node_task') def __mouse_node_task(self, task): if self.__base.mouseWatcherNode.hasMouse(): x = self.__base.mouseWatcherNode.getMouseX() y = self.__base.mouseWatcherNode.getMouseY() self.__mouse_node.setPos(self.__base.render2d, x, 0, y) return task.cont def __start_drag(self, *discarded): for c in self.__mouse1_press_callbacks: c() self.__frame.wrt_reparent_to(self.__mouse_node) def __stop_drag(self, *discarded): if self.__frame.get_parent() != self.__base.aspect2d: self.__frame.wrt_reparent_to(self.__base.aspect2d) @property def frame(self) -> DirectFrame: return self.__frame def focus(self) -> None: self.__frame.detach_node() self.__frame.reparent_to(self.__base.aspect2d) def zoom_in(self): self.__zoom += 0.05 self.frame.set_scale(self.__zoom) def zoom_out(self): self.__zoom -= 0.05 self.frame.set_scale(self.__zoom) @property def zoom(self) -> float: return self.__zoom def toggle_visible(self): if self.__visible: self.frame.hide() else: self.frame.show() self.focus() self.__visible = not self.__visible @property def visible(self) -> bool: return self.__visible
class ColourPicker: pick_colour_callback: Callable[[Tuple[float, float, float, float]], None] __base: ShowBase __palette_img: PNMImage __palette_size: Tuple[int, int] __palette_frame: DirectFrame __marker: DirectFrame __marker_center: DirectFrame enabled: bool def __init__(self, base: ShowBase, pick_colour_callback: Callable[ [Tuple[float, float, float, float]], None], **kwargs) -> None: self.__base = base self.pick_colour_callback = pick_colour_callback self.enabled = True # PALETTE # palette_filename = os.path.join(GUI_DATA_PATH, "colour_palette.png") self.__palette_img = PNMImage( Filename.fromOsSpecific(palette_filename)) self.__palette_size = (self.__palette_img.getReadXSize(), self.__palette_img.getReadYSize()) self.__palette_frame = DirectFrame(image=palette_filename, **kwargs) self.__palette_frame['state'] = DGG.NORMAL self.__palette_frame.bind(DGG.B1PRESS, command=self.__pick) # MARKER # self.__marker = DirectFrame(parent=self.__palette_frame, frameColor=(0.0, 0.0, 0.0, 1.0), frameSize=(-0.08, .08, -.08, .08), pos=(0.0, 0.0, 0.0)) self.__marker_center = DirectFrame(parent=self.__marker, frameSize=(-0.03, 0.03, -0.03, 0.03)) self.__marker.hide() def __colour_at( self, x: float, y: float) -> Union[Tuple[float, float, float, float], None]: w, h = self.__palette_size screen = self.__base.pixel2d img_scale = self.__palette_frame['image_scale'] sx = self.__palette_frame.getSx(screen) * img_scale[0] sy = self.__palette_frame.getSz(screen) * img_scale[2] x -= self.__palette_frame.getX(screen) y -= self.__palette_frame.getZ(screen) x = (0.5 + x / (2.0 * sx)) * w y = (0.5 - y / (2.0 * sy)) * h if 0 <= x < w and 0 <= y < h: return (*self.__palette_img.getXel(int(x), int(y)), 1.0) else: return None def __update_marker_colour(self) -> Tuple[float, float, float, float]: c = self.colour_under_marker() if c is None: c = self.__marker_center['frameColor'] else: self.__marker_center['frameColor'] = c return c def __update_marker_pos(self) -> None: if not self.__base.mouseWatcherNode.hasMouse(): return None pointer = self.__base.win.get_pointer(0) x, y = pointer.getX(), -pointer.getY() w, h = self.__palette_size screen = self.__base.pixel2d img_scale = self.__palette_frame['image_scale'] sx = self.__palette_frame.getSx(screen) * img_scale[0] sy = self.__palette_frame.getSz(screen) * img_scale[2] x -= self.__palette_frame.getX(screen) y -= self.__palette_frame.getZ(screen) x /= sx y /= sy x = max(-0.92, min(0.92, x)) y = max(-0.92, min(0.92, y)) self.__marker.set_pos(x, 0.0, y) self.__marker.show() def colour_under_marker( self) -> Union[Tuple[float, float, float, float], None]: x, _, y = self.__marker.get_pos() w, h = self.__palette_size screen = self.__base.pixel2d img_scale = self.__palette_frame['image_scale'] sx = self.__palette_frame.getSx(screen) * img_scale[0] sy = self.__palette_frame.getSz(screen) * img_scale[2] x *= sx y *= sy x += self.__palette_frame.getX(screen) y += self.__palette_frame.getZ(screen) return self.__colour_at(x, y) def colour_under_mouse( self) -> Union[Tuple[float, float, float, float], None]: if not self.__base.mouseWatcherNode.hasMouse(): return None pointer = self.__base.win.get_pointer(0) return self.__colour_at(pointer.getX(), -pointer.getY()) def __pick(self, *args): if self.enabled: self.__update_marker_pos() self.pick_colour_callback(self.__update_marker_colour()) @property def frame(self) -> DirectFrame: return self.__palette_frame @property def marker(self) -> DirectFrame: return self.__marker
class ScrolledButtonsList(DirectObject): """ A class to display a list of selectable buttons. It is displayed using scrollable window (DirectScrolledFrame). """ def __init__(self, parent=None, frameSize=(.8,1.2), buttonTextColor=(1,1,1,1), font=None, itemScale=.045, itemTextScale=0.85, itemTextZ=0, command=None, contextMenu=None, autoFocus=0, colorChange=1, colorChangeDuration=1, newItemColor=globals.colors['guiblue1'], rolloverColor=globals.colors['guiyellow'], suppressMouseWheel=1, modifier='control'): self.mode = None self.focusButton=None self.command=command self.contextMenu=contextMenu self.autoFocus=autoFocus self.colorChange=colorChange self.colorChangeDuration=colorChangeDuration*.5 self.newItemColor=newItemColor self.rolloverColor=rolloverColor self.rightClickTextColors=(Vec4(0,1,0,1),Vec4(0,35,100,1)) self.font=font if font: self.fontHeight=font.getLineHeight() else: self.fontHeight=TextNode.getDefaultFont().getLineHeight() self.fontHeight*=1.2 # let's enlarge font height a little self.xtraSideSpace=.2*self.fontHeight self.itemTextScale=itemTextScale self.itemTextZ=itemTextZ self.buttonTextColor=buttonTextColor self.suppressMouseWheel=suppressMouseWheel self.modifier=modifier self.buttonsList=[] self.numItems=0 self.__eventReceivers={} # DirectScrolledFrame to hold items self.itemScale=itemScale self.itemVertSpacing=self.fontHeight*self.itemScale self.frameWidth,self.frameHeight=frameSize # I set canvas' Z size smaller than the frame to avoid the auto-generated vertical slider bar self.childrenFrame = DirectScrolledFrame( parent=parent,pos=(-self.frameWidth*.5,0,.5*self.frameHeight), relief=DGG.GROOVE, state=DGG.NORMAL, # to create a mouse watcher region frameSize=(0, self.frameWidth, -self.frameHeight, 0), frameColor=(0,0,0,.7), canvasSize=(0, 0, -self.frameHeight*.5, 0), borderWidth=(0.01,0.01), manageScrollBars=0, enableEdit=0, suppressMouse=0, sortOrder=1000 ) # the real canvas is "self.childrenFrame.getCanvas()", # but if the frame is hidden since the beginning, # no matter how I set the canvas Z pos, the transform would be resistant, # so just create a new node under the canvas to be my canvas self.canvas=self.childrenFrame.getCanvas().attachNewNode('myCanvas') # slider background SliderBG=DirectFrame( parent=self.childrenFrame,frameSize=(-.025,.025,-self.frameHeight,0), frameColor=(0,0,0,.7), pos=(-.03,0,0),enableEdit=0, suppressMouse=0) # slider thumb track sliderTrack = DirectFrame( parent=SliderBG, relief=DGG.FLAT, #state=DGG.NORMAL, frameColor=(1,1,1,.2), frameSize=(-.015,.015,-self.frameHeight+.01,-.01), enableEdit=0, suppressMouse=0) # page up self.pageUpRegion=DirectFrame( parent=SliderBG, relief=DGG.FLAT, state=DGG.NORMAL, frameColor=(1,.8,.2,.1), frameSize=(-.015,.015,0,0), enableEdit=0, suppressMouse=0) self.pageUpRegion.setAlphaScale(0) self.pageUpRegion.bind(DGG.B1PRESS,self.__startScrollPage,[-1]) self.pageUpRegion.bind(DGG.WITHIN,self.__continueScrollUp) self.pageUpRegion.bind(DGG.WITHOUT,self.__suspendScrollUp) # page down self.pageDnRegion=DirectFrame( parent=SliderBG, relief=DGG.FLAT, state=DGG.NORMAL, frameColor=(1,.8,.2,.1), frameSize=(-.015,.015,0,0), enableEdit=0, suppressMouse=0) self.pageDnRegion.setAlphaScale(0) self.pageDnRegion.bind(DGG.B1PRESS,self.__startScrollPage,[1]) self.pageDnRegion.bind(DGG.WITHIN,self.__continueScrollDn) self.pageDnRegion.bind(DGG.WITHOUT,self.__suspendScrollDn) self.pageUpDnSuspended=[0,0] # slider thumb self.vertSliderThumb=DirectButton(parent=SliderBG, relief=DGG.FLAT, frameColor=(1,1,1,.6), frameSize=(-.015,.015,0,0), enableEdit=0, suppressMouse=0, rolloverSound=None, clickSound=None) self.vertSliderThumb.bind(DGG.B1PRESS,self.__startdragSliderThumb) self.vertSliderThumb.bind(DGG.WITHIN,self.__enteringThumb) self.vertSliderThumb.bind(DGG.WITHOUT,self.__exitingThumb) self.oldPrefix=base.buttonThrowers[0].node().getPrefix() self.sliderThumbDragPrefix='draggingSliderThumb-' # GOD & I DAMN IT !!! # These things below don't work well if the canvas has a lot of buttons. # So I end up checking the mouse region every frame by myself using a continuous task. # self.accept(DGG.WITHIN+self.childrenFrame.guiId,self.__enteringFrame) # self.accept(DGG.WITHOUT+self.childrenFrame.guiId,self.__exitingFrame) self.isMouseInRegion=False self.mouseOutInRegionCommand=(self.__exitingFrame,self.__enteringFrame) taskMgr.doMethodLater(.2,self.__getFrameRegion,'getFrameRegion') def __getFrameRegion(self,t): for g in range(base.mouseWatcherNode.getNumGroups()): region=base.mouseWatcherNode.getGroup(g).findRegion(self.childrenFrame.guiId) if region!=None: self.frameRegion=region taskMgr.add(self.__mouseInRegionCheck,'mouseInRegionCheck') break def __mouseInRegionCheck(self,t): """ check if the mouse is within or without the scrollable frame, and upon within or without, run the provided command """ if not base.mouseWatcherNode.hasMouse(): return Task.cont m=base.mouseWatcherNode.getMouse() bounds=self.frameRegion.getFrame() inRegion=bounds[0]<m[0]<bounds[1] and bounds[2]<m[1]<bounds[3] if self.isMouseInRegion==inRegion: return Task.cont self.isMouseInRegion=inRegion self.mouseOutInRegionCommand[inRegion]() return Task.cont def __startdragSliderThumb(self,m=None): if self.mode != None: if hasattr(self.mode, 'enableMouseCamControl') == 1: if self.mode.enableMouseCamControl == 1: self.mode.game.app.disableMouseCamControl() mpos=base.mouseWatcherNode.getMouse() parentZ=self.vertSliderThumb.getParent().getZ(render2d) sliderDragTask=taskMgr.add(self.__dragSliderThumb,'dragSliderThumb') sliderDragTask.ZposNoffset=mpos[1]-self.vertSliderThumb.getZ(render2d)+parentZ # sliderDragTask.mouseX=base.winList[0].getPointer(0).getX() self.oldPrefix=base.buttonThrowers[0].node().getPrefix() base.buttonThrowers[0].node().setPrefix(self.sliderThumbDragPrefix) self.acceptOnce(self.sliderThumbDragPrefix+'mouse1-up',self.__stopdragSliderThumb) def __dragSliderThumb(self,t): if not base.mouseWatcherNode.hasMouse(): return mpos=base.mouseWatcherNode.getMouse() # newY=base.winList[0].getPointer(0).getY() self.__updateCanvasZpos((t.ZposNoffset-mpos[1])/self.canvasRatio) # base.winList[0].movePointer(0, t.mouseX, newY) return Task.cont def __stopdragSliderThumb(self,m=None): if self.mode != None: if hasattr(self.mode, 'enableMouseCamControl') == 1: if self.mode.enableMouseCamControl == 1: self.mode.game.app.enableMouseCamControl() taskMgr.remove('dragSliderThumb') self.__stopScrollPage() base.buttonThrowers[0].node().setPrefix(self.oldPrefix) if self.isMouseInRegion: self.mouseOutInRegionCommand[self.isMouseInRegion]() def __startScrollPage(self,dir,m): self.oldPrefix=base.buttonThrowers[0].node().getPrefix() base.buttonThrowers[0].node().setPrefix(self.sliderThumbDragPrefix) self.acceptOnce(self.sliderThumbDragPrefix+'mouse1-up',self.__stopdragSliderThumb) t=taskMgr.add(self.__scrollPage,'scrollPage',extraArgs=[int((dir+1)*.5),dir*.01/self.canvasRatio]) self.pageUpDnSuspended=[0,0] def __scrollPage(self,dir,scroll): if not self.pageUpDnSuspended[dir]: self.__scrollCanvas(scroll) return Task.cont def __stopScrollPage(self,m=None): taskMgr.remove('scrollPage') def __suspendScrollUp(self,m=None): self.pageUpRegion.setAlphaScale(0) self.pageUpDnSuspended[0]=1 def __continueScrollUp(self,m=None): if taskMgr.hasTaskNamed('dragSliderThumb'): return self.pageUpRegion.setAlphaScale(1) self.pageUpDnSuspended[0]=0 def __suspendScrollDn(self,m=None): self.pageDnRegion.setAlphaScale(0) self.pageUpDnSuspended[1]=1 def __continueScrollDn(self,m=None): if taskMgr.hasTaskNamed('dragSliderThumb'): return self.pageDnRegion.setAlphaScale(1) self.pageUpDnSuspended[1]=0 def __suspendScrollPage(self,m=None): self.__suspendScrollUp() self.__suspendScrollDn() def __enteringThumb(self,m=None): self.vertSliderThumb['frameColor']=(1,1,1,1) self.__suspendScrollPage() def __exitingThumb(self,m=None): self.vertSliderThumb['frameColor']=(1,1,1,.6) def __scrollCanvas(self,scroll): if self.vertSliderThumb.isHidden() or self.buttonsList == []: return self.__updateCanvasZpos(self.canvas.getZ()+scroll) def __updateCanvasZpos(self,Zpos): newZ=clampScalar(Zpos, .0, self.canvasLen-self.frameHeight+.015) self.canvas.setZ(newZ) thumbZ=-newZ*self.canvasRatio self.vertSliderThumb.setZ(thumbZ) self.pageUpRegion['frameSize']=(-.015,.015,thumbZ-.01,-.01) self.pageDnRegion['frameSize']=(-.015,.015,-self.frameHeight+.01,thumbZ+self.vertSliderThumb['frameSize'][2]) def __adjustCanvasLength(self,numItem): self.canvasLen=float(numItem)*self.itemVertSpacing self.canvasRatio=(self.frameHeight-.015)/(self.canvasLen+.01) if self.canvasLen<=self.frameHeight-.015: canvasZ=.0 self.vertSliderThumb.hide() self.pageUpRegion.hide() self.pageDnRegion.hide() self.canvasLen=self.frameHeight-.015 else: canvasZ=self.canvas.getZ() self.vertSliderThumb.show() self.pageUpRegion.show() self.pageDnRegion.show() self.__updateCanvasZpos(canvasZ) self.vertSliderThumb['frameSize']=(-.015,.015,-self.frameHeight*self.canvasRatio,-.01) thumbZ=self.vertSliderThumb.getZ() self.pageUpRegion['frameSize']=(-.015,.015,thumbZ-.01,-.01) self.pageDnRegion['frameSize']=(-.015,.015,-self.frameHeight+.01,thumbZ+self.vertSliderThumb['frameSize'][2]) def __acceptAndIgnoreWorldEvent(self,event,command,extraArgs=[]): receivers=messenger.whoAccepts(event) if receivers is None: self.__eventReceivers[event]={} else: self.__eventReceivers[event]=receivers.copy() for r in self.__eventReceivers[event].keys(): if type(r) != types.TupleType: r.ignore(event) self.accept(event,command,extraArgs) def __ignoreAndReAcceptWorldEvent(self,events): for event in events: self.ignore(event) if self.__eventReceivers.has_key(event): for r, method_xtraArgs_persist in self.__eventReceivers[event].items(): if type(r) != types.TupleType: messenger.accept(event,r,*method_xtraArgs_persist) self.__eventReceivers[event]={} def __enteringFrame(self,m=None): # sometimes the WITHOUT event for page down region doesn't fired, # so directly suspend the page scrolling here self.__suspendScrollPage() BTprefix=base.buttonThrowers[0].node().getPrefix() if BTprefix==self.sliderThumbDragPrefix: return self.inOutBTprefix=BTprefix if self.suppressMouseWheel: self.__acceptAndIgnoreWorldEvent(self.inOutBTprefix+'wheel_up', command=self.__scrollCanvas, extraArgs=[-.07]) self.__acceptAndIgnoreWorldEvent(self.inOutBTprefix+'wheel_down', command=self.__scrollCanvas, extraArgs=[.07]) else: self.accept(self.inOutBTprefix+self.modifier+'-wheel_up',self.__scrollCanvas, [-.07]) self.accept(self.inOutBTprefix+self.modifier+'-wheel_down',self.__scrollCanvas, [.07]) def __exitingFrame(self,m=None): if not hasattr(self,'inOutBTprefix'): return if self.suppressMouseWheel: self.__ignoreAndReAcceptWorldEvent( ( self.inOutBTprefix+'wheel_up', self.inOutBTprefix+'wheel_down', ) ) else: self.ignore(self.inOutBTprefix+self.modifier+'-wheel_up') self.ignore(self.inOutBTprefix+self.modifier+'-wheel_down') def __setFocusButton(self,button,item): if self.focusButton: self.restoreNodeButton2Normal() self.focusButton=button self.highlightNodeButton() if callable(self.command) and button in self.buttonsList: # run user command and pass the selected item, it's index, and the button self.command(item,self.buttonsList.index(button),button) def __rightPressed(self,button,m): self.__isRightIn=True # text0 : normal # text1 : pressed # text2 : rollover # text3 : disabled button._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rightClickTextColors[self.focusButton==button]) button.bind(DGG.B3RELEASE,self.__rightReleased,[button]) button.bind(DGG.WITHIN,self.__rightIn,[button]) button.bind(DGG.WITHOUT,self.__rightOut,[button]) def __rightIn(self,button,m): self.__isRightIn=True button._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rightClickTextColors[self.focusButton==button]) def __rightOut(self,button,m): self.__isRightIn=False button._DirectGuiBase__componentInfo['text2'][0].setColorScale(Vec4(1,1,1,1)) def __rightReleased(self,button,m): button.unbind(DGG.B3RELEASE) button.unbind(DGG.WITHIN) button.unbind(DGG.WITHOUT) button._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rolloverColor) if not self.__isRightIn: return if callable(self.contextMenu): # run user command and pass the selected item, it's index, and the button self.contextMenu(button['extraArgs'][1],self.buttonsList.index(button),button) def scrollToBottom(self): ##for i in range(0,self.numItems): self.__scrollCanvas(1) def selectButton(self, button, item): self.__setFocusButton(button, item) def restoreNodeButton2Normal(self): """ stop highlighting item """ if self.focusButton != None: #self.focusButton['text_fg']=(1,1,1,1) self.focusButton['frameColor']=(0,0,0,0) def highlightNodeButton(self,idx=None): """ highlight the item """ if idx is not None: self.focusButton=self.buttonsList[idx] #self.focusButton['text_fg']=(.01,.01,.01,1) # nice dark blue. don't mess with the text fg color though! we want it custom self.focusButton['frameColor']=(0,.3,.8,1) def clear(self): """ clear the list """ for c in self.buttonsList: c.remove() self.buttonsList=[] self.focusButton=None self.numItems=0 def addItem(self,text,extraArgs=None,atIndex=None,textColorName=None): """ add item to the list text : text for the button extraArgs : the object which will be passed to user command(s) (both command and contextMenu) when the button get clicked atIndex : where to add the item <None> : put item at the end of list <integer> : put item at index <integer> <button> : put item at <button>'s index textColorName : the color name eg. 'yellow' """ textColor = self.buttonTextColor if textColorName != None: textColor = globals.colors[textColorName] button = DirectButton(parent=self.canvas, scale=self.itemScale, relief=DGG.FLAT, frameColor=(0,0,0,0),text_scale=self.itemTextScale, text=text, text_pos=(0,self.itemTextZ),text_fg=textColor, text_font=self.font, text_align=TextNode.ALeft, command=self.__setFocusButton, enableEdit=0, suppressMouse=0, rolloverSound=None,clickSound=None) #button.setMyMode(self.mode) l,r,b,t=button.getBounds() # top & bottom are blindly set without knowing where exactly the baseline is, # but this ratio fits most fonts baseline=-self.fontHeight*.25 #button['saved_color'] = textColor button['frameSize']=(l-self.xtraSideSpace,r+self.xtraSideSpace,baseline,baseline+self.fontHeight) # Zc=NodePath(button).getBounds().getCenter()[1]-self.fontHeight*.5+.25 # # Zc=button.getCenter()[1]-self.fontHeight*.5+.25 # button['frameSize']=(l-self.xtraSideSpace,r+self.xtraSideSpace,Zc,Zc+self.fontHeight) button['extraArgs']=[button,extraArgs] button._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rolloverColor) button.bind(DGG.B3PRESS,self.__rightPressed,[button]) if isinstance(atIndex,DirectButton): if atIndex.isEmpty(): atIndex=None else: index=self.buttonsList.index(atIndex) self.buttonsList.insert(index,button) if atIndex==None: self.buttonsList.append(button) index=self.numItems elif type(atIndex)==IntType: index=atIndex self.buttonsList.insert(index,button) Zpos=(-.7-index)*self.itemVertSpacing button.setPos(.02,0,Zpos) if index!=self.numItems: for i in range(index+1,self.numItems+1): self.buttonsList[i].setZ(self.buttonsList[i],-self.fontHeight) self.numItems+=1 self.__adjustCanvasLength(self.numItems) if self.autoFocus: self.focusViewOnItem(index) if self.colorChange: Sequence( button.colorScaleInterval(self.colorChangeDuration,self.newItemColor,globals.colors['guiblue3']), button.colorScaleInterval(self.colorChangeDuration,Vec4(1,1,1,1),self.newItemColor) ).start() def focusViewOnItem(self,idx): """ Scroll the window so the newly added item will be displayed in the middle of the window, if possible. """ Zpos=(idx+.7)*self.itemVertSpacing-self.frameHeight*.5 self.__updateCanvasZpos(Zpos) def setAutoFocus(self,b): """ set auto-view-focus state of newly added item """ self.autoFocus=b def index(self,button): """ get the index of button """ if not button in self.buttonsList: return None return self.buttonsList.index(button) def getNumItems(self): """ get the current number of items on the list """ return self.numItems def disableItem(self,i): if not 0<=i<self.numItems: print 'DISABLING : invalid index (%s)' %i return self.buttonsList[i]['state']=DGG.DISABLED self.buttonsList[i].setColorScale(.3,.3,.3,1) def enableItem(self,i): if not 0<=i<self.numItems: print 'ENABLING : invalid index (%s)' %i return self.buttonsList[i]['state']=DGG.NORMAL self.buttonsList[i].setColorScale(1,1,1,1) def removeItem(self,index): if not 0<=index<self.numItems: print 'REMOVAL : invalid index (%s)' %index return if self.numItems==0: return if self.focusButton==self.buttonsList[index]: self.focusButton=None self.buttonsList[index].removeNode() del self.buttonsList[index] self.numItems-=1 for i in range(index,self.numItems): self.buttonsList[i].setZ(self.buttonsList[i],self.fontHeight) self.__adjustCanvasLength(self.numItems) def destroy(self): self.clear() self.__exitingFrame() self.ignoreAll() self.childrenFrame.removeNode() taskMgr.remove('mouseInRegionCheck') def hide(self): self.childrenFrame.hide() self.isMouseInRegion=False self.__exitingFrame() taskMgr.remove('mouseInRegionCheck') def show(self): self.childrenFrame.show() if not hasattr(self,'frameRegion'): taskMgr.doMethodLater(.2,self.__getFrameRegion,'getFrameRegion') elif not taskMgr.hasTaskNamed('mouseInRegionCheck'): taskMgr.add(self.__mouseInRegionCheck,'mouseInRegionCheck') def toggleVisibility(self): if self.childrenFrame.isHidden(): self.show() else: self.hide() def setMyMode(self, myMode): self.mode = myMode
class DropDownMenu(DirectObject): ALeft = 0 ACenter = 1 ARight = 2 ENone = 0 EFade = 1 ESlide = 2 EStretch = 3 PLeft = 0 PRight = 1 PBottom = 2 PTop = 3 parents = (('a2dBottomLeft', 'a2dLeftCenter', 'a2dTopLeft'), ('a2dTopRight', 'a2dRightCenter', 'a2dBottomRight'), ('a2dBottomLeft', 'a2dBottomCenter', 'a2dBottomRight'), ('a2dTopLeft', 'a2dTopCenter', 'a2dTopRight')) def __init__(self, items, parent=None, sidePad=.0, edgePos=PTop, align=ALeft, effect=ENone, buttonThrower=None, font=None, baselineOffset=.0, scale=.05, itemHeight=1., leftPad=.0, separatorHeight=.5, underscoreThickness=1, BGColor=(0, 0, 0, .7), BGBorderColor=(1, .85, .4, 1), separatorColor=(1, 1, 1, 1), frameColorHover=(1, .85, .4, 1), frameColorPress=(0, 1, 0, 1), textColorReady=(1, 1, 1, 1), textColorHover=(0, 0, 0, 1), textColorPress=(0, 0, 0, 1), textColorDisabled=(.5, .5, .5, 1), draggable=False, onMove=None): ''' sidePad : additional space on the left and right of the text item edgePos : menu bar position on the screen, use DropDownMenu.PLeft, PRight, PBottom, or PTop align : menu items alignment on menu bar, use DropDownMenu.ALeft, ACenter, or ARight effect : the drop down appearance effect, use DropDownMenu.ENone, EFade, ESlide, or EStretch draggable : menu bar's draggability status onMove : a function which will be called after changing edge position Read the remaining options documentation in PopupMenu class. ''' self.parent = parent if parent else getattr( base, DropDownMenu.parents[edgePos][align]) self.BT = buttonThrower if buttonThrower else base.buttonThrowers[ 0].node() self.menu = self.parent.attachNewNode('dropdownmenu-%s' % id(self)) self.font = font if font else TextNode.getDefaultFont() self.baselineOffset = baselineOffset self.scale = scale self.itemHeight = itemHeight self.sidePad = sidePad self.edgePos = edgePos self.alignment = align self.effect = effect self.leftPad = leftPad self.underscoreThickness = underscoreThickness self.separatorHeight = separatorHeight self.BGColor = BGColor self.BGBorderColor = BGBorderColor self.separatorColor = separatorColor self.frameColorHover = frameColorHover self.frameColorPress = frameColorPress self.textColorReady = textColorReady self.textColorHover = textColorHover self.textColorPress = textColorPress self.textColorDisabled = textColorDisabled self.draggable = draggable self.onMove = onMove self.dropDownMenu = self.whoseDropDownMenu = None self.gapFromEdge = gapFromEdge = .008 texMargin = self.font.getTextureMargin() * self.scale * .25 b = DirectButton(parent=NodePath(''), text='^|g_', text_font=self.font, scale=self.scale) fr = b.node().getFrame() b.getParent().removeNode() baselineToCenter = (fr[2] + fr[3]) * self.scale LH = (fr[3] - fr[2]) * self.itemHeight * self.scale baselineToTop = (fr[3] * self.itemHeight * self.scale / LH) / (1. + self.baselineOffset) baselineToBot = LH / self.scale - baselineToTop self.height = LH + .01 l, r, b, t = 0, 5, -self.height, 0 self.menuBG = DirectFrame(parent=self.menu, frameColor=BGColor, frameSize=(l, r, b, t), state=DGG.NORMAL, suppressMouse=1) if self.draggable: self.setDraggable(1) LSborder = LineSegs() LSborder.setThickness(2) LSborder.setColor(0, 0, 0, 1) LSborder.moveTo(l, 0, b) LSborder.drawTo(r, 0, b) self.menuBG.attachNewNode(LSborder.create()) self.itemsParent = self.menu.attachNewNode('menu items parent') x = sidePad * self.scale + gapFromEdge for t, menuItemsGenerator in items: underlinePos = t.find('_') t = t.replace('_', '') b = DirectButton( parent=self.itemsParent, text=t, text_font=self.font, pad=(sidePad, 0), scale=self.scale, pos=(x, 0, -baselineToTop * self.scale - gapFromEdge), text_fg=textColorReady, # text color when mouse over text2_fg=textColorHover, # text color when pressed text1_fg=textColorPress, # framecolor when pressed frameColor=frameColorPress, command=self.__createMenu, extraArgs=[True, menuItemsGenerator], text_align=TextNode.ALeft, relief=DGG.FLAT, rolloverSound=0, clickSound=0) b['extraArgs'] += [b.getName()] b.stateNodePath[2].setColor( *frameColorHover) # framecolor when mouse over b.stateNodePath[0].setColor(0, 0, 0, 0) # framecolor when ready fr = b.node().getFrame() b['frameSize'] = (fr[0], fr[1], -baselineToBot, baselineToTop) self.accept(DGG.ENTER + b.guiId, self.__createMenu, [False, menuItemsGenerator, b.getName()]) if underlinePos > -1: tn = TextNode('') tn.setFont(self.font) tn.setText(t[:underlinePos + 1]) tnp = NodePath(tn.getInternalGeom()) underlineXend = tnp.getTightBounds()[1][0] tnp.removeNode() tn.setText(t[underlinePos]) tnp = NodePath(tn.getInternalGeom()) b3 = tnp.getTightBounds() underlineXstart = underlineXend - (b3[1] - b3[0])[0] tnp.removeNode() LSunder = LineSegs() LSunder.setThickness(underscoreThickness) LSunder.moveTo(underlineXstart + texMargin, 0, -.7 * baselineToBot) LSunder.drawTo(underlineXend - texMargin, 0, -.7 * baselineToBot) underline = b.stateNodePath[0].attachNewNode(LSunder.create()) underline.setColor(Vec4(*textColorReady), 1) underline.copyTo(b.stateNodePath[1], 10).setColor(Vec4(*textColorPress), 1) underline.copyTo(b.stateNodePath[2], 10).setColor(Vec4(*textColorHover), 1) self.accept('alt-' + t[underlinePos].lower(), self.__createMenu, [True, menuItemsGenerator, b.getName()]) x += (fr[1] - fr[0]) * self.scale self.width = x - 2 * gapFromEdge self.align(align) self.setEdgePos(edgePos) self.minZ = base.a2dBottom + self.height if edgePos == DropDownMenu.PBottom else None viewPlaneNode = PlaneNode('cut menu') viewPlaneNode.setPlane(Plane(Vec3(0, 0, -1), Point3(0, 0, -LH))) self.clipPlane = self.menuBG.attachNewNode(viewPlaneNode) def __createMenu(self, clicked, menuItemsGenerator, DBname, crap=None): itself = self.dropDownMenu and self.whoseDropDownMenu == DBname if not (clicked or self.dropDownMenu) or (not clicked and itself): return self.__removeMenu() if clicked and itself: return # removes any context menu if clicked: self.__removePopupMenu() self.dropDownMenu = PopupMenu( items=menuItemsGenerator(), parent=self.parent, buttonThrower=self.BT, font=self.font, baselineOffset=self.baselineOffset, scale=self.scale, itemHeight=self.itemHeight, leftPad=self.leftPad, separatorHeight=self.separatorHeight, underscoreThickness=self.underscoreThickness, BGColor=self.BGColor, BGBorderColor=self.BGBorderColor, separatorColor=self.separatorColor, frameColorHover=self.frameColorHover, frameColorPress=self.frameColorPress, textColorReady=self.textColorReady, textColorHover=self.textColorHover, textColorPress=self.textColorPress, textColorDisabled=self.textColorDisabled, minZ=self.minZ) self.acceptOnce(self.dropDownMenu.BTprefix + 'destroyed', setattr, [self, 'dropDownMenu', None]) self.whoseDropDownMenu = DBname item = self.menu.find('**/%s' % DBname) fr = item.node().getFrame() #~ if self.edgePos==DropDownMenu.PLeft: #~ x=max(fr[1],self.dropDownMenu.menu.getX(item)) #~ z=fr[2] #~ elif self.edgePos==DropDownMenu.PRight: #~ x=min(fr[0],self.dropDownMenu.menu.getX(item)) #~ z=fr[2]-self.dropDownMenu.maxWidth #~ elif self.edgePos in (DropDownMenu.PBottom,DropDownMenu.PTop): #~ x=fr[1]-self.dropDownMenu.maxWidth if self.alignment==DropDownMenu.ARight else fr[0] #~ z=fr[2] if self.edgePos==DropDownMenu.PTop else fr[3]+self.dropDownMenu.height if self.edgePos == DropDownMenu.PLeft: x = max(fr[1], self.dropDownMenu.menu.getX(item)) z = fr[3] - (self.height - self.gapFromEdge) / self.scale elif self.edgePos == DropDownMenu.PRight: x = min(fr[0], self.dropDownMenu.menu.getX(item)) z = fr[3] - (self.height - self.gapFromEdge ) / self.scale - self.dropDownMenu.maxWidth elif self.edgePos in (DropDownMenu.PBottom, DropDownMenu.PTop): x = fr[ 1] - self.dropDownMenu.maxWidth if self.alignment == DropDownMenu.ARight else fr[ 0] z = fr[3] - ( self.height - self.gapFromEdge ) / self.scale if self.edgePos == DropDownMenu.PTop else fr[2] + ( self.height) / self.scale + self.dropDownMenu.height self.dropDownMenu.menu.setPos(item, x, 0, z) if self.effect == DropDownMenu.EFade: self.dropDownMenu.menu.colorScaleInterval( .3, Vec4(1), Vec4(1, 1, 1, 0), blendType='easeIn').start() elif self.effect == DropDownMenu.ESlide: pos = self.dropDownMenu.menu.getPos() if self.edgePos == DropDownMenu.PTop: startPos = Point3(0, 0, self.dropDownMenu.height * self.scale) elif self.edgePos == DropDownMenu.PBottom: startPos = Point3(0, 0, -self.dropDownMenu.height * self.scale) elif self.edgePos == DropDownMenu.PLeft: startPos = Point3(-self.dropDownMenu.maxWidth * self.scale, 0, 0) elif self.edgePos == DropDownMenu.PRight: startPos = Point3(self.dropDownMenu.maxWidth * self.scale, 0, 0) self.dropDownMenu.menu.posInterval(.3, pos, pos + startPos, blendType='easeIn').start() self.dropDownMenu.menu.setClipPlane(self.clipPlane) elif self.effect == DropDownMenu.EStretch: if self.edgePos == DropDownMenu.PTop: startHpr = Vec3(0, -90, 0) elif self.edgePos == DropDownMenu.PBottom: dz = self.dropDownMenu.height * self.scale for c in asList(self.dropDownMenu.menu.getChildren()): c.setZ(c.getZ() + dz) self.dropDownMenu.menu.setZ(self.dropDownMenu.menu, -dz) startHpr = Vec3(0, 90, 0) elif self.edgePos == DropDownMenu.PLeft: startHpr = Vec3(90, 0, 0) elif self.edgePos == DropDownMenu.PRight: dx = self.dropDownMenu.maxWidth * self.scale for c in asList(self.dropDownMenu.menu.getChildren()): c.setX(c.getX() - dx) self.dropDownMenu.menu.setX(self.dropDownMenu.menu, dx) startHpr = Vec3(-90, 0, 0) self.dropDownMenu.menu.hprInterval(.3, Vec3(0), startHpr, blendType='easeIn').start() def __removeMenu(self): if self.dropDownMenu: self.dropDownMenu.destroy() self.dropDownMenu = None def __removePopupMenu(self): menuEvents = messenger.find('menu-') if menuEvents: menuEvent = menuEvents.keys() activeMenu = menuEvents[menuEvent[0]].values()[0][0].im_self activeMenu.destroy(delParents=True) def __reverseItems(self): tmp = NodePath('') self.itemsParent.getChildren().reparentTo(tmp) children = asList(tmp.getChildren()) for c in reversed(children): c.reparentTo(self.itemsParent) tmp.removeNode() x = self.sidePad * self.scale + self.gapFromEdge for c in asList(self.itemsParent.getChildren()): c.setX(x) fr = c.node().getFrame() x += (fr[1] - fr[0]) * self.scale def __startDrag(self, crap): taskMgr.add(self.__drag, 'dragging menu bar', extraArgs=[Point2(base.mouseWatcherNode.getMouse())]) self.__removePopupMenu() self.origBTprefix = self.BT.getPrefix() self.BT.setPrefix('dragging menu bar') def __drag(self, origMpos): if base.mouseWatcherNode.hasMouse(): mpos = base.mouseWatcherNode.getMouse() if mpos != origMpos: x, y = mpos.getX(), mpos.getY(), deltas = [x + 1, 1 - x, y + 1, 1 - y] closestEdge = deltas.index(min(deltas)) if closestEdge != self.edgePos: self.setEdgePos(closestEdge) return Task.cont def __stopDrag(self, crap): taskMgr.remove('dragging menu bar') self.BT.setPrefix(self.origBTprefix) def destroy(self): self.__removeMenu() self.ignoreAll() self.menu.removeNode() def isDraggable(self): ''' Returns menu bar's draggable status ''' return self.draggable def setDraggable(self, d): ''' Sets menu bar's draggable status ''' self.draggable = d if d: self.menuBG.bind(DGG.B1PRESS, self.__startDrag) self.menuBG.bind(DGG.B1RELEASE, self.__stopDrag) else: self.menuBG.unbind(DGG.B1PRESS) self.menuBG.unbind(DGG.B1RELEASE) def align(self, align=None): ''' Aligns menu text on menu bar. Use one of DropDownMenu.ALeft, DropDownMenu.ACenter, or DropDownMenu.ARight. ''' self.parent = getattr( base, DropDownMenu.parents[self.edgePos] [self.alignment if align is None else align]) self.menu.reparentTo(self.parent) if align is not None: self.itemsParent.setX(-.5 * self.width * align) self.menuBG.setX(-.5 * (5 - self.width) * align) self.alignment = align def setEdgePos(self, edge): ''' Sticks menu bar to 4 possible screen edges : DropDownMenu.PLeft, DropDownMenu.PRight, DropDownMenu.PBottom, or DropDownMenu.PTop. ''' lastEdge = self.edgePos reverseItems = lastEdge == DropDownMenu.PLeft self.edgePos = edge self.itemsParent.setZ(0) self.menuBG.setSz(1) alignment = None if self.edgePos == DropDownMenu.PLeft: self.menu.setR(-90) if self.alignment != DropDownMenu.ACenter: alignment = 2 - self.alignment self.__reverseItems() self.minZ = None else: if self.edgePos == DropDownMenu.PRight: self.menu.setR(90) self.minZ = None elif self.edgePos == DropDownMenu.PBottom: self.menu.setR(0) self.menuBG.setSz(-1) self.itemsParent.setZ(-self.menuBG.node().getFrame()[2]) self.minZ = base.a2dBottom + self.height elif self.edgePos == DropDownMenu.PTop: self.menu.setR(0) self.minZ = None if reverseItems: if self.alignment != DropDownMenu.ACenter: alignment = 2 - self.alignment self.__reverseItems() self.align(alignment) if callable(self.onMove): self.onMove()
def add_button(self, text, label_id, pos_x, pos_y, width=0.0, hight=0.1): if width == 0.0: for c in range(len(text)/2): width += 0.08 ls = LineSegs("lines") ls.setColor(0,1,0,1) ls.drawTo(-width/2, 0, hight/2) ls.drawTo(width/2, 0, hight/2) ls.drawTo(width/2, 0,-hight/2) ls.drawTo(-width/2, 0,-hight/2) ls.drawTo(-width/2, 0, hight/2) border = ls.create(False) border.setTag('back_ground', '1') array = GeomVertexArrayFormat() array.addColumn("vertex", 4, Geom.NTFloat32, Geom.CPoint) arr_format = GeomVertexFormat() arr_format.addArray(array) arr_format = GeomVertexFormat.registerFormat(arr_format) vdata = GeomVertexData('fill', arr_format, Geom.UHStatic) vdata.setNumRows(4) vertex = GeomVertexWriter(vdata, 'vertex') vertex.addData3f(-width/2, 0, hight/2) vertex.addData3f(width/2, 0, hight/2) vertex.addData3f(-width/2, 0,-hight/2) vertex.addData3f(width/2, 0,-hight/2) prim = GeomTristrips(Geom.UHStatic) prim.addVertex(0) prim.addVertex(1) prim.addVertex(2) prim.addVertex(3) geom = Geom(vdata) geom.addPrimitive(prim) node = GeomNode('gnode') node.addGeom(geom) nodePath = NodePath("button") nodePath.attachNewNode(node) nodePath.setPos(0,0,0) nodePath.setTag('button', '1') nodePath.setBin("unsorted", 0) nodePath.setDepthTest(False) nodePath.setColor(0,0,0,1) nodePath.attachNewNode(border) nodePath1 = NodePath("button") nodePath1.attachNewNode(node) nodePath1.setPos(0,0,0) nodePath1.setTag('button1', '1') nodePath1.setBin("unsorted", 0) nodePath1.setDepthTest(False) nodePath1.setColor(0,1,0,1) nodePath1.attachNewNode(border) button=DirectFrame( enableEdit=1, text=text, geom=nodePath, text_scale=0.05, text_fg=(0,1,0,1), borderWidth=(1,1), relief = None, text_pos=(0,-0.01,0), textMayChange=1, state=DGG.NORMAL, parent=aspect2d ) button.setPos(pos_x,0,pos_y) button.bind(DGG.B1PRESS, button_click, [button]) button.bind(DGG.WITHIN, button_hover, [button]) button.bind(DGG.WITHOUT, button_no_hover, [button]) # button.resetFrameSize() # self.button.bind(DGG.WITHIN, self.onMouseHoverInFunction, [button, some_value1]) defines.ENTITIES[defines.ENTITY_ID] = {'CATEGORY':'button', 'BUTTON':button, 'NODE':nodePath, 'LABEL':label_id,'STATUS': 0} defines.ENTITY_ID += 1
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])