class BaseSelectOperationUI(wx.BoxSizer, BaseToolUI): def __init__(self, canvas: "EditCanvas"): wx.BoxSizer.__init__(self, wx.VERTICAL) BaseToolUI.__init__(self, canvas) self._active_operation: Optional[OperationUIType] = None horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL) self._operation_choice = SimpleChoiceAny(self.canvas) self._reload_operation = wx.BitmapButton( self.canvas, bitmap=REFRESH_ICON.bitmap(16, 16) ) self._reload_operation.SetToolTip("Reload selected operation") horizontal_sizer.Add(self._operation_choice) horizontal_sizer.Add(self._reload_operation) self.Add(horizontal_sizer) self._operation_choice.SetItems( {key: value.name for key, value in self._operations.items()} ) self._operation_choice.Bind(wx.EVT_CHOICE, self._on_operation_change) self._reload_operation.Bind(wx.EVT_BUTTON, self._reload_operation_loader) self._operation_sizer = wx.BoxSizer(wx.VERTICAL) self.Add(self._operation_sizer, 1, wx.EXPAND) # self._operation_change() @property def name(self) -> str: raise NotImplementedError def bind_events(self): pass @property def _operations(self) -> OperationStorageType: raise NotImplementedError @property def operation(self) -> str: return self._operation_choice.GetCurrentObject() def _on_operation_change(self, evt): self._operation_change() evt.Skip() def _unload_active_operation(self): if self._active_operation is not None: self._active_operation.unload() if isinstance(self._active_operation, wx.Window): self._active_operation.Destroy() elif isinstance(self._active_operation, wx.Sizer): self._operation_sizer.GetItem(self._active_operation).DeleteWindows() self._active_operation = None def _operation_change(self): operation_path = self._operation_choice.GetCurrentObject() if operation_path: operation = self._operations[operation_path] self.disable() self._active_operation = operation( self.canvas, self.canvas, self.canvas.world ) self._operation_sizer.Add( self._active_operation, *self._active_operation.wx_add_options ) self.Layout() def _reload_operation_loader(self, evt): operation_path = self._operation_choice.GetCurrentObject() if operation_path: operation_loader, success = self._operations[operation_path].reload() if success: self._operations[operation_path] = operation_loader else: log.warning(f"Couldn't successfully reload {operation_path}") self.reload_operations() self._operation_change() def enable(self): self._operation_change() self.canvas.draw_structure = False self.canvas.draw_selection = True self.canvas.selection_editable = False def disable(self): super().disable() self._unload_active_operation() def reload_operations(self): self._operation_choice.SetItems( {key: value.name for key, value in self._operations.items()} ) if not self.operation: return items = self._operation_choice.GetItems() operation_loader = self._operations[self.operation] if operation_loader.name in items and operation_loader.name != items[0]: self._operation_choice.SetValue(operation_loader.name) else: log.warning( f"Couldn't successfully reload currently selected operation: {self.operation}" ) self._operation_choice.SetValue(self._operation_choice.GetItems()[0]) self._operation_change()
class BaseOperationChoiceToolUI(wx.BoxSizer, BaseToolUI): OperationGroupName = None _active_operation: Optional[OperationUIType] def __init__(self, canvas: "EditCanvas"): wx.BoxSizer.__init__(self, wx.VERTICAL) BaseToolUI.__init__(self, canvas) self._active_operation: Optional[OperationUIType] = None self._last_active_operation_id: Optional[str] = None horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL) self._operation_choice = SimpleChoiceAny(self.canvas) self._reload_operation = wx.BitmapButton( self.canvas, bitmap=REFRESH_ICON.bitmap(16, 16) ) self._reload_operation.SetToolTip("Reload Operations") horizontal_sizer.Add(self._operation_choice) horizontal_sizer.Add(self._reload_operation) self.Add(horizontal_sizer) assert isinstance( self.OperationGroupName, str ), "OperationGroupName has not been set or is not a string." self._operations = UIOperationManager(self.OperationGroupName) self._operation_choice.SetItems( {op.identifier: op.name for op in self._operations.operations} ) self._operation_choice.Bind(wx.EVT_CHOICE, self._on_operation_change) self._reload_operation.Bind(wx.EVT_BUTTON, self._on_reload_operations) self._operation_sizer = wx.BoxSizer(wx.VERTICAL) self.Add(self._operation_sizer, 1, wx.EXPAND) @property def name(self) -> str: """The name of the group of operations.""" raise NotImplementedError @property def active_operation_id(self) -> str: """The identifier of the operation selected by the choice input. Note if in the process of changing this may be different to self._active_operation.""" return self._operation_choice.GetCurrentObject() def _on_operation_change(self, evt): """Run when the operation selection changes.""" if ( self.active_operation_id and self._last_active_operation_id != self.active_operation_id ): self._setup_operation() self.canvas.reset_bound_events() evt.Skip() def _setup_operation(self): """Remove the old operation and create the UI for the new operation.""" operation_path = self.active_operation_id if operation_path: # only reload the operation if the operation = self._operations[operation_path] if self._active_operation is not None: self._active_operation.disable() self._operation_sizer.Clear(delete_windows=True) try: self._active_operation = operation( self.canvas, self.canvas, self.canvas.world ) self._operation_sizer.Add( self._active_operation, *self._active_operation.wx_add_options ) self._active_operation.enable() except Exception as e: # If something went wrong clear the created UI self._active_operation = None self._operation_sizer.Clear(delete_windows=True) for window in self.canvas.GetChildren(): window: wx.Window # remove orphaned windows. # If the Sizer.Add method was not run it will not be in self._operation_sizer if window.GetContainingSizer() is None: window.Destroy() log.error("Error loading Operation UI.", exc_info=True) dialog = TracebackDialog( self.canvas, "Error loading Operation UI.", str(e), traceback.format_exc(), ) dialog.ShowModal() dialog.Destroy() finally: self._last_active_operation_id = operation.identifier self.Layout() def bind_events(self): if self._active_operation is not None: self._active_operation.bind_events() def enable(self): if self._active_operation is None: self._setup_operation() else: self._active_operation.enable() def disable(self): if self._active_operation is not None: self._active_operation.disable() def _on_reload_operations(self, evt): """Run when the button is pressed to reload the operations.""" self.reload_operations() def reload_operations(self): """Reload all operations and repopulate the UI.""" # store the id of the old operation operation_id = self.active_operation_id # reload the operations self._operations.reload() # repopulate the selection self._operation_choice.SetItems( {op.identifier: op.name for op in self._operations.operations} ) if operation_id: identifiers = self._operation_choice.values if identifiers: if operation_id in identifiers: self._operation_choice.SetSelection(identifiers.index(operation_id)) else: log.info(f"Operation {operation_id} was not found.") self._operation_choice.SetSelection(0) else: log.error("No operations found. Something has gone wrong.") self._setup_operation() self.canvas.reset_bound_events()
class SetBiome(SimpleOperationPanel): def __init__( self, parent: wx.Window, canvas: "EditCanvas", world: "BaseLevel", options_path: str, ): SimpleOperationPanel.__init__(self, parent, canvas, world, options_path) self.Freeze() options = self._load_options({}) self._mode = SimpleChoiceAny(self, sort=False) self._mode.SetItems({mode: lang[mode] for mode in MODES.keys()}) self._sizer.Add(self._mode, 0, Border, 5) self._mode.Bind(wx.EVT_CHOICE, self._on_mode_change) self._mode_description = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_BESTWRAP) self._sizer.Add(self._mode_description, 0, Border, 5) self._mode_description.SetLabel(MODES[self._mode.GetCurrentObject()]) self._mode_description.Fit() self._biome_choice = BiomeDefine( self, world.level_wrapper.translation_manager, wx.VERTICAL, *(options.get("original_block_options", []) or [world.level_wrapper.platform]), show_pick_biome=True, ) self._biome_choice.Bind(EVT_PICK, self._on_pick_biome_button) self._sizer.Add(self._biome_choice, 1, Border, 5) self._add_run_button() self.Thaw() def _on_mode_change(self, evt): self._mode_description.SetLabel(MODES[self._mode.GetCurrentObject()]) self._mode_description.Fit() self.Layout() evt.Skip() def _on_pick_biome_button(self, evt): """Set up listening for the biome click""" self._show_pointer = True def _on_box_click(self): if self._show_pointer: self._show_pointer = False x, y, z = self._pointer.pointer_base # TODO: replace with "get_biome(x, y, z)" if it'll be created cx, cz = block_coords_to_chunk_coords( x, z, sub_chunk_size=self.world.sub_chunk_size) offset_x, offset_z = x - 16 * cx, z - 16 * cz chunk = self.world.get_chunk(cx, cz, self.canvas.dimension) if chunk.biomes.dimension == BiomesShape.Shape3D: biome = chunk.biomes[offset_x // 4, y // 4, offset_z // 4] elif chunk.biomes.dimension == BiomesShape.Shape2D: biome = chunk.biomes[offset_x, offset_z] else: return self._biome_choice.universal_biome = chunk.biome_palette[biome] def _operation(self, world: "BaseLevel", dimension: "Dimension", selection: "SelectionGroup") -> "OperationReturnType": mode = self._mode.GetCurrentObject() iter_count = len( list(world.get_chunk_slice_box(dimension, selection, False))) for count, (chunk, slices, _) in enumerate( world.get_chunk_slice_box(dimension, selection, False)): new_biome = chunk.biome_palette.get_add_biome( self._biome_choice.universal_biome) if mode == BoxMode: if chunk.biomes.dimension == BiomesShape.Shape3D: slices = ( slice(slices[0].start // 4, math.ceil(slices[0].stop / 4)), slice(slices[1].start // 4, math.ceil(slices[1].stop / 4)), slice(slices[2].start // 4, math.ceil(slices[2].stop / 4)), ) elif chunk.biomes.dimension == BiomesShape.Shape2D: slices = (slices[0], slices[2]) else: continue elif mode == ColumnMode: if chunk.biomes.dimension == BiomesShape.Shape3D: slices = ( slice(slices[0].start // 4, math.ceil(slices[0].stop / 4)), slice(None, None, None), slice(slices[2].start // 4, math.ceil(slices[2].stop / 4)), ) elif chunk.biomes.dimension == BiomesShape.Shape2D: slices = (slices[0], slices[2]) else: continue else: raise ValueError( f"mode {mode} is not a valid mode for the Set Biome operation." ) chunk.biomes[slices] = new_biome chunk.changed = True yield (count + 1) / iter_count
class FilePanel(wx.BoxSizer, EditCanvasContainer): def __init__(self, canvas: "EditCanvas"): wx.BoxSizer.__init__(self, wx.HORIZONTAL) EditCanvasContainer.__init__(self, canvas) level = self.canvas.world self._version_text = wx.StaticText( canvas, label= f"{level.level_wrapper.platform}, {level.level_wrapper.version}", ) self.Add(self._version_text, 0) self.AddStretchSpacer(1) self._projection_button = wx.Button(canvas, label="3D") self._projection_button.Bind(wx.EVT_BUTTON, self._on_projection_button) self.Add(self._projection_button, 0, wx.TOP | wx.BOTTOM | wx.RIGHT | wx.CENTER, 5) self._location_button = wx.Button( canvas, label=", ".join([f"{s:.2f}" for s in self.canvas.camera.location])) self._location_button.Bind(wx.EVT_BUTTON, lambda evt: self.canvas.goto()) self.Add(self._location_button, 0, wx.TOP | wx.BOTTOM | wx.RIGHT | wx.CENTER, 5) self._dim_options = SimpleChoiceAny(canvas) self._dim_options.SetItems(level.level_wrapper.dimensions) if "overworld" in level.level_wrapper.dimensions: self._dim_options.SetValue("overworld") else: self._dim_options.SetValue(level.level_wrapper.dimensions[0]) self._dim_options.Bind(wx.EVT_CHOICE, self._on_dimension_change) self.Add(self._dim_options, 0, wx.TOP | wx.BOTTOM | wx.RIGHT | wx.CENTER, 5) def create_button(text, operation): button = wx.Button(canvas, label=text) button.Bind(wx.EVT_BUTTON, operation) self.Add(button, 0, wx.TOP | wx.BOTTOM | wx.RIGHT, 5) return button self._undo_button: Optional[wx.Button] = create_button( "0", lambda evt: self.canvas.undo()) self._undo_button.SetBitmap( image.icon.tablericons.arrow_back_up.bitmap(20, 20)) self._redo_button: Optional[wx.Button] = create_button( "0", lambda evt: self.canvas.redo()) self._redo_button.SetBitmap( image.icon.tablericons.arrow_forward_up.bitmap(20, 20)) self._save_button: Optional[wx.Button] = create_button( "0", lambda evt: self.canvas.save()) self._save_button.SetBitmap( image.icon.tablericons.device_floppy.bitmap(20, 20)) close_button = wx.BitmapButton( canvas, bitmap=image.icon.tablericons.square_x.bitmap(20, 20)) close_button.Bind(wx.EVT_BUTTON, lambda evt: self.canvas.close()) self.Add(close_button, 0, wx.TOP | wx.BOTTOM | wx.RIGHT, 5) self._update_buttons() self.Layout() def bind_events(self): self.canvas.Bind(EVT_CAMERA_MOVED, self._on_camera_move) self.canvas.Bind(EVT_UNDO, self._on_update_buttons) self.canvas.Bind(EVT_REDO, self._on_update_buttons) self.canvas.Bind(EVT_SAVE, self._on_update_buttons) self.canvas.Bind(EVT_CREATE_UNDO, self._on_update_buttons) self.canvas.Bind(EVT_PROJECTION_CHANGED, self._on_projection_change) self.canvas.Bind(EVT_DIMENSION_CHANGE, self._change_dimension) def _on_update_buttons(self, evt): self._update_buttons() evt.Skip() def _update_buttons(self): self._undo_button.SetLabel( f"{self.canvas.world.history_manager.undo_count}") self._redo_button.SetLabel( f"{self.canvas.world.history_manager.redo_count}") self._save_button.SetLabel( f"{self.canvas.world.history_manager.unsaved_changes}") def _on_dimension_change(self, evt): """Run when the dimension selection is changed by the user.""" dimension = self._dim_options.GetCurrentObject() if dimension is not None: self.canvas.dimension = dimension evt.Skip() def _on_projection_change(self, evt): if self.canvas.camera.projection_mode == Projection.PERSPECTIVE: self._projection_button.SetLabel("3D") elif self.canvas.camera.projection_mode == Projection.TOP_DOWN: self._projection_button.SetLabel("2D") evt.Skip() def _on_projection_button(self, evt): if self.canvas.camera.projection_mode == Projection.PERSPECTIVE: self.canvas.camera.projection_mode = Projection.TOP_DOWN else: self.canvas.camera.projection_mode = Projection.PERSPECTIVE evt.Skip() def _change_dimension(self, evt: DimensionChangeEvent): """Run when the dimension attribute in the canvas is changed. This is run when the user changes the attribute and when it is changed manually in code.""" dimension = evt.dimension index = self._dim_options.FindString(dimension) if not (index == wx.NOT_FOUND or index == self._dim_options.GetSelection()): self._dim_options.SetSelection(index) def _on_camera_move(self, evt): x, y, z = evt.camera_location label = f"{x:.2f}, {y:.2f}, {z:.2f}" old_label = self._location_button.GetLabel() self._location_button.SetLabel(label) if len(label) != len(old_label): self.canvas.Layout() evt.Skip()
class FilePanel(wx.BoxSizer, BaseUI): def __init__(self, canvas: "EditCanvas"): wx.BoxSizer.__init__(self, wx.HORIZONTAL) BaseUI.__init__(self, canvas) self._location_button = wx.Button( canvas, label=", ".join([f"{s:.2f}" for s in self.canvas.camera_location]) ) self._location_button.Bind(wx.EVT_BUTTON, lambda evt: self.canvas.goto()) self.Add(self._location_button, 0, wx.TOP | wx.BOTTOM | wx.RIGHT | wx.CENTER, 5) self._dim_options = SimpleChoiceAny(canvas) self._dim_options.SetItems(self.canvas.world.world_wrapper.dimensions) self._dim_options.SetValue("overworld") self._dim_options.Bind(wx.EVT_CHOICE, self._on_dimension_change) self.Add(self._dim_options, 0, wx.TOP | wx.BOTTOM | wx.RIGHT | wx.CENTER, 5) def create_button(text, operation): button = wx.Button(canvas, label=text) button.Bind(wx.EVT_BUTTON, operation) self.Add(button, 0, wx.TOP | wx.BOTTOM | wx.RIGHT, 5) return button self._undo_button: Optional[wx.Button] = create_button( "Undo", lambda evt: self.canvas.undo() ) self._redo_button: Optional[wx.Button] = create_button( "Redo", lambda evt: self.canvas.redo() ) self._save_button: Optional[wx.Button] = create_button( "Save", lambda evt: self.canvas.save() ) create_button("Close", lambda evt: self.canvas.close()) self._update_buttons() # self.Fit(self) self.Layout() def bind_events(self): self.canvas.Bind(EVT_CAMERA_MOVE, self._on_camera_move) self.canvas.Bind(EVT_UNDO, self._on_update_buttons) self.canvas.Bind(EVT_REDO, self._on_update_buttons) self.canvas.Bind(EVT_SAVE, self._on_update_buttons) self.canvas.Bind(EVT_CREATE_UNDO, self._on_update_buttons) def _on_update_buttons(self, evt): self._update_buttons() evt.Skip() def _update_buttons(self): self._undo_button.SetLabel( f"Undo | {self.canvas.world.history_manager.undo_count}" ) self._redo_button.SetLabel( f"Redo | {self.canvas.world.history_manager.redo_count}" ) self._save_button.SetLabel( f"Save | {self.canvas.world.history_manager.unsaved_changes}" ) def _on_dimension_change(self, evt): """Run when the dimension selection is changed by the user.""" dimension = self._dim_options.GetCurrentObject() if dimension is not None: self.canvas.dimension = dimension evt.Skip() def _change_dimension(self, evt): """Run when the dimension attribute in the canvas is changed. This is run when the user changes the attribute and when it is changed manually in code.""" dimension = evt.dimension index = self._dim_options.FindString(dimension) if not (index == wx.NOT_FOUND or index == self._dim_options.GetSelection()): self._dim_options.SetSelection(index) def _on_camera_move(self, evt): x, y, z = evt.location label = f"{x:.2f}, {y:.2f}, {z:.2f}" old_label = self._location_button.GetLabel() self._location_button.SetLabel(label) if len(label) != len(old_label): self.canvas.Layout()
class BaseSelectOperationUI(wx.BoxSizer, CameraToolUI): OperationGroupName = None def __init__(self, canvas: "EditCanvas"): wx.BoxSizer.__init__(self, wx.VERTICAL) CameraToolUI.__init__(self, canvas) self._selection = StaticSelectionBehaviour(self.canvas) self._active_operation: Optional[OperationUIType] = None horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL) self._operation_choice = SimpleChoiceAny(self.canvas) self._reload_operation = wx.BitmapButton( self.canvas, bitmap=REFRESH_ICON.bitmap(16, 16) ) self._reload_operation.SetToolTip("Reload Operations") horizontal_sizer.Add(self._operation_choice) horizontal_sizer.Add(self._reload_operation) self.Add(horizontal_sizer) assert isinstance( self.OperationGroupName, str ), "OperationGroupName has not been set or is not a string." self._operations = UIOperationManager(self.OperationGroupName) self._operation_choice.SetItems( {op.identifier: op.name for op in self._operations.operations} ) self._operation_choice.Bind(wx.EVT_CHOICE, self._on_operation_change) self._reload_operation.Bind(wx.EVT_BUTTON, self._on_reload_operations) self._operation_sizer = wx.BoxSizer(wx.VERTICAL) self.Add(self._operation_sizer, 1, wx.EXPAND) @property def name(self) -> str: raise NotImplementedError @property def operation(self) -> str: return self._operation_choice.GetCurrentObject() def _unload_active_operation(self): """Unload and destroy the UI for the active operation.""" if self._active_operation is not None: self._active_operation.unload() if isinstance(self._active_operation, wx.Window): self._active_operation.Destroy() elif isinstance(self._active_operation, wx.Sizer): self._operation_sizer.GetItem(self._active_operation).DeleteWindows() self._active_operation = None def _on_operation_change(self, evt): """Run when the operation selection changes.""" self._setup_operation() evt.Skip() def _setup_operation(self): """Create the UI for the new operation.""" operation_path = self._operation_choice.GetCurrentObject() if operation_path: operation = self._operations[operation_path] self._unload_active_operation() self._active_operation = operation( self.canvas, self.canvas, self.canvas.world ) self._operation_sizer.Add( self._active_operation, *self._active_operation.wx_add_options ) self.Layout() def bind_events(self): super().bind_events() self._selection.bind_events() def enable(self): super().enable() self._selection.update_selection() self._setup_operation() def disable(self): super().disable() self._unload_active_operation() def _on_reload_operations(self, evt): """Run when the button is pressed to reload the operations.""" self.reload_operations() def reload_operations(self): """Reload all operations and repopulate the UI.""" # store the id of the old operation operation_id = self.operation # reload the operations self._operations.reload() # repopulate the selection self._operation_choice.SetItems( {op.identifier: op.name for op in self._operations.operations} ) if operation_id: operation_loader = self._operations[operation_id] identifiers = self._operation_choice.values if identifiers: if operation_id in identifiers: self._operation_choice.SetSelection(identifiers.index(operation_id)) else: log.info(f"Operation {operation_id} was not found.") self._operation_choice.SetSelection(0) else: log.error("No operations found. Something has gone wrong.") self._setup_operation() def _on_draw(self, evt): self.canvas.renderer.start_draw() if self.canvas.camera.projection_mode == Projection.PERSPECTIVE: self.canvas.renderer.draw_sky_box() glClear(GL_DEPTH_BUFFER_BIT) self.canvas.renderer.draw_level() self._selection.draw() self.canvas.renderer.end_draw()