def _createComponents(self): self.debug("Creating GUI Components") self.categoryMenu = self.parent.attachNewNode("CategoryMenu") self.categoryMenu.setPos(-350, 0, -49) self.sidebar = self.parent.attachNewNode("EditorSidebar") self.sidebarBackground = DirectFrame( parent=self.sidebar, pos=(0, 0, 0), frameSize=(0, 92, 0, -base.win.getYSize()), frameColor=(0.05, 0.05, 0.05, 1.0), ) self.logo = BetterOnscreenImage( parent=self.sidebar, transparent=False, image="Editor/GUI/logo.png", x=0, y=0, w=92, h=48 ) self.categoriesParent = self.sidebar.attachNewNode("Categories") self.categoriesParent.setPos(0, 0, -48) self.categoryIcons = {} self.animations = {"moveMenuArrow": None, "moveCategoryMenu": None} for index, category in enumerate(EditorCategories.Categories): iconDefault = "Editor/GUI/Icon-" + category.name + ".png" iconHover = "Editor/GUI/Icon-" + category.name + "-Hover.png" # iconActive = "Editor/GUI/Icon-" + category.name + "-Hover.png" self.categoryIcons[category.name] = BetterOnscreenImage( parent=self.categoriesParent, transparent=False, image=iconDefault, x=0, y=94 * index, w=92, h=94 ) # i hate direct gui hoverCatch = DirectFrame( parent=self.categoriesParent, frameSize=(0, 92, 0, -94), pos=(0, 0, -94 * index), frameColor=(0, 0, 0, 0), state=DGG.NORMAL, ) # Add a hover effect hoverCatch.bind(DGG.ENTER, partial(self._showCategoryMenu, category.name)) hoverCatch.bind(DGG.EXIT, partial(self._hideCategoryMenu, category.name)) self.currentCategoryMarker = BetterOnscreenImage( parent=self.categoriesParent, image="Editor/GUI/Arrow-Right.png", x=92, y=0, w=11, h=21 ) self.currentCategoryMarker.hide() self.categoryMenuBg = DirectFrame( parent=self.categoryMenu, pos=(15, 0, 0), frameSize=(0, 300, 0, -400), frameColor=(0.2, 0.2, 0.2, 1.0) )
def renderBuffers(self): self.buffersParent.node().removeAllChildren() posX = 0 posY = 0 for name in self.bufferOrder: target = self.buffers[name] for targetType in RenderTargetType.All: if not target.hasTarget(targetType): continue tex = target.getTexture(targetType) sizeStr = str(tex.getXSize()) + " x " + str(tex.getYSize()) if tex.getZSize() != 1: sizeStr += " x " + str(tex.getZSize()) sizeStr += " - " + str(self.calculateTexSize(tex)) + " MB" node = DirectFrame(parent=self.buffersParent, frameColor=( 1, 1, 1, 0.2), frameSize=(-self.innerPadding, self.texWidth + self.innerPadding, -self.texHeight - 30 - self.innerPadding, self.innerPadding + 15), state=DGG.NORMAL) node.setPos( 20 + posX * (self.texWidth + self.texPadding), 0, -self.paddingTop - 22 - posY * (self.texHeight + self.texPadding + 44)) node.bind(DGG.ENTER, partial(self.onMouseOver, node)) node.bind(DGG.EXIT, partial(self.onMouseOut, node)) node.bind(DGG.B1RELEASE, partial(self.showDetail, tex)) aspect = tex.getYSize() / float(tex.getXSize()) computedWidth = self.texWidth computedHeight = self.texWidth * aspect if computedHeight > self.texHeight: # have to scale tex width instead computedHeight = self.texHeight computedWidth = tex.getXSize() / float(tex.getYSize()) * \ self.texHeight img = BetterOnscreenImage( image=tex, parent=node, x=0, y=30, w=computedWidth, h=computedHeight, transparent=False, nearFilter=False, anyFilter=False) txtName = BetterOnscreenText( text=name, x=0, y=0, size=13, parent=node) txtSizeFormat = BetterOnscreenText( text=sizeStr, x=0, y=20, size=13, parent=node, color=Vec3(0.2)) txtTarget = BetterOnscreenText( text=str(targetType), align="right", x=self.texWidth, y=20, size=13, parent=node, color=Vec3(0.2)) posX += 1 if posX > self.pageSize: posY += 1 posX = 0
class SocketBase: def __init__(self, node, name): self.socketID = uuid4() self.node = node self.name = name self.height = 0.2 self.type = None self.value = None self.connected = False def getValue(self): """Returns a string serializable value stored in this node""" return self.value def setValue(self, value): self.value = value def createPlug(self, parent): self.plug = DirectFrame( state=DGG.NORMAL, frameColor=(0, 0, 0, 1), frameSize=(-0.05, 0.05, -0.05, 0.05), parent=parent, ) self.setupBind() def setupBind(self): self.plug.bind(DGG.B1PRESS, self.startPlug) self.plug.bind(DGG.B1RELEASE, self.releasePlug) self.plug.bind(DGG.ENTER, self.endPlug) def startPlug(self, event): base.messenger.send("startPlug", [self]) base.messenger.send("startLineDrawing", [self.plug.getPos(render2d)]) def endPlug(self, event): taskMgr.remove("delayedPlugRelease") base.messenger.send("endPlug", [self]) base.messenger.send("connectPlugs") def releasePlug(self, event): base.messenger.send("stopLineDrawing") taskMgr.doMethodLater(0.2, base.messenger.send, "delayedPlugRelease", extraArgs=["cancelPlug"]) def updateConnectedNodes(self, *args): base.messenger.send("updateConnectedNodes", [self.node]) def setConnected(self, connected): self.connected = connected
class NodeBase(DirectObject): def __init__(self, name, parent): self.right = 0.5 self.left = -0.5 self.name = name self.nodeID = uuid4() self.inputList = [] self.outputList = [] self.selected = False self.frame = DirectFrame(state=DGG.NORMAL, text=name, text_align=TextNode.A_left, text_scale=0.1, text_pos=(self.left, 0.12), text_fg=(1, 1, 1, 1), frameColor=(0.25, 0.25, 0.25, 1), frameSize=(-0.5, 0.5, -.6, 0.2), parent=parent) self.setupBind() self.hide() self.setPos = self.frame.setPos self.getPos = self.frame.getPos def addIn(self, name, socketType): """Add a new input socket of the given socket type""" inSocket = socketType(self, name) self.inputList.append(inSocket) def addOut(self, name): """Add a new output socket""" outSocket = OutSocket(self, name) self.outputList.append(outSocket) def isLeaveNode(self): """Returns true if this is a leave node. Leave nodes do not have any input connections. Either if no input sockets are defined at all or none of the sockets is connected.""" # check if we have any input sockets and if so if any of them is connected for inSocket in self.inputList: if inSocket.connected: return False return True def logic(self): """Run the logic of this node, process all in and output data. This is a stub and should be overwritten by the derived classes""" pass def update(self): """Show all sockets and resize the frame to fit all sockets in""" z = 0 for outSocket in self.outputList: outSocket.show(z, self.right) z -= outSocket.height for inSocket in self.inputList: inSocket.show(z, self.left) z -= inSocket.height fs = self.frame["frameSize"] self.frame["frameSize"] = (fs[0], fs[1], z, fs[3]) def create(self): """Place and show the node under the mouse and start draging it.""" mwn = base.mouseWatcherNode if mwn.hasMouse(): newPos = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1]) self.frame.setPos(render2d, newPos) self._dragStart(self.frame, None) self.show() def show(self): """Shows the Node frame and updates its sockets""" self.update() self.frame.show() def hide(self): """Hide the Node frame""" self.frame.hide() def destroy(self): self.frame.destroy() def setupBind(self): """Setup the mousebutton actions for drag and drop feature""" self.frame.bind(DGG.B1PRESS, self._dragStart, [self.frame]) self.frame.bind(DGG.B1RELEASE, self._dragStop) def select(self, select): """Set this node as selected or deselected""" if self.selected == select: return self.selected = select if self.selected: self.frame["frameColor"] = (0.45, 0.45, 0.45, 1) else: self.frame["frameColor"] = (0.25, 0.25, 0.25, 1) def _dragStart(self, nodeFrame, event): # Mark this node as selected base.messenger.send("selectNode", [ self, True, base.mouseWatcherNode.isButtonDown(KeyboardButton.shift()), True ]) # tell everyone we started to drag this node base.messenger.send("dragNodeStart", [self]) # Remove any previous started drag tasks taskMgr.remove("dragNodeDropTask") # get some positions vWidget2render2d = nodeFrame.getPos(render2d) vMouse2render2d = Point3(0) if event is not None: # we get the mouse position from the event vMouse2render2d = Point3(event.getMouse()[0], 0, event.getMouse()[1]) else: # we try to get the current mouse position from the mouse watcher mwn = base.mouseWatcherNode if mwn.hasMouse(): vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1]) editVec = Vec3(vWidget2render2d - vMouse2render2d) self.hasMoved = False # Initiate the task to move the node and pass it some initial values t = taskMgr.add(self.dragTask, "dragNodeDropTask") t.nodeFrame = nodeFrame t.editVec = editVec t.mouseVec = vMouse2render2d def dragTask(self, t): mwn = base.mouseWatcherNode if mwn.hasMouse(): # get the current mouse position fitting for a render2d position vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1]) # check if the cursor has moved enough to drag this node # this gives us some puffer zone for clicking if not self.hasMoved and (t.mouseVec - vMouse2render2d).length() < 0.01: return t.cont # We actually have moved now self.hasMoved = True # calculate the new position newPos = vMouse2render2d + t.editVec # move the node to the new position t.nodeFrame.setPos(render2d, newPos) # tell everyone we moved the node base.messenger.send("dragNodeMove", [t.mouseVec, vMouse2render2d]) return t.cont def _dragStop(self, event=None): self.ignore("mouse1-up") # remove the node dragging task taskMgr.remove("dragNodeDropTask") # check if the node has moved if not self.hasMoved: # we want to select this node as it has not been moved base.messenger.send("selectNode", [ self, True, base.mouseWatcherNode.isButtonDown(KeyboardButton.shift()) ]) # tell everyone we stopped moving the node base.messenger.send("dragNodeStop", [self]) def getLeftEdge(self): """Get the left edge of the frame as seen from the frame""" return self.frame["frameSize"][0] def getRightEdge(self): """Get the right edge of the frame as seen from the frame""" return self.frame["frameSize"][1] def getBottomEdge(self): """Get the bottom edge of the frame as seen from the frame""" return self.frame["frameSize"][2] def getTopEdge(self): """Get the top edge of the frame as seen from the frame""" return self.frame["frameSize"][3] def getLeft(self, np=None): """Get left edge of the frame with respect to it's position as seen from the given np""" if np is None: np = render2d return self.getPos(render2d).getX() + self.frame["frameSize"][0] def getRight(self, np=None): """Get right edge of the frame with respect to it's position as seen from the given np""" if np is None: np = render2d return self.getPos(render2d).getX() + self.frame["frameSize"][1] def getBottom(self, np=None): """Get bottom edge of the frame with respect to it's position as seen from the given np""" if np is None: np = render2d return self.getPos(render2d).getZ() + self.frame["frameSize"][2] def getTop(self, np=None): """Get top edge of the frame with respect to it's position as seen from the given np""" if np is None: np = render2d return self.getPos(render2d).getZ() + self.frame["frameSize"][3]
def _createComponents(self): self.debug("Creating GUI Components") self.categoryMenu = self.parent.attachNewNode("CategoryMenu") self.categoryMenu.setPos(-350, 0, -49) self.sidebar = self.parent.attachNewNode("EditorSidebar") self.sidebarBackground = DirectFrame(parent=self.sidebar, pos=(0, 0, 0), frameSize=(0, 92, 0, -base.win.getYSize()), frameColor=(0.05, 0.05, 0.05, 1.0)) self.logo = BetterOnscreenImage(parent=self.sidebar, transparent=False, image="Editor/GUI/logo.png", x=0, y=0, w=92, h=48) self.categoriesParent = self.sidebar.attachNewNode("Categories") self.categoriesParent.setPos(0, 0, -48) self.categoryIcons = {} self.animations = {"moveMenuArrow": None, "moveCategoryMenu": None} for index, category in enumerate(EditorCategories.Categories): iconDefault = "Editor/GUI/Icon-" + category.name + ".png" iconHover = "Editor/GUI/Icon-" + category.name + "-Hover.png" # iconActive = "Editor/GUI/Icon-" + category.name + "-Hover.png" self.categoryIcons[category.name] = BetterOnscreenImage( parent=self.categoriesParent, transparent=False, image=iconDefault, x=0, y=94 * index, w=92, h=94) # i hate direct gui hoverCatch = DirectFrame(parent=self.categoriesParent, frameSize=(0, 92, 0, -94), pos=(0, 0, -94 * index), frameColor=(0, 0, 0, 0), state=DGG.NORMAL) # Add a hover effect hoverCatch.bind(DGG.ENTER, partial(self._showCategoryMenu, category.name)) hoverCatch.bind(DGG.EXIT, partial(self._hideCategoryMenu, category.name)) self.currentCategoryMarker = BetterOnscreenImage( parent=self.categoriesParent, image="Editor/GUI/Arrow-Right.png", x=92, y=0, w=11, h=21) self.currentCategoryMarker.hide() self.categoryMenuBg = DirectFrame(parent=self.categoryMenu, pos=(15, 0, 0), frameSize=(0, 300, 0, -400), frameColor=(0.2, 0.2, 0.2, 1.0))
def _render_stages(self): """ Renders the stages to the window """ self._remove_components() entries_per_row = 8 aspect = Globals.base.win.get_y_size() /\ float(Globals.base.win.get_x_size()) entry_width = 180 entry_height = (entry_width - 20) * aspect + 55 # Store already processed images processed = set() index = -1 # Iterate over all stages for stage_tex in self._stages: if stage_tex in processed: continue processed.add(stage_tex) index += 1 stage_name = stage_tex.get_name() xoffs = index % entries_per_row yoffs = index // entries_per_row node = self._content_node.attach_new_node("Preview") node.set_sz(-1) node.set_pos(10 + xoffs * (entry_width - 14), 1, yoffs * (entry_height-14)) if stage_name.startswith("Image"): r, g, b = 0.4, 0.4, 0.4 else: r, g, b = rgb_from_string(stage_name) DirectFrame( parent=node, frameSize=(7, entry_width - 17, -7, -entry_height + 17), frameColor=(r, g, b, 1.0), pos=(0, 0, 0)) frame_hover = DirectFrame( parent=node, frameSize=(0, entry_width - 10, 0, -entry_height + 10), frameColor=(0, 0, 0, 0), pos=(0, 0, 0), state=DGG.NORMAL) frame_hover.bind( DGG.ENTER, partial(self._on_texture_hovered, frame_hover)) frame_hover.bind( DGG.EXIT, partial(self._on_texture_blurred, frame_hover)) frame_hover.bind( DGG.B1PRESS, partial(self._on_texture_clicked, stage_tex)) BetterOnscreenText(text=stage_name, x=15, y=29, parent=node, size=12, color=Vec3(0.2)) # Scale image so it always fits w, h = stage_tex.get_x_size(), stage_tex.get_y_size() scale_x = float(entry_width - 30) / max(1, w) scale_y = float(entry_height - 60) / max(1, h) scale_factor = min(scale_x, scale_y) if stage_tex.get_texture_type() == Texture.TT_buffer_texture: scale_factor = 1 w = entry_width - 30 h = entry_height - 60 preview = BetterOnscreenImage( image=stage_tex, w=scale_factor * w, h=scale_factor * h, any_filter=False, parent=node, x=10, y=40, transparent=False) preview.set_shader_input("mipmap", 0) preview.set_shader_input("slice", 0) preview_shader = DisplayShaderBuilder.build(stage_tex, scale_factor*w, scale_factor*h) preview.set_shader(preview_shader)
def _render_stages(self): """ Renders the stages to the window """ self._remove_components() entries_per_row = 6 aspect = Globals.native_resolution.y / Globals.native_resolution.x entry_width = 235 entry_height = (entry_width - 20) * aspect + 55 # Store already processed images processed = set() index = -1 # Iterate over all stages for stage_tex in self._stages: if stage_tex in processed: continue processed.add(stage_tex) index += 1 stage_name = stage_tex.get_name() xoffs = index % entries_per_row yoffs = index // entries_per_row node = self._content_node.attach_new_node("Preview") node.set_sz(-1) node.set_pos(10 + xoffs * (entry_width - 14), 1, yoffs * (entry_height - 14 + 10)) r, g, b = 0.2, 0.2, 0.2 if isinstance(stage_tex, Image): r, g, b = 0.2, 0.4, 0.6 stage_name = stage_name.replace("render_pipeline_internal:", "") parts = stage_name.split(":") stage_name = parts[-1] DirectFrame( parent=node, frameSize=(7, entry_width - 17, -7, -entry_height + 17), frameColor=(r, g, b, 1.0), pos=(0, 0, 0)) frame_hover = DirectFrame( parent=node, frameSize=(0, entry_width - 10, 0, -entry_height + 10), frameColor=(0, 0, 0, 0), pos=(0, 0, 0), state=DGG.NORMAL) frame_hover.bind( DGG.ENTER, partial(self._on_texture_hovered, frame_hover)) frame_hover.bind( DGG.EXIT, partial(self._on_texture_blurred, frame_hover)) frame_hover.bind( DGG.B1PRESS, partial(self._on_texture_clicked, stage_tex)) Text(text=stage_name, x=15, y=29, parent=node, size=12, color=Vec3(0.8)) # Scale image so it always fits w, h = stage_tex.get_x_size(), stage_tex.get_y_size() padd_x, padd_y = 24, 57 scale_x = (entry_width - padd_x) / max(1, w) scale_y = (entry_height - padd_y) / max(1, h) scale_factor = min(scale_x, scale_y) if stage_tex.get_texture_type() == Image.TT_buffer_texture: scale_factor = 1 w = entry_width - padd_x h = entry_height - padd_y preview = Sprite( image=stage_tex, w=scale_factor * w, h=scale_factor * h, any_filter=False, parent=node, x=7, y=40, transparent=False) preview.set_shader_input("mipmap", 0) preview.set_shader_input("slice", 0) preview.set_shader_input("brightness", 1) preview.set_shader_input("tonemap", False) preview_shader = DisplayShaderBuilder.build( stage_tex, scale_factor * w, scale_factor * h) preview.set_shader(preview_shader) num_rows = (index + entries_per_row) // entries_per_row self._set_scroll_height(50 + (entry_height - 14 + 10) * num_rows)
class DraggableWindow(RPObject): """ This is a simple draggable but not resizeable window """ def __init__(self, width=800, height=500, title="Window", parent=None): """ Constructs a new window with the given dimensions and title """ RPObject.__init__(self, "Window-" + title) self._width = width self._height = height self._title = title self._visible = True self._parent = parent if parent else Globals.base.pixel2d self._dragging = False self._drag_offset = Vec2(0) self._pos = Vec2(0) def center_on_screen(self): """ Centers the window on screen """ self._context_scale = 1.0 / self._parent.get_sx() self._context_width = Globals.native_resolution.x * self._context_scale self._context_height = Globals.native_resolution.y * self._context_scale self._set_pos( Vec2((self._context_width - self._width) / 2, (self._context_height - self._height) / 2)) def set_title(self, title): """ Sets the window title """ self._title = title self._window_title.set_text(title) def show(self): """ Shows the window """ self._visible = True self.center_on_screen() self._node.show() def hide(self): """ Hides the window """ self._visible = False self._stop_drag() self._node.hide() def remove(self): """ Removes the window from the scene graph. You should still delete the instance """ self._stop_drag() self._node.remove_node() def _create_components(self): """ Creates the window components """ self._node = self._parent.attach_new_node("Window") self._node.set_pos(self._pos.x, 1, -self._pos.y) border_px = 1 border_frame_size = (-border_px, self._width + border_px, border_px, -self._height - border_px) self._border_frame = DirectFrame(pos=(0, 1, 0), frameSize=border_frame_size, frameColor=(24 / 255.0, 131 / 255.0, 215 / 255.0, 1), parent=self._node, state=DGG.NORMAL) self._background = DirectFrame(pos=(0, 1, 0), frameSize=(0, self._width, 0, -self._height), frameColor=(0.1, 0.1, 0.1, 1.0), parent=self._node) self._title_bar = DirectFrame( pos=(0, 1, 0), frameSize=(0, self._width, 0, -25), # frameColor=(0.058, 0.058, 0.058, 1), frameColor=(1, 1, 1, 1), parent=self._node, state=DGG.NORMAL) self._window_title = Text(parent=self._node, x=8, y=17, text=self._title, size=13, color=Vec3(0.15), may_change=True) self._btn_close = DirectButton(relief=DGG.FLAT, pressEffect=1, pos=(self._width - 22, 1, -12), frameColor=(1.0, 0.2, 0.2, 0.5), parent=self._node, scale=(45 / 2, 1, 24 / 2), image="/$$rp/data/gui/close_window.png") # Init bindings self._btn_close.set_transparency(TransparencyAttrib.M_alpha) self._btn_close.bind(DGG.B1CLICK, self._request_close) self._btn_close.bind(DGG.WITHIN, self._on_close_btn_hover) self._btn_close.bind(DGG.WITHOUT, self._on_close_btn_out) self._title_bar.bind(DGG.B1PRESS, self._start_drag) self._title_bar.bind(DGG.B1RELEASE, self._stop_drag) def _start_drag(self, evt=None): # pylint: disable=unused-argument """ Gets called when the user starts dragging the window """ self._dragging = True self._node.detach_node() self._node.reparent_to(self._parent) Globals.base.taskMgr.add(self._on_tick, "UIWindowDrag", uponDeath=self._stop_drag) self._drag_offset = self._pos - self._get_mouse_pos() def _on_close_btn_hover(self, evt=None): # pylint: disable=unused-argument """ Internal method when the close button got hovered """ self._btn_close["image"] = "/$$rp/data/gui/close_window_hover.png" def _on_close_btn_out(self, evt=None): # pylint: disable=unused-argument """ Internal method when the close button is no longer hovered """ self._btn_close["image"] = "/$$rp/data/gui/close_window.png" def _request_close(self, evt=None): # pylint: disable=unused-argument """ This method gets called when the close button gets clicked """ self.hide() def _stop_drag(self, evt=None): # pylint: disable=unused-argument """ Gets called when the user stops dragging the window """ Globals.base.taskMgr.remove("UIWindowDrag") self._dragging = False def _get_mouse_pos(self): """ Internal helper function to get the mouse position, scaled by the context scale """ mouse_x, mouse_y = (Globals.base.win.get_pointer(0).x, Globals.base.win.get_pointer(0).y) return Vec2(mouse_x, mouse_y) * self._context_scale def _set_pos(self, pos): """ Moves the window to the specified position """ self._pos = pos self._pos.x = max(self._pos.x, -self._width + 100) self._pos.y = max(self._pos.y, 25) self._pos.x = min(self._pos.x, self._context_width - 100) self._pos.y = min(self._pos.y, self._context_height - 50) self._node.set_pos(self._pos.x, 1, -self._pos.y) def _on_tick(self, task): """ Task which updates the window while being dragged """ self._set_pos(self._get_mouse_pos() + self._drag_offset) return task.cont
class SocketBase: def __init__(self, node, name): self.socketID = uuid4() self.node = node self.name = name self.height = 0.2 self.type = None self.value = None self.connected = False self.frame = None self.allowMultiConnect = False def enable(self): """Enable any elements on the node""" pass def disable(self): """Disable any elements on the node that could possbily interfer with the mouse watcher and the drag/drop feature""" pass def getValue(self): """Returns a string serializable value stored in this node""" return self.value def setValue(self, value): self.value = value def createPlug(self, parent): self.plug = DirectFrame( state=DGG.NORMAL, image="icons/Plug.png", image_scale=.05, frameColor=(0, 0, 0, 0), frameSize=(-0.05, 0.05, -0.05, 0.05), parent=parent, ) self.plug.setTransparency(TransparencyAttrib.M_multisample) self.setupBind() def setupBind(self): self.plug.bind(DGG.B1PRESS, self.startPlug) self.plug.bind(DGG.B1RELEASE, self.releasePlug) self.plug.bind(DGG.ENTER, self.endPlug) def startPlug(self, event): base.messenger.send("startPlug", [self]) base.messenger.send("startLineDrawing", [self.plug.getPos(render2d)]) def endPlug(self, event): taskMgr.remove("delayedPlugRelease") base.messenger.send("endPlug", [self]) base.messenger.send("connectPlugs") def releasePlug(self, event): base.messenger.send("stopLineDrawing") taskMgr.doMethodLater(0.2, base.messenger.send, "delayedPlugRelease", extraArgs=["cancelPlug"]) def updateConnectedNodes(self, *args): base.messenger.send("updateConnectedNodes", [self.node]) def setConnected(self, connected): self.connected = connected if self.connected: self.plug["image"] = "icons/PlugConnectedGood.png" else: self.plug["image"] = "icons/Plug.png"
def _render_stages(self): """ Renders the stages to the window """ self._remove_components() entries_per_row = 6 aspect = Globals.native_resolution.y / Globals.native_resolution.x entry_width = 235 entry_height = (entry_width - 20) * aspect + 55 # Store already processed images processed = set() index = -1 # Iterate over all stages for stage_tex in self._stages: if stage_tex in processed: continue processed.add(stage_tex) index += 1 stage_name = stage_tex.get_name() xoffs = index % entries_per_row yoffs = index // entries_per_row node = self._content_node.attach_new_node("Preview") node.set_sz(-1) node.set_pos(10 + xoffs * (entry_width - 14), 1, yoffs * (entry_height - 14 + 10)) r, g, b = 0.2, 0.2, 0.2 if isinstance(stage_tex, Image): r, g, b = 0.2, 0.4, 0.6 stage_name = stage_name.replace("render_pipeline_internal:", "") parts = stage_name.split(":") stage_name = parts[-1] DirectFrame(parent=node, frameSize=(7, entry_width - 17, -7, -entry_height + 17), frameColor=(r, g, b, 1.0), pos=(0, 0, 0)) frame_hover = DirectFrame(parent=node, frameSize=(0, entry_width - 10, 0, -entry_height + 10), frameColor=(0, 0, 0, 0), pos=(0, 0, 0), state=DGG.NORMAL) frame_hover.bind(DGG.ENTER, partial(self._on_texture_hovered, frame_hover)) frame_hover.bind(DGG.EXIT, partial(self._on_texture_blurred, frame_hover)) frame_hover.bind(DGG.B1PRESS, partial(self._on_texture_clicked, stage_tex)) Text(text=stage_name, x=15, y=29, parent=node, size=12, color=Vec3(0.8)) # Scale image so it always fits w, h = stage_tex.get_x_size(), stage_tex.get_y_size() padd_x, padd_y = 24, 57 scale_x = (entry_width - padd_x) / max(1, w) scale_y = (entry_height - padd_y) / max(1, h) scale_factor = min(scale_x, scale_y) if stage_tex.get_texture_type() == Image.TT_buffer_texture: scale_factor = 1 w = entry_width - padd_x h = entry_height - padd_y preview = Sprite(image=stage_tex, w=scale_factor * w, h=scale_factor * h, any_filter=False, parent=node, x=7, y=40, transparent=False) preview.set_shader_input("mipmap", 0) preview.set_shader_input("slice", 0) preview.set_shader_input("brightness", 1) preview.set_shader_input("tonemap", False) preview_shader = DisplayShaderBuilder.build( stage_tex, scale_factor * w, scale_factor * h) preview.set_shader(preview_shader) num_rows = (index + entries_per_row) // entries_per_row self._set_scroll_height(50 + (entry_height - 14 + 10) * num_rows)
class DraggableWindow(DebugObject): """ This is a simple draggable but not resizeable window """ def __init__(self, width=800, height=500, title="Window", parent=None): DebugObject.__init__(self, "Window-" + title) self._width = width self._height = height self._title = title self._visible = True self._parent = parent if parent else Globals.base.pixel2d self._context_scale = 1.0 / parent.get_sx() self._context_width = Globals.base.win.get_x_size() * self._context_scale self._context_height = Globals.base.win.get_y_size() * self._context_scale self._pos = Vec2((self._context_width - self._width) / 2, (self._context_height - self._height) / 2) self._dragging = False self._drag_offset = Vec2(0) def set_title(self, title): """ Sets the window title """ self._title = title self._window_title.set_text(title) def show(self): """ Shows the window """ self._visible = True self._node.show() def hide(self): """ Hides the window """ self._visible = False self._stop_drag() self._node.hide() def remove(self): """ Removes the window from the scene graph. You should still delete the instance """ self._stop_drag() self._node.remove_node() def _create_components(self): """ Creates the window components """ self._node = self._parent.attach_new_node("Window") self._node.set_pos(self._pos.x, 1, -self._pos.y) border_px = 1 self._border_frame = DirectFrame( pos=(0, 1, 0), frameSize=(-border_px, self._width + border_px, border_px, -self._height - border_px), frameColor=(0.0, 0.0, 0.0, 1), parent=self._node, state=DGG.NORMAL, ) # self._border_frame.hide() self._background = DirectFrame( pos=(0, 1, 0), frameSize=(0, self._width, 0, -self._height), frameColor=(0.098, 0.098, 0.098, 1), parent=self._node, ) self._title_bar = DirectFrame( pos=(0, 1, 0), frameSize=(0, self._width, 0, -45), frameColor=(0.058, 0.058, 0.058, 1), parent=self._node, state=DGG.NORMAL, ) self._window_title = BetterOnscreenText( parent=self._node, x=12, y=29, text=self._title, size=19, color=Vec3(0.7), may_change=True ) self._btn_close = DirectButton( relief=DGG.FLAT, pressEffect=1, pos=(self._width - 22, 1, -22), frameColor=(0, 0, 0, 0), scale=(20, 1, 20), parent=self._node, image="Data/GUI/CloseWindow.png", ) # Init bindings self._btn_close.set_transparency(TransparencyAttrib.M_alpha) self._btn_close.bind(DGG.B1CLICK, self._request_close) self._btn_close.bind(DGG.WITHIN, self._on_close_btn_hover) self._btn_close.bind(DGG.WITHOUT, self._on_close_btn_out) self._title_bar.bind(DGG.B1PRESS, self._start_drag) self._title_bar.bind(DGG.B1RELEASE, self._stop_drag) def _start_drag(self, evt=None): """ Gets called when the user starts dragging the window """ self._dragging = True self._node.detach_node() self._node.reparent_to(self._parent) Globals.base.taskMgr.add(self._on_tick, "UIWindowDrag", uponDeath=self._stop_drag) self._drag_offset = self._pos - self._get_mouse_pos() def _on_close_btn_hover(self, evt=None): """ Internal method when the close button got hovered """ self._btn_close["frameColor"] = (1.0, 0.2, 0.2, 1.0) def _on_close_btn_out(self, evt=None): """ Internal method when the close button is no longer hovered """ self._btn_close["frameColor"] = (0, 0, 0, 0) def _request_close(self, evt=None): """ This method gets called when the close button gets clicked """ self.hide() def _stop_drag(self, evt=None): """ Gets called when the user stops dragging the window """ Globals.base.taskMgr.remove("UIWindowDrag") self._dragging = False def _get_mouse_pos(self): """ Internal helper function to get the mouse position, scaled by the context scale """ mouse_x, mouse_y = (Globals.base.win.get_pointer(0).x, Globals.base.win.get_pointer(0).y) return Vec2(mouse_x, mouse_y) * self._context_scale def _set_pos(self, pos): """ Moves the window to the specified position """ self._pos = pos self._pos.x = max(self._pos.x, -self._width + 100) self._pos.y = max(self._pos.y, 25) self._pos.x = min(self._pos.x, self._context_width - 100) self._pos.y = min(self._pos.y, self._context_height - 50) self._node.set_pos(self._pos.x, 1, -self._pos.y) def _on_tick(self, task): """ Task which updates the window while being dragged """ self._set_pos(self._get_mouse_pos() + self._drag_offset) return task.cont
class CanvasPanel(): def __init__(self, parent): self.parent = parent self.elementHandler = None color = ( (0.8, 0.8, 0.8, 1), # Normal (0.9, 0.9, 1, 1), # Click (0.8, 0.8, 1, 1), # Hover (0.5, 0.5, 0.5, 1)) # Disabled # respect menu bar self.canvasScale = 1080 # min(base.getSize()[0], base.getSize()[1]) # we default to a 1920x1080 FHD screen self.canvasLeft = -1920/2 self.canvasRight = 1920/2 self.canvasTop = 1080/2 self.canvasBottom = -1080/2 self.visualEditor = DirectScrolledFrame( frameColor=(0.25, 0.25, 0.25, 1), canvasSize=(self.canvasLeft, self.canvasRight, self.canvasBottom, self.canvasTop), scrollBarWidth=20, # vertical scrollbar verticalScroll_value=0.5, verticalScroll_thumb_relief=DGG.FLAT, verticalScroll_incButton_relief=DGG.FLAT, verticalScroll_decButton_relief=DGG.FLAT, verticalScroll_thumb_frameColor=color, verticalScroll_incButton_frameColor=color, verticalScroll_decButton_frameColor=color, # horizontal scrollbar horizontalScroll_value=0.5, horizontalScroll_thumb_relief=DGG.FLAT, horizontalScroll_incButton_relief=DGG.FLAT, horizontalScroll_decButton_relief=DGG.FLAT, horizontalScroll_thumb_frameColor=color, horizontalScroll_incButton_frameColor=color, horizontalScroll_decButton_frameColor=color, ) self.scaleParent = DirectFrame( scale=(1,1,1) ) # store which base parent should be used for the elements self.currentVisEditorParent = base.aspect2d self.visEditorInAspect2D = True # Layouting self.sizer = DirectAutoSizer( updateOnWindowResize=False, parent=parent, child=self.visualEditor, ) # zoom scale self.minScale = DEFAULT_MIN_SCALE self.maxScale = DEFAULT_MAX_SCALE self.zoomInMultiplyer = 1.1 self.zoomOutMultiplyer = 0.9 # This frame will be the base parent for the added GUI elements self.elementHolder = DirectFrame( #frameColor=(0.25, 0.25, 0.25, 1), scale=LVecBase3f(self.canvasScale/2,1,self.canvasScale/2), parent=self.scaleParent ) self.elementHolder.bind(DGG.B1RELEASE, base.messenger.send, ["mouse3"]) # Ensure the holder frame will be streched to fill the parent self.scaleParentSizer = DirectAutoSizer( parent=self.visualEditor.canvas, child=self.scaleParent, parentGetSizeFunction=self.visualEditor.cget, parentGetSizeExtraArgs=["canvasSize"], ) self.elementHolderSizer = DirectAutoSizer( parent=self.scaleParent, child=self.elementHolder ) # The designers grid self.grid = DirectGrid(gridSize=50.0, gridSpacing=0.05,parent=self.elementHolder) self.grid.setP(90) self.grid.snapMarker.hide() self.snapToGrid = not self.grid.isHidden() self.canvasTopCenter = self.elementHolder.attachNewNode("canvasTopCenter") self.canvasBottomCenter = self.elementHolder.attachNewNode("canvasBottomCenter") self.canvasLeftCenter = self.elementHolder.attachNewNode("canvasLeftCenter") self.canvasRightCenter = self.elementHolder.attachNewNode("canvasRightCenter") self.canvasTopLeft = self.elementHolder.attachNewNode("canvasTopLeft") self.canvasTopRight = self.elementHolder.attachNewNode("canvasTopRight") self.canvasBottomLeft = self.elementHolder.attachNewNode("canvasBottomLeft") self.canvasBottomRight = self.elementHolder.attachNewNode("canvasBottomRight") # default to Aspect2D self.setVisualEditorParent(False) base.taskMgr.add(self.watchCanvasProps, "watch-canvas-properties", sort=50, priority=0) def getEditorCanvasSize(self): cs = self.elementHolder["frameSize"] if self.currentVisEditorParent == base.pixel2d: cs = self.visualEditor["canvasSize"] return cs def getEditorRootCanvas(self): return self.elementHolder def watchCanvasProps(self, task): """Watch for all properties that can be changed on the canvas and won't directly propagate down to the actual background, which is the element holder.""" self.sizer.refresh() sizeChanged = False cs = self.getEditorCanvasSize() if self.canvasLeft != cs[0]: sizeChanged = True elif self.canvasRight != cs[1]: sizeChanged = True elif self.canvasBottom != cs[2]: sizeChanged = True elif self.canvasTop != cs[3]: sizeChanged = True if sizeChanged: width = cs[1] - cs[0] height = cs[3] - cs[2] self.canvasScale = min(width, height) if self.currentVisEditorParent == base.pixel2d: if width > height: self.canvasScale *= self.visualEditor.getScale()[2] else: self.canvasScale *= self.visualEditor.getScale()[0] else: if width > height: self.canvasScale *= self.elementHolder.getScale()[2] else: self.canvasScale *= self.elementHolder.getScale()[0] #TODO: the scale probably needs to be calculated dependent on the users screen size self.elementHolder["scale"]= LVecBase3f(self.canvasScale/2,1,self.canvasScale/2), self.elementHolderSizer.refresh() self.scaleParentSizer.refresh() self.setCanvasPlacers() if self.visualEditor["frameColor"] != self.elementHolder["frameColor"]: fc = self.visualEditor["frameColor"] self.elementHolder["frameColor"] = fc self.elementHolderSizer["frameColor"] = fc self.scaleParentSizer["frameColor"] = fc self.scaleParent["frameColor"] = fc self.visualEditor return task.cont def setCanvasPlacers(self): cs = self.getEditorCanvasSize() self.canvasLeft = cs[0] self.canvasRight = cs[1] self.canvasBottom = cs[2] self.canvasTop = cs[3] # Put the nodes in their places self.canvasTopCenter.setPos(0, 0, self.canvasTop) self.canvasBottomCenter.setPos(0, 0, self.canvasBottom) self.canvasLeftCenter.setPos(self.canvasLeft, 0, 0) self.canvasRightCenter.setPos(self.canvasRight, 0, 0) self.canvasTopLeft.setPos(self.canvasLeft, 0, self.canvasTop) self.canvasTopRight.setPos(self.canvasRight, 0, self.canvasTop) self.canvasBottomLeft.setPos(self.canvasLeft, 0, self.canvasBottom) self.canvasBottomRight.setPos(self.canvasRight, 0, self.canvasBottom) def getEditorPlacer(self, placerName): placerName = placerName.lower() placerName = placerName.replace("a2d", "canvas") if placerName == "canvasTopCenter".lower(): return self.canvasTopCenter elif placerName == "canvasBottomCenter".lower(): return self.canvasBottomCenter elif placerName == "canvasLeftCenter".lower(): return self.canvasLeftCenter elif placerName == "canvasRightCenter".lower(): return self.canvasRightCenter elif placerName == "canvasTopLeft".lower(): return self.canvasTopLeft elif placerName == "canvasTopRight".lower(): return self.canvasTopRight elif placerName == "canvasBottomLeft".lower(): return self.canvasBottomLeft elif placerName == "canvasBottomRight".lower(): return self.canvasBottomRight def setElementHandler(self, elementHandler): self.elementHandler = elementHandler def setVisualEditorCanvasSize(self, newCanvasSize): self.visualEditor["canvasSize"] = newCanvasSize self.elementHolderSizer.refresh() self.scaleParentSizer.refresh() self.setCanvasPlacers() def setVisualEditorParent(self, toPixel2D): if toPixel2D: # change to pixel2d # we default to a 1920x1080 FHD screen self.canvasLeft = 0 self.canvasRight = 1920 self.canvasBottom = -1080 self.canvasTop = 0 self.setVisualEditorCanvasSize((self.canvasLeft, self.canvasRight, self.canvasBottom, self.canvasTop)) self.currentVisEditorParent = base.pixel2d # Speed up the setGridSpacing call by setting the size to 1 self.grid.setGridSize(1) self.grid.setGridSpacing(0.05 * (self.canvasScale / 2)) self.grid.setGridSize(1920*4) self.visEditorInAspect2D = False if self.elementHandler is not None: self.elementHandler.setEditorParentType(self.visEditorInAspect2D) self.elementHandler.setEditorCenter((self.visualEditor.getWidth()/2, 0, -self.visualEditor.getHeight()/2)) else: # change to aspect2d # we default to a 1920x1080 FHD screen self.canvasLeft = -1920/2 self.canvasRight = 1920/2 self.canvasTop = 1080/2 self.canvasBottom = -1080/2 self.scaleParent.setScale(1, 1, 1) self.setVisualEditorCanvasSize((self.canvasLeft, self.canvasRight, self.canvasBottom, self.canvasTop)) self.currentVisEditorParent = base.aspect2d # Speed up the setGridSpacing call by setting the size to 1 self.grid.setGridSize(1) self.grid.setGridSpacing(0.05) self.grid.setGridSize(50) self.visEditorInAspect2D = True if self.elementHandler is not None: self.elementHandler.setEditorParentType(self.visEditorInAspect2D) self.elementHandler.setEditorCenter((0, 0, 0)) # reset the zoom value self.resetZoom() self.setCanvasPlacers() def toggleVisualEditorParent(self): if self.currentVisEditorParent == base.aspect2d: self.setVisualEditorParent(True) elif self.currentVisEditorParent != base.aspect2d: self.setVisualEditorParent(False) def resizeFrame(self): self.sizer.refresh() def toggleGrid(self, enable): if enable: self.grid.show() self.snapToGrid = True else: self.grid.hide() self.snapToGrid = False def resetZoom(self): self.visualEditor["verticalScroll_range"] = (0, 1) self.visualEditor["horizontalScroll_range"] = (0, 1) if self.currentVisEditorParent != base.aspect2d: # we are in pixel2d self.getEditorRootCanvas().setScale(1,1,1) self.visualEditor.verticalScroll["value"] = 0 self.visualEditor.horizontalScroll["value"] = 0 posParentScaleX = DGH.getRealWidth(self.parent) self.minScale = DEFAULT_MIN_SCALE self.maxScale = DEFAULT_MAX_SCALE base.messenger.send("setZoomValeMinMax", [self.minScale, self.maxScale]) base.messenger.send("setZoomValue", [1]) else: # we are in aspect2d self.getEditorRootCanvas().setScale(self.canvasScale/2,1,self.canvasScale/2) self.visualEditor.verticalScroll["value"] = 0.5 self.visualEditor.horizontalScroll["value"] = 0.5 posParentScaleX = DGH.getRealWidth(self.parent) self.minScale = posParentScaleX * DEFAULT_MIN_SCALE self.maxScale = posParentScaleX * DEFAULT_MAX_SCALE base.messenger.send("setZoomValeMinMax", [self.minScale, self.maxScale]) base.messenger.send("setZoomValue", [self.canvasScale/2]) def setZoom(self, zoomValue): z = zoomValue s = self.getEditorRootCanvas().getScale() self.getEditorRootCanvas().setScale(z, s[1], z) # update scroll bars vr = self.visualEditor["verticalScroll_range"] vv = self.visualEditor.verticalScroll["value"] hr = self.visualEditor["horizontalScroll_range"] hv = self.visualEditor.horizontalScroll["value"] vw = vr[1] - vr[0] hw = hr[1] - hr[0] curPosVer = vv / vw * 100 curPosHor = hv / hw * 100 self.visualEditor["verticalScroll_range"] = (vr[0]*(z/s[0]), vr[1]*(z/s[2])) self.visualEditor["horizontalScroll_range"] = (hr[0]*(z/s[0]), hr[1]*(z/s[2])) vr = self.visualEditor["verticalScroll_range"] hr = self.visualEditor["horizontalScroll_range"] self.visualEditor.verticalScroll["value"] = (vr[1] - vr[0]) / 100 * curPosVer self.visualEditor.horizontalScroll["value"] = (hr[1] - hr[0]) / 100 * curPosHor self.elementHolderSizer.refresh() def zoom(self, direction): z = 1 s = self.getEditorRootCanvas().getScale() if direction < 0 and self.getEditorRootCanvas().getScale()[0] > self.minScale: z = self.zoomOutMultiplyer elif direction > 0 and self.getEditorRootCanvas().getScale()[0] < self.maxScale: z = self.zoomInMultiplyer self.getEditorRootCanvas().setScale(s[0]*z, s[1], s[2]*z) base.messenger.send("setZoomValue", [self.getEditorRootCanvas().getScale()[0]]) #print(self.getEditorRootCanvas().getScale()) # update scroll bars vr = self.visualEditor["verticalScroll_range"] vv = self.visualEditor.verticalScroll["value"] hr = self.visualEditor["horizontalScroll_range"] hv = self.visualEditor.horizontalScroll["value"] vw = vr[1] - vr[0] hw = hr[1] - hr[0] curPosVer = vv / vw * 100 curPosHor = hv / hw * 100 self.visualEditor["verticalScroll_range"] = (vr[0]*z, vr[1]*z) self.visualEditor["horizontalScroll_range"] = (hr[0]*z, hr[1]*z) vr = self.visualEditor["verticalScroll_range"] hr = self.visualEditor["horizontalScroll_range"] self.visualEditor.verticalScroll["value"] = (vr[1] - vr[0]) / 100 * curPosVer self.visualEditor.horizontalScroll["value"] = (hr[1] - hr[0]) / 100 * curPosHor self.elementHolderSizer.refresh() def dragEditorFrame(self, dragEnabled): taskMgr.remove("dragEditorFrameTask") mwn = base.mouseWatcherNode if dragEnabled: t = taskMgr.add(self.dragEditorFrameTask, "dragEditorFrameTask") t.vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1]) def dragEditorFrameTask(self, t): mwn = base.mouseWatcherNode if mwn.hasMouse(): vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1]) moveVec = t.vMouse2render2d - vMouse2render2d t.vMouse2render2d = vMouse2render2d newValue = self.visualEditor["verticalScroll_value"] - moveVec.getZ() if newValue <= 1 and newValue >= 0: self.visualEditor["verticalScroll_value"] = newValue elif newValue > 1: self.visualEditor["verticalScroll_value"] = 1 elif newValue < 0: self.visualEditor["verticalScroll_value"] = 0 newValue = self.visualEditor["horizontalScroll_value"] + moveVec.getX() if newValue <= 1 and newValue >= 0: self.visualEditor["horizontalScroll_value"] = newValue elif newValue > 1: self.visualEditor["horizontalScroll_value"] = 1 elif newValue < 0: self.visualEditor["horizontalScroll_value"] = 0 return t.cont
def renderBuffers(self): self.buffersParent.node().removeAllChildren() posX = 0 posY = 0 for name in self.bufferOrder: target = self.buffers[name] for targetType in RenderTargetType.All: if not target.hasTarget(targetType): continue tex = target.getTexture(targetType) sizeStr = str(tex.getXSize()) + " x " + str(tex.getYSize()) if tex.getZSize() != 1: sizeStr += " x " + str(tex.getZSize()) node = DirectFrame( parent=self.buffersParent, frameColor=(1, 1, 1, 0.2), frameSize=(-self.innerPadding, self.texWidth + self.innerPadding, -self.texHeight - 30 - self.innerPadding, self.innerPadding + 15), state=DGG.NORMAL) node.setPos( 20 + posX * (self.texWidth + self.texPadding), 0, -self.paddingTop - 22 - posY * (self.texHeight + self.texPadding + 44)) node.bind(DGG.ENTER, partial(self.onMouseOver, node)) node.bind(DGG.EXIT, partial(self.onMouseOut, node)) node.bind(DGG.B1RELEASE, partial(self.showDetail, tex)) aspect = tex.getYSize() / float(tex.getXSize()) computedWidth = self.texWidth computedHeight = self.texWidth * aspect if computedHeight > self.texHeight: # have to scale tex width instead computedHeight = self.texHeight computedWidth = tex.getXSize() / float(tex.getYSize()) * \ self.texHeight img = BetterOnscreenImage(image=tex, parent=node, x=0, y=30, w=computedWidth, h=computedHeight, transparent=False, nearFilter=False, anyFilter=False) txtName = BetterOnscreenText(text=name, x=0, y=0, size=15, parent=node) txtSizeFormat = BetterOnscreenText(text=sizeStr, x=0, y=20, size=15, parent=node, color=Vec3(0.2)) txtTarget = BetterOnscreenText(text=str(targetType), align="right", x=self.texWidth, y=20, size=15, parent=node, color=Vec3(0.2)) posX += 1 if posX > self.pageSize: posY += 1 posX = 0