class Layout(object): def __init__(self, width, height, parent=None, frameColor=(1, 1, 1, 1), frameSize=(0, 0.5, -0.5, 0)): self.width = width self.height = height if parent is None: parent = aspect2d self.parent = parent self.frame = DirectFrame(parent=parent, frameColor=frameColor, frameSize=frameSize, state=DGG.DISABLED) self.frame.setPos(0, 0, 0) self.children = [[None for y in range(self.height)] for x in range(self.width)] self.children_width = [[0.0 for y in range(self.height)] for x in range(self.width)] self.children_height = [[0.0 for y in range(self.height)] for x in range(self.width)] def set_child(self, x, y, child): if x >= self.width or y >= self.height: return child.reparent_to(self.frame) self.children[x][y] = child bounds = child.getBounds() if bounds is not None: width = bounds[1] - bounds[0] height = bounds[3] - bounds[2] self.children_width[x][y] = width self.children_height[x][y] = height else: self.children_width[x][y] = 0 self.children_height[x][y] = 0 def recalc_positions(self): max_widths = [] for x in range(self.width): max_width = 0.0 for y in range(self.height): max_width = max(max_width, self.children_width[x][y]) max_widths.append(max_width) max_heights = [] for y in range(self.height): max_height = 0.0 for x in range(self.width): max_height = max(max_height, self.children_height[x][y]) max_heights.append(max_height) pos_x = 0.0 for x in range(self.width): pos_y = 0.0 for y in range(self.height): pos_y -= max_heights[y] child = self.children[x][y] if child is not None: child.setPos(pos_x, 0, pos_y) pos_x += max_widths[x] self.frame['frameSize']= [0, pos_x, 0, pos_y] def destroy(self): self.frame.destroy() def reparent_to(self, parent): self.frame.reparent_to(parent)
class Window(): __base: Final[ShowBase] __name: Final[str] __mouse1_press_callbacks: Final[List[Callable[[], None]]] __frame: Final[DirectFrame] __mouse_node: Final[NodePath] __zoom: float __visible: bool def __init__(self, base: ShowBase, name: str, mouse1_press_callbacks: List[Callable[[], None]], *args, **kwargs): self.__base = base self.__name = name self.__mouse1_press_callbacks = mouse1_press_callbacks self.__visible = True self.__zoom = 1 / 5 if 'frameSize' not in kwargs: kwargs['frameSize'] = (-.8, .8, -1., 1.) self.__frame = DirectFrame(*args, parent=self.__base.aspect2d, **kwargs) self.__frame['state'] = DGG.NORMAL self.__frame.bind(DGG.B1PRESS, self.__start_drag) self.__frame.bind(DGG.B1RELEASE, self.__stop_drag) self.__frame.set_scale(self.zoom) # set sort value to ensure when window is reparented to mouse node it is on top # of everything else. self.__mouse_node = self.__base.aspect2d.attach_new_node( name + '_mouse_node', sort=100000000) self.__base.taskMgr.add(self.__mouse_node_task, name + '_mouse_node_task') def __mouse_node_task(self, task): if self.__base.mouseWatcherNode.hasMouse(): x = self.__base.mouseWatcherNode.getMouseX() y = self.__base.mouseWatcherNode.getMouseY() self.__mouse_node.setPos(self.__base.render2d, x, 0, y) return task.cont def __start_drag(self, *discarded): for c in self.__mouse1_press_callbacks: c() self.__frame.wrt_reparent_to(self.__mouse_node) def __stop_drag(self, *discarded): if self.__frame.get_parent() != self.__base.aspect2d: self.__frame.wrt_reparent_to(self.__base.aspect2d) @property def frame(self) -> DirectFrame: return self.__frame def focus(self) -> None: self.__frame.detach_node() self.__frame.reparent_to(self.__base.aspect2d) def zoom_in(self): self.__zoom += 0.05 self.frame.set_scale(self.__zoom) def zoom_out(self): self.__zoom -= 0.05 self.frame.set_scale(self.__zoom) @property def zoom(self) -> float: return self.__zoom def toggle_visible(self): if self.__visible: self.frame.hide() else: self.frame.show() self.focus() self.__visible = not self.__visible @property def visible(self) -> bool: return self.__visible