class HostMenu(DirectObject): def __init__(self): self.defaultBtnMap = base.loader.loadModel("gui/button_map") self.buttonGeom = ( self.defaultBtnMap.find("**/button_ready"), self.defaultBtnMap.find("**/button_click"), self.defaultBtnMap.find("**/button_rollover"), self.defaultBtnMap.find("**/button_disabled")) defaultFont = loader.loadFont('gui/eufm10.ttf') self.logFrame = DirectScrolledFrame( canvasSize = (0, base.a2dRight * 2, -5, 0), frameSize = (0, base.a2dRight * 2, (base.a2dBottom+.2) * 2, 0), frameColor = (0.1, 0.1, 0.1, 1)) self.logFrame.reparentTo(base.a2dTopLeft) # create the info and server debug output self.textscale = 0.1 self.txtinfo = OnscreenText( scale = self.textscale, pos = (0.1, -0.1), text = "", align = TextNode.ALeft, fg = (0.1,1.0,0.15,1), bg = (0, 0, 0, 0), shadow = (0, 0, 0, 1), shadowOffset = (-0.02, -0.02)) self.txtinfo.setTransparency(1) self.txtinfo.reparentTo(self.logFrame.getCanvas()) # create a close Server button self.btnBackPos = Vec3(0.4, 0, 0.2) self.btnBackScale = 0.25 self.btnBack = DirectButton( # Scale and position scale = self.btnBackScale, pos = self.btnBackPos, # Text text = "Quit Server", text_scale = 0.45, text_pos = (0, -0.1), text_fg = (0.82,0.85,0.87,1), text_shadow = (0, 0, 0, 1), text_shadowOffset = (-0.02, -0.02), text_font = defaultFont, # Frame geom = self.buttonGeom, frameColor = (0, 0, 0, 0), relief = 0, pressEffect = False, # Functionality command = self.back, rolloverSound = None, clickSound = None) self.btnBack.setTransparency(1) self.btnBack.reparentTo(base.a2dBottomLeft) # catch window resizes and recalculate the aspectration self.accept("window-event", self.recalcAspectRatio) self.accept("addLog", self.addLog) def show(self): self.logFrame.show() self.btnBack.show() def hide(self): self.logFrame.hide() self.btnBack.hide() def back(self): self.hide() base.messenger.send("stop_server") self.addLog("Quit Server!") def addLog(self, text): self.txtinfo.appendText(text + "\n") textbounds = self.txtinfo.getTightBounds() self.logFrame["canvasSize"] = (0, textbounds[1].getX(), textbounds[0].getZ(), 0) def recalcAspectRatio(self, window): """get the new aspect ratio to resize the mainframe""" # set the mainframe size to the window borders again self.logFrame["frameSize"] = ( 0, base.a2dRight * 2, (base.a2dBottom+.2) * 2, 0)
class CraftInventory(DirectObject): def __init__(self): self.itemFrame = DirectScrolledFrame( text="Inventory", text_scale=0.25, text_pos=(0, 0.75, 0), text_bg=(1, 1, 1, 1), # make the frame occupy the whole window frameSize=(-0.6, 0.6, -0.8, 0.8), pos=(-0.7, 0, 0), # make the canvas as big as the frame canvasSize=(-0.6, 0.6, -0.8, 0.8), # set the frames color to white frameColor=(1, 1, 1, 1), ) self.itemList = [] self.craftFrame = DirectFrame( text="Crafting", text_scale=0.25, text_pos=(0, 0.75, 0), text_bg=(1, 1, 1, 1), # make the frame occupy the whole window frameSize=(-0.6, 0.6, -0.8, 0.8), pos=(0.7, 0, 0), # set the frames color to white frameColor=(1, 1, 1, 1), ) self.craftDrop = DirectFrame( text="drop ore", text_scale=0.1, pos=(0, 0.4, 0), frameSize=(-0.25, 0.25, -0.25, 0.25), frameColor=(1, 0, 0, 1), ) self.craftDrop.reparentTo(self.craftFrame) def show(self): self.itemFrame.show() self.craftFrame.show() def hide(self): self.itemFrame.hide() self.craftFrame.hide() def __makeItem(self, item): obj = DirectLabel( text=str(item.giveLoot()) + "x " + item.giveType(), text_scale=0.5, text_pos=(0, -1, 0), image=("item.png", "item_hover.png", "item.png"), # normal, hover, disabled scale=0.16, numStates=2, state=DGG.NORMAL, relief=DGG.GROOVE, ) obj.setTransparency(True) obj["activeState"] = 0 obj.reparentTo(self.itemFrame.getCanvas()) obj.bind(DGG.B1PRESS, self.dragStart, [obj]) obj.bind(DGG.B1RELEASE, self.dragStop) obj.bind(DGG.ENTER, self.inObject, [obj]) obj.bind(DGG.EXIT, self.outObject, [obj]) return obj def updateList(self, items): for item in self.itemList: item.destroy() i = 1 j = 0 pad = 0.2 numItemsPerRow = 2 itemWidth = 0.32 itemHeight = 0.32 # left = -(itemWidth * (numItemsPerRow/2.0)) - 0.16 left = self.itemFrame.getX() # + (itemWidth + 0.16) xStep = itemWidth + 0.13 yStep = -(itemHeight + 0.13) top = 0.8 - (0.16 + pad) for item in items: self.itemList.append(self.__makeItem(item)) x = left + i * xStep y = top + j * yStep self.itemList[-1].setPos(x, 0.0, y) if i == numItemsPerRow: i = 0 j += 1 i += 1 if i == 1: j -= 1 height = ((j) * -yStep) - 0.16 # resize the canvas. This will make the scrollbars dis-/appear, # dependent on if the canvas is bigger than the frame size. self.itemFrame["canvasSize"] = (-0.6, 0.6, -height, 0.8) def inObject(self, element, event): # Can be used to highlight objects element["activeState"] = 1 element.setActiveState() # print "over object" def outObject(self, element, event): # Can be used to unhighlight objects # element["state"] = 0 element["activeState"] = 0 element.setActiveState() def dragStart(self, element, event): print "start drag" taskMgr.remove("dragDropTask") vWidget2render2d = element.getPos(render2d) vMouse2render2d = Point3(event.getMouse()[0], 0, event.getMouse()[1]) editVec = Vec3(vWidget2render2d - vMouse2render2d) t = taskMgr.add(self.dragTask, "dragDropTask") element.reparentTo(aspect2d) t.element = element t.editVec = editVec t.elementStartPos = element.getPos() def dragTask(self, state): mwn = base.mouseWatcherNode if mwn.hasMouse(): vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1]) newPos = vMouse2render2d + state.editVec state.element.setPos(render2d, newPos) return Task.cont def dragStop(self, event): print "stop drag with:", event isInArea = False t = taskMgr.getTasksNamed("dragDropTask")[0] # for dropArea in self.dropAreas: # if self.isInBounds(event.getMouse(), dropArea["frameSize"], dropArea.getPos(render)): # print "inside Area:", dropArea["text"], dropArea.getPos(render) # isInArea = True # t.element.setPos(dropArea.getPos(render)) # t.element.setX(t.element.getX() * self.ratio) # #t.element.setX(t.element.getX() - t.element.getWidth() / 2.0) # break if not isInArea: t.element.setPos(t.elementStartPos) t.element.reparentTo(self.itemFrame.getCanvas()) taskMgr.remove("dragDropTask") def isInBounds(self, location, bounds, posShift=None): x = 0 if posShift is None else posShift.getX() y = 0 if posShift is None else posShift.getZ() left = x + bounds[0] right = x + bounds[1] bottom = y + bounds[2] top = y + bounds[3] # are we outside of the bounding box from the left if location[0] < left: return False # are we outside of the bounding box from the right if location[0] > right: return False # are we outside of the bounding box from the bottom if location[1] < bottom: return False # are we outside of the bounding box from the top if location[1] > top: return False # the location is inside the bbox return True
class CraftInventory(DirectObject): def __init__(self): self.itemFrame = DirectScrolledFrame( text="Inventory", text_scale=0.25, text_pos=(0, 0.75, 0), text_bg=(1, 1, 1, 1), # make the frame occupy the whole window frameSize=(-0.6, 0.6, -0.8, 0.8), pos=(-0.7, 0, 0), # make the canvas as big as the frame canvasSize=(-0.6, 0.6, -0.8, 0.8), # set the frames color to white frameColor=(1, 1, 1, 1)) self.itemList = [] self.craftFrame = DirectFrame( text="Crafting", text_scale=0.25, text_pos=(0, 0.75, 0), text_bg=(1, 1, 1, 1), # make the frame occupy the whole window frameSize=(-0.6, 0.6, -0.8, 0.8), pos=(0.7, 0, 0), # set the frames color to white frameColor=(1, 1, 1, 1)) self.craftDrop = DirectFrame(text="drop ore", text_scale=0.1, pos=(0, 0.4, 0), frameSize=(-0.25, 0.25, -0.25, 0.25), frameColor=(1, 0, 0, 1)) self.craftDrop.reparentTo(self.craftFrame) def show(self): self.itemFrame.show() self.craftFrame.show() def hide(self): self.itemFrame.hide() self.craftFrame.hide() def __makeItem(self, item): obj = DirectLabel( text=str(item.giveLoot()) + "x " + item.giveType(), text_scale=0.5, text_pos=(0, -1, 0), image=("item.png", "item_hover.png", "item.png"), #normal, hover, disabled scale=0.16, numStates=2, state=DGG.NORMAL, relief=DGG.GROOVE) obj.setTransparency(True) obj["activeState"] = 0 obj.reparentTo(self.itemFrame.getCanvas()) obj.bind(DGG.B1PRESS, self.dragStart, [obj]) obj.bind(DGG.B1RELEASE, self.dragStop) obj.bind(DGG.ENTER, self.inObject, [obj]) obj.bind(DGG.EXIT, self.outObject, [obj]) return obj def updateList(self, items): for item in self.itemList: item.destroy() i = 1 j = 0 pad = 0.2 numItemsPerRow = 2 itemWidth = 0.32 itemHeight = 0.32 #left = -(itemWidth * (numItemsPerRow/2.0)) - 0.16 left = self.itemFrame.getX() # + (itemWidth + 0.16) xStep = itemWidth + 0.13 yStep = -(itemHeight + 0.13) top = 0.8 - (0.16 + pad) for item in items: self.itemList.append(self.__makeItem(item)) x = left + i * xStep y = top + j * yStep self.itemList[-1].setPos(x, 0.0, y) if i == numItemsPerRow: i = 0 j += 1 i += 1 if i == 1: j -= 1 height = ((j) * -yStep) - 0.16 # resize the canvas. This will make the scrollbars dis-/appear, # dependent on if the canvas is bigger than the frame size. self.itemFrame["canvasSize"] = (-0.6, 0.6, -height, 0.8) def inObject(self, element, event): # Can be used to highlight objects element["activeState"] = 1 element.setActiveState() #print "over object" def outObject(self, element, event): # Can be used to unhighlight objects #element["state"] = 0 element["activeState"] = 0 element.setActiveState() def dragStart(self, element, event): print "start drag" taskMgr.remove('dragDropTask') vWidget2render2d = element.getPos(render2d) vMouse2render2d = Point3(event.getMouse()[0], 0, event.getMouse()[1]) editVec = Vec3(vWidget2render2d - vMouse2render2d) t = taskMgr.add(self.dragTask, 'dragDropTask') element.reparentTo(aspect2d) t.element = element t.editVec = editVec t.elementStartPos = element.getPos() def dragTask(self, state): mwn = base.mouseWatcherNode if mwn.hasMouse(): vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1]) newPos = vMouse2render2d + state.editVec state.element.setPos(render2d, newPos) return Task.cont def dragStop(self, event): print "stop drag with:", event isInArea = False t = taskMgr.getTasksNamed('dragDropTask')[0] #for dropArea in self.dropAreas: # if self.isInBounds(event.getMouse(), dropArea["frameSize"], dropArea.getPos(render)): # print "inside Area:", dropArea["text"], dropArea.getPos(render) # isInArea = True # t.element.setPos(dropArea.getPos(render)) # t.element.setX(t.element.getX() * self.ratio) # #t.element.setX(t.element.getX() - t.element.getWidth() / 2.0) # break if not isInArea: t.element.setPos(t.elementStartPos) t.element.reparentTo(self.itemFrame.getCanvas()) taskMgr.remove('dragDropTask') def isInBounds(self, location, bounds, posShift=None): x = 0 if posShift is None else posShift.getX() y = 0 if posShift is None else posShift.getZ() left = x + bounds[0] right = x + bounds[1] bottom = y + bounds[2] top = y + bounds[3] # are we outside of the bounding box from the left if location[0] < left: return False # are we outside of the bounding box from the right if location[0] > right: return False # are we outside of the bounding box from the bottom if location[1] < bottom: return False # are we outside of the bounding box from the top if location[1] > top: return False # the location is inside the bbox return True
class DeviceMonitor(DirectObject): def __init__(self, device): super().__init__() self.device = device self.create_panel() self.activate() self.hide() def activate(self): print("Device connected") print(" Name : {}".format(self.device.name)) print(" Type : {}".format(self.device.device_class.name)) print(" Manufacturer: {}".format(self.device.manufacturer)) print(" ID : {:04x}:{:04x}".format(self.device.vendor_id, self.device.product_id)) axis_names = [axis.axis.name for axis in self.device.axes] print(" Axes : {} ({})".format(len(self.device.axes), ', '.join(axis_names))) button_names = [button.handle.name for button in self.device.buttons] print(" Buttons : {} ({})".format(len(self.device.buttons), ', '.join(button_names))) base.attachInputDevice(self.device) self.task = base.taskMgr.add( self.update, "Monitor for {}".format(self.device.name), sort=10, ) def deactivate(self): print("\"{}\" disconnected".format(self.device.name)) base.taskMgr.remove(self.task) self.panel.detach_node() def create_panel(self): panel_width = base.a2dLeft * -0.25 + base.a2dRight scroll_bar_width = 0.08 # NOTE: -0.001 because thanks to inaccuracy the vertical bar appears... canvas_width = panel_width - scroll_bar_width - 0.001 canvas_height = base.a2dBottom - base.a2dTop self.panel = DirectScrolledFrame( frameSize=VBase4( 0, panel_width, canvas_height, 0, ), frameColor=VBase4(0.8, 0.8, 0.8, 1), canvasSize=VBase4( 0, canvas_width, canvas_height, 0, ), scrollBarWidth=scroll_bar_width, manageScrollBars=True, autoHideScrollBars=True, pos=(base.a2dLeft * 0.25, 0, base.a2dTop), parent=base.aspect2d, ) panel_canvas = self.panel.getCanvas() offset = -0.0 # Style sheets half_width_entry = dict( frameSize=VBase4( 0, canvas_width / 2, -0.1, 0, ), parent=panel_canvas, frameColor=VBase4(0.8, 0.8, 0.8, 1), ) left_aligned_small_text = dict( text_align=TextNode.ALeft, text_scale=0.05, text_fg=VBase4(0,0,0,1), text_pos=(0.05, -0.06), ) half_width_text_frame = dict( **half_width_entry, **left_aligned_small_text, ) header = dict( frameSize=VBase4( 0, canvas_width, -0.1, 0, ), parent=panel_canvas, frameColor=VBase4(0.6, 0.6, 0.6, 1), text_align=TextNode.ALeft, text_scale=0.1, text_fg=VBase4(0,0,0,1), text_pos=(0.05, -0.075), ) # Basic device data (name, device class, manufacturer, USB ID) self.device_header = DirectLabel( text="Device data", pos=(0, 0, offset), **header, ) offset -= 0.1 def add_data_entry(offset, label, text): self.name = DirectLabel( text=label, pos=(0, 0, offset), **half_width_text_frame, ) self.name = DirectLabel( text=text, pos=(canvas_width / 2, 0, offset), **half_width_text_frame, ) metadata = [ ('Name', self.device.name), ('Device class', self.device.device_class.name), ('Manufacturer', self.device.manufacturer), ('USB ID', "{:04x}:{:04x}".format( self.device.vendor_id, self.device.product_id, ), ), ] for label, text in metadata: add_data_entry(offset, label, text) offset -= 0.1 # Axes self.axis_sliders = [] if len(self.device.axes) > 0: offset -= 0.1 self.axes_header = DirectLabel( text="Axes", pos=(0, 0, offset), **header, ) offset -= 0.1 def add_axis(offset, axis_name): slider_width = canvas_width / 2 label = DirectLabel( text=axis_name, **left_aligned_small_text, pos=(0.05, 0, offset), parent=panel_canvas, ) slider = DirectSlider( value=0.0, range=(-1.0, 1.0), state=DGG.DISABLED, frameSize=VBase4( 0, slider_width, -0.1, 0, ), thumb_frameSize=VBase4( 0.0, 0.04, -0.04, 0.04), frameColor=VBase4(0.3, 0.3, 0.3, 1), pos=(canvas_width - slider_width, 0, offset), parent=panel_canvas, ) return slider for axis in self.device.axes: axis_slider = add_axis(offset, axis.axis.name) self.axis_sliders.append(axis_slider) offset -= 0.1 # Buttons self.button_buttons = [] if len(self.device.buttons) > 0: offset -= 0.1 self.buttons_header = DirectLabel( text="Buttons", pos=(0, 0, offset), **header, ) offset -= 0.1 def add_button(offset, button_name): button_width = canvas_width / 2 label = DirectLabel( text=button_name, **left_aligned_small_text, pos=(0.05, 0, offset), parent=panel_canvas, ) button = DirectFrame( frameSize=VBase4( 0, button_width, -0.1, 0, ), text="", text_align=TextNode.ACenter, text_scale=0.05, text_fg=VBase4(0,0,0,1), text_pos=(button_width / 2, -0.06), frameColor=VBase4(0.3, 0.3, 0.3, 1), pos=(canvas_width - button_width, 0, offset), parent=panel_canvas, ) return button for i in range(len(self.device.buttons)): button_name = self.device.buttons[i].handle.name button_button = add_button(offset, button_name) self.button_buttons.append(button_button) offset -= 0.1 # Vibration self.vibration = [] if self.device.has_feature(InputDevice.Feature.vibration): offset -= 0.1 self.vibration_header = DirectLabel( text="Vibration", pos=(0, 0, offset), **header, ) offset -= 0.1 def add_vibration(offset, axis_name, index): slider_width = canvas_width / 2 label = DirectLabel( text=axis_name, **left_aligned_small_text, pos=(0.05, 0, offset), parent=panel_canvas, ) slider = DirectSlider( value=0.0, range=(0.0, 1.0), command=self.update_vibration, frameSize=VBase4( 0, slider_width, -0.1, 0, ), thumb_frameSize=VBase4( 0.0, 0.04, -0.04, 0.04), frameColor=VBase4(0.3, 0.3, 0.3, 1), pos=(canvas_width - slider_width, 0, offset), parent=panel_canvas, ) return slider for index, name in enumerate(["low frequency", "high frequency"]): self.vibration.append(add_vibration(offset, name, index)) offset -= 0.1 # Resize the panel's canvas to the widgets actually in it. if -offset > -canvas_height: self.panel['canvasSize'] = VBase4( 0, canvas_width, offset, 0, ) self.panel.setCanvasSize() def show(self): # FIXME: Activate update task here, and deactivate it in hide()? self.panel.show() def hide(self): self.panel.hide() def update_vibration(self): low = self.vibration[0]['value'] high = self.vibration[1]['value'] self.device.set_vibration(low, high) def update(self, task): # FIXME: There needs to be a demo of events here, too. for idx, slider in enumerate(self.axis_sliders): slider["value"] = self.device.axes[idx].value for idx, button in enumerate(self.button_buttons): if self.device.buttons[idx].known: if self.device.buttons[idx].pressed: button['frameColor'] = VBase4(0.0, 0.8, 0.0, 1) button['text'] = "down" else: button['frameColor'] = VBase4(0.3, 0.3, 0.3, 1) button['text'] = "up" else: # State is InputDevice.S_unknown. This happens if the device # manager hasn't polled yet, and in some cases before a button # has been pressed after the program's start. button['frameColor'] = VBase4(0.8, 0.8, 0.0, 1) button['text'] = "unknown" return task.cont
class ScrolledItemSelector(DirectObject): '''Touch optimised list that holds a set of items. By clicking on one you select it and return its Value with get_selected(). To add an item simply use add_item(). You can also pass a function with the 'command' field that gets called on a selection switch''' def __init__(self, frame_size=(1, 1.5), frame_color=(0.2, 0.2, 0.2, 0.8), pos=(0, 0, 0), item_v_padding=0.02, item_h_padding=0.04, item_scale=1, item_side_ratio=0.2, item_background=(0.3, 0.3, 0.3, 1), item_background_active=(0.6, 0.6, 0.6, 1), command=lambda: None): self.f_x, self.f_y = frame_size self.c_x = self.f_x self.c_y = self.f_y self.item_v_padding = item_v_padding self.item_h_padding = item_h_padding self.item_scale = item_scale self.item_background = item_background self.item_background_active = item_background_active self.frame = DirectScrolledFrame( frameSize=(-self.f_x / 2, self.f_x / 2, -self.f_y / 2, self.f_y / 2), canvasSize=(-self.c_x / 2, self.c_x / 2, -self.c_y / 2, self.c_y / 2), frameColor=frame_color, pos=pos, scrollBarWidth=(0), manageScrollBars=False, state=DGG.NORMAL) self.frame.verticalScroll.destroy() self.frame.horizontalScroll.destroy() self.frame.bind(DGG.WITHIN, self._start_listening) self.frame.bind(DGG.WITHOUT, self._stop_listening) self.canvas = self.frame.getCanvas() self.i_x = self.f_x * (1 - item_h_padding) self.i_y = self.f_y * item_side_ratio self.i_size = (-self.i_x / 2, self.i_x / 2, -self.i_y / 2, self.i_y / 2) self.i_start = self.c_y / 2 + self.i_y / 2 self.active_item = None self.item_list = [] self.value_list = [] self.start_mY = None self.old_mY = None self.m_diff = 0 self.is_scrolling = False self.command = command # messenger.toggleVerbose() def _start_listening(self, watcher): print('start listening') self.accept("mouse1", self._start_scroll) self.accept("mouse1-up", self._stop_scroll) def _stop_listening(self, watcher): print('stop listening') self.ignore("mouse1") self.ignore("mouse1-up") self._stop_scroll() def _switch_active_item(self, item, watcher): if not self.is_scrolling: print('switched') if self.active_item is not None: self.active_item['frameColor'] = self.item_background item['frameColor'] = self.item_background_active self.active_item = item self.command() def _start_scroll(self): n = len(self.item_list) content_length = ( (n * (self.i_y + self.item_v_padding)) + # Size of all elements with padding self.item_v_padding) # Add one padding for the bottom if content_length > self.c_y: taskMgr.doMethodLater(0.01, self._scroll_task, 'scroll_task') self.start_mY = base.mouseWatcherNode.getMouse().getY() self.old_mY = self.start_mY def _stop_scroll(self): taskMgr.remove('scroll_task') taskMgr.doMethodLater(0.01, self._scroll_fade_task, 'scrollfadetask') self.is_scrolling = False def _scroll_task(self, task): mY = base.mouseWatcherNode.getMouse().getY() old_c = self.canvas.getZ() self.m_diff = self.old_mY - mY n = len(self.item_list) if self.m_diff != 0: self.is_scrolling = True self.c_scroll_start = 0 self.c_scroll_stop = ( (n * (self.i_y + self.item_v_padding)) + # Size of all elements with padding self.item_v_padding - # Add one padding for the bottom self.f_y) # Substract the length of the canvas self.c_new_pos = (old_c - self.m_diff) hits_not_upper_bound = self.c_new_pos >= self.c_scroll_start hits_not_lower_bound = self.c_new_pos <= self.c_scroll_stop # print('canvas : ' + str(self.canvas.getZ())) if hits_not_upper_bound and hits_not_lower_bound: self.canvas.setZ(self.c_new_pos) elif not hits_not_upper_bound: self.canvas.setZ(self.c_scroll_start) elif not hits_not_lower_bound: self.canvas.setZ(self.c_scroll_stop) self.old_mY = mY return task.again def _scroll_fade_task(self, task): if self.m_diff is None or abs(self.m_diff) < 0.005: self.m_diff = 0 return task.done old_c = self.canvas.getZ() n = len(self.item_list) self.c_scroll_start = 0 self.c_scroll_stop = ( (n * (self.i_y + self.item_v_padding)) + # Size of all elements with padding self.item_v_padding - # Add one padding for the bottom self.c_y) # Substract the length of the canvas hits_not_upper_bound = (old_c - self.m_diff) >= self.c_scroll_start hits_not_lower_bound = (old_c - self.m_diff) <= self.c_scroll_stop if hits_not_upper_bound and hits_not_lower_bound: self.canvas.setZ(old_c - self.m_diff) self.m_diff *= 0.85 return task.again elif not hits_not_upper_bound: self.canvas.setZ(self.c_scroll_start) self.m_diff = 0 return task.done elif not hits_not_lower_bound: self.canvas.setZ(self.c_scroll_stop) self.m_diff = 0 return task.done 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) def get_active_item(self): return self.active_item def set_active_item(self, pos): self._switch_active_item(self.item_list[pos], None) def get_active_id(self): return int(self.active_item['text']) def get_active_value(self): return self.value_list[int(self.active_item['text']) - 1] def hide(self): '''Triggers the DirectFrame.hide() of the main frame''' self.frame.hide() def show(self): '''Triggers the DirectFrame.show() of the main frame''' self.frame.show() def clear(self): '''Destroys every item that was added to the list''' for item in self.item_list: item.destroy() self.item_list = [] self.active_item = None self.value_list = [] self.canvas.setZ(0) def destroy(self): '''Destroys the whole list and every item in it''' self.canvas.destroy() self.frame.destroy()
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 HostMenu(DirectObject): def __init__(self): self.defaultBtnMap = base.loader.loadModel("gui/button_map") self.buttonGeom = (self.defaultBtnMap.find("**/button_ready"), self.defaultBtnMap.find("**/button_click"), self.defaultBtnMap.find("**/button_rollover"), self.defaultBtnMap.find("**/button_disabled")) defaultFont = loader.loadFont('gui/eufm10.ttf') self.logFrame = DirectScrolledFrame( canvasSize=(0, base.a2dRight * 2, -5, 0), frameSize=(0, base.a2dRight * 2, (base.a2dBottom + .2) * 2, 0), frameColor=(0.1, 0.1, 0.1, 1)) self.logFrame.reparentTo(base.a2dTopLeft) # create the info and server debug output self.textscale = 0.1 self.txtinfo = OnscreenText(scale=self.textscale, pos=(0.1, -0.1), text="", align=TextNode.ALeft, fg=(0.1, 1.0, 0.15, 1), bg=(0, 0, 0, 0), shadow=(0, 0, 0, 1), shadowOffset=(-0.02, -0.02)) self.txtinfo.setTransparency(1) self.txtinfo.reparentTo(self.logFrame.getCanvas()) # create a close Server button self.btnBackPos = Vec3(0.4, 0, 0.2) self.btnBackScale = 0.25 self.btnBack = DirectButton( # Scale and position scale=self.btnBackScale, pos=self.btnBackPos, # Text text="Quit Server", text_scale=0.45, text_pos=(0, -0.1), text_fg=(0.82, 0.85, 0.87, 1), text_shadow=(0, 0, 0, 1), text_shadowOffset=(-0.02, -0.02), text_font=defaultFont, # Frame geom=self.buttonGeom, frameColor=(0, 0, 0, 0), relief=0, pressEffect=False, # Functionality command=self.back, rolloverSound=None, clickSound=None) self.btnBack.setTransparency(1) self.btnBack.reparentTo(base.a2dBottomLeft) # catch window resizes and recalculate the aspectration self.accept("window-event", self.recalcAspectRatio) self.accept("addLog", self.addLog) def show(self): self.logFrame.show() self.btnBack.show() def hide(self): self.logFrame.hide() self.btnBack.hide() def back(self): self.hide() base.messenger.send("stop_server") self.addLog("Quit Server!") def addLog(self, text): self.txtinfo.appendText(text + "\n") textbounds = self.txtinfo.getTightBounds() self.logFrame["canvasSize"] = (0, textbounds[1].getX(), textbounds[0].getZ(), 0) def recalcAspectRatio(self, window): """get the new aspect ratio to resize the mainframe""" # set the mainframe size to the window borders again self.logFrame["frameSize"] = (0, base.a2dRight * 2, (base.a2dBottom + .2) * 2, 0)
class DirectWindow( DirectFrame ): def __init__( self , pos = ( -.5, .5) , title = 'Title' , bgColor = (.5,.5,.5,1) , buttonColor = (1,1,1,1) #( .6, .6, .6, 1 ) #, minSize = ( .5, .5 ) #, maxSize = ( 1, 1 ) , minWindowSize = (0,0) , maxWindowSize = (10000,10000) , virtualSize = (1,1) , windowBorderTextureFiles = [ DEFAULT_TITLE_TEXTURE_LEFT , DEFAULT_TITLE_TEXTURE_CENTER , DEFAULT_TITLE_TEXTURE_RIGHT , DEFAULT_RESIZE_GEOM ] , windowBorderGeomFiles = [ DEFAULT_TITLE_GEOM_RIGHT ] , windowColors = [ ( 1, 1, 1, 1 ) , ( 1, 1, 1, 1 ) , ( 1, 1, 1, 1 ) , ( 1, 1, 1, 1 ) ] , borderSize = 0.01 , dragbarSize = 0.05 , parent=None): self.windowPos = pos self.minWindowSize = minWindowSize self.maxWindowSize = maxWindowSize self.virtualSize = virtualSize self.borderSize = borderSize self.dragbarSize = dragbarSize if parent is None: parent=aspect2d self.parent=parent self.previousSize = (10,10) self.collapsed = False # maybe we should check if aspect2d doesnt already contain the aspect2dMouseNode self.mouseNode = self.parent.attachNewNode( 'aspect2dMouseNode', sort = 999999 ) taskMgr.add( self.mouseNodeTask, 'mouseNodeTask' ) windowBorderTextures = list() for windowBorder in windowBorderTextureFiles: if windowBorder is not None: mdlFile = loader.loadTexture(windowBorder) windowBorderTextures.append(mdlFile) else: windowBorderTextures.append(None) windowBorderGeoms = list() for windowGeom in windowBorderGeomFiles: if windowGeom is not None: mdlFile = loader.loadModel(windowGeom) mdls = ( mdlFile.find('**/**-default'), mdlFile.find('**/**-click'), mdlFile.find('**/**-rollover'), mdlFile.find('**/**-disabled') ) windowBorderGeoms.append(mdls) else: windowBorderGeoms.append((None,None,None,None,),) # the main window we want to move around self.parentWindow = DirectFrame( parent=self.parent, pos=(self.windowPos[0], 0, self.windowPos[1]), #frameSize=# is defined in resize scale=(1, 1, -1), frameColor=bgColor, borderWidth=(0, 0), relief=DGG.FLAT, sortOrder=1, ) # header of the window (drag&drop with it) # the title part of the window, drag around to move the window self.headerParent = DirectButton( parent=self.parentWindow, pos=(0, 0, 0), #frameSize=# is defined in resize scale=(1, 1, self.dragbarSize), frameColor=(1, 1, 1, 1), borderWidth=(0, 0), relief=DGG.FLAT, ) self.headerParent.bind(DGG.B1PRESS,self.startWindowDrag) # images in the headerParent self.headerCenter = DirectFrame( parent=self.headerParent, pos=(0, 0, 1), #frameSize=# is defined in resize scale=(1,1,-1), frameColor=windowColors[1], frameTexture=windowBorderTextures[1], borderWidth=(0, 0), relief=DGG.FLAT, ) self.headerLeft = DirectFrame( parent=self.headerParent, pos=(0, 0, 1), frameSize=(0, self.dragbarSize, 0, 1), scale=(1,1,-1), frameColor=windowColors[0], frameTexture=windowBorderTextures[0], borderWidth=(0, 0), relief=DGG.FLAT, ) # collapse button self.headerRight = DirectButton( parent=self.headerParent, #pos=# is defined in resize frameSize=(0, self.dragbarSize, 0, 1), scale=(1,1,-1), frameColor=windowColors[2], #frameTexture=windowBorderTextures[2], borderWidth=(0, 0), relief=DGG.FLAT, command=self.toggleCollapsed, geom=windowBorderGeoms[0], geom_scale=(self.dragbarSize,1,1) ) # the resize button of the window self.resizeButton = DirectButton( parent=self.parentWindow, pos=(1-self.dragbarSize, 0, 1), frameSize=(0, 1, 0, 1), scale=(self.dragbarSize,1,-self.dragbarSize), frameColor=windowColors[3], frameTexture=windowBorderTextures[3], borderWidth=(0, 0), relief=DGG.FLAT, sortOrder=1, ) self.resizeButton.bind(DGG.B1PRESS,self.startResizeDrag) # text in the center of the window 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.headerCenter.attachNewNode(text) self.textNodePath.setPos(.5,0,.3) self.textNodePath.setScale(0.8*self.dragbarSize,1,0.8) if Y_INVERTED: scale = (1,1,-1) else: scale = (1,1,1) # the content part of the window, put stuff beneath # contentWindow.getCanvas() to put it into it self.contentWindow = DirectScrolledFrame( parent = self.parentWindow, #pos = # is defined in resize scale = scale, canvasSize = (0,self.virtualSize[0],0,self.virtualSize[1]), frameColor = buttonColor, relief = DGG.RAISED, borderWidth = (0,0), verticalScroll_frameSize = [0,self.dragbarSize,0,1], verticalScroll_frameTexture = loader.loadTexture( 'rightBorder.png' ), verticalScroll_incButton_frameTexture = loader.loadTexture( 'scrollDown.png' ), verticalScroll_decButton_frameTexture = loader.loadTexture( 'scrollDown.png' ), verticalScroll_thumb_frameTexture = loader.loadTexture( 'scrollBar.png' ), horizontalScroll_frameSize = [0,1,0,self.dragbarSize], horizontalScroll_frameTexture = loader.loadTexture( 'bottomBorder.png' ), horizontalScroll_incButton_frameTexture = loader.loadTexture( 'scrollDown.png' ), horizontalScroll_decButton_frameTexture = loader.loadTexture( 'scrollDown.png' ), horizontalScroll_thumb_frameTexture = loader.loadTexture( 'scrollBar.png' ), ) # child we attach should be inside the window DirectFrame.__init__( self, parent = self.contentWindow.getCanvas(), pos = (0,0,self.virtualSize[1]), scale = (1,1,1), frameSize = ( 0, self.virtualSize[0]+2*self.borderSize, 0, self.virtualSize[1] ), #frameColor = (0,0,0,1), relief = DGG.RIDGE, borderWidth = (0,0), ) self.initialiseoptions(DirectWindow) # offset then clicking on the resize button from the mouse to the resizebutton # position, required to calculate the position / scaling self.offset = None self.resizeButtonTaskName = "resizeTask-%s" % str(hash(self)) # do sizing of the window to virtualSize #self.resize( self.virtualSize[0]+2*self.borderSize # , self.virtualSize[1]+self.dragbarSize+2*self.borderSize ) self.resize(10,10) # a task that keeps a node at the position of the mouse-cursor def mouseNodeTask(self, task): if WindowManager.hasMouse(): x=WindowManager.getMouseX() y=WindowManager.getMouseY() # the mouse position is read relative to render2d, so set it accordingly self.mouseNode.setPos( render2d, x, 0, y ) return task.cont # dragging functions def startWindowDrag( self, param ): self.parentWindow.wrtReparentTo( self.mouseNode ) self.ignoreAll() self.accept( 'mouse1-up', self.stopWindowDrag ) def stopWindowDrag( self, param=None ): # this could be called even after the window has been destroyed #if self: # this is called 2 times (bug), so make sure it's not already parented to aspect2d if self.parentWindow.getParent() != self.parent: self.parentWindow.wrtReparentTo(self.parent) self.ignoreAll() # resize functions def startResizeDrag(self, param): self.offset = self.resizeButton.getPos(aspect2d) - self.mouseNode.getPos(aspect2d) taskMgr.remove( self.resizeButtonTaskName ) taskMgr.add( self.resizeButtonTask, self.resizeButtonTaskName ) self.accept( 'mouse1-up', self.stopResizeDrag,['x'] ) def resize(self,windowX,windowY): # limit max/min size of the window maxX = min(self.maxWindowSize[0], self.virtualSize[0]+2*self.borderSize) minX = max( self.dragbarSize*3, self.minWindowSize[0]) windowWidth = min( maxX, max( minX, windowX ) ) maxY = min( self.maxWindowSize[1], self.virtualSize[1]+self.dragbarSize+2*self.borderSize ) minY = max( self.dragbarSize*4, self.minWindowSize[1]) windowHeight = min( maxY, max( minY, windowY ) ) if self.collapsed: windowHeight = 2*self.dragbarSize+2*self.borderSize windowWidth = windowWidth self.contentWindow.hide() # store changed window width only self.previousSize = windowWidth, self.previousSize[1] else: self.contentWindow.show() self.previousSize = windowWidth, windowHeight # set the window size self.headerParent['frameSize'] = (0, windowWidth, 0, 1) self.headerCenter['frameSize'] = (0, windowWidth, 0, 1) self.parentWindow['frameSize'] = (0, windowWidth, 0, windowHeight) self.contentWindow['frameSize'] = (0, windowWidth-self.borderSize*2, 0, windowHeight-self.dragbarSize-2*self.borderSize) self.contentWindow.setPos(self.borderSize,0,windowHeight-self.borderSize) self.headerRight.setPos(windowWidth-self.dragbarSize, 0, 1) self.textNodePath.setPos(windowWidth/2.,0,.3) self.resizeButton.setPos(windowWidth-self.dragbarSize, 0, windowHeight) def resizeButtonTask(self, task=None): mPos = self.mouseNode.getPos(self.parentWindow) # max height, the smaller of (given maxWindowSize and real size of content and borders windowX = mPos.getX() + self.offset.getX() + self.dragbarSize windowY = mPos.getZ() - self.offset.getZ() self.resize(windowX,windowY) return task.cont def stopResizeDrag(self, param): taskMgr.remove( self.resizeButtonTaskName ) self.ignoreAll() # a bugfix for a wrong implementation def detachNode( self ): self.parentWindow.detachNode() #self. = None #DirectFrame.detachNode( self ) def removeNode( self ): self.parentWindow.removeNode() #DirectFrame.removeNode( self ) def toggleCollapsed(self,state=None): if state is None: state=not self.collapsed if state: self.collapse() else: self.uncollapse() def collapse(self): self.collapsed = True self.resize(*self.previousSize) def uncollapse(self): self.collapsed = False self.resize(*self.previousSize)