123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844 |
- # Flexlay - A Generic 2D Game Editor
- # Copyright (C) 2014 Ingo Ruhnke <grumbel@gmail.com>
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- from typing import cast, Any, Optional, Callable, TYPE_CHECKING
- import logging
- import os
- import subprocess
- import tempfile
- import threading
- from PyQt5.QtGui import QIcon
- from PyQt5.QtWidgets import QMessageBox, QFileDialog
- from flexlay.color import Color
- from flexlay.commands.object_add_command import ObjectAddCommand
- from flexlay.gui.file_dialog import OpenFileDialog, SaveFileDialog
- from flexlay.input_event import InputEvent
- from flexlay.math import Point, Size, Rectf, Pointf, Sizef
- from flexlay.objmap_path_node import ObjMapPathNode
- from flexlay.objmap_rect_object import ObjMapRectObject
- from flexlay.tool_context import ToolContext
- from flexlay.tools.objmap_select_tool import ObjMapSelectTool
- from flexlay.tools.tile_brush_create_tool import TileBrushCreateTool
- from flexlay.tools.tile_fill_tool import TileFillTool
- from flexlay.tools.tile_paint_tool import TilePaintTool
- from flexlay.tools.tile_replace_tool import TileReplaceTool
- from flexlay.tools.tilemap_select_tool import TileMapSelectTool
- from flexlay.tools.workspace_move_tool import WorkspaceMoveTool
- from flexlay.tools.zoom_out_tool import ZoomOutTool
- from flexlay.tools.zoom_tool import ZoomTool
- from flexlay.util.config import Config
- from flexlay.workspace import Workspace
- from flexlay.gui.layer_selector import LayerSelector
- from flexlay.object_brush import ObjectBrush
- from flexlay.objmap_tilemap_object import ObjMapTilemapObject
- from supertux.addon import Addon
- from supertux.addon_dialog import SaveAddonDialog
- from supertux.button_panel import SuperTuxButtonPanel
- from supertux.gameobj_factor import supertux_gameobj_factory
- from supertux.gameobjs import PathNode
- from supertux.level import Level
- from supertux.level_file_dialog import OpenLevelFileDialog, SaveLevelFileDialog
- from supertux.menubar import SuperTuxMenuBar
- from supertux.new_addon import NewAddonWizard
- from supertux.new_level import NewLevelWizard
- from supertux.sector import Sector
- from supertux.supertux_arguments import SuperTuxArguments
- from supertux.tilemap import SuperTuxTileMap
- from supertux.tileset import SuperTuxTileset
- from supertux.toolbox import SuperTuxToolbox
- if TYPE_CHECKING:
- from flexlay.flexlay import Flexlay
- from flexlay.gui_manager import GUIManager
- class SuperTuxGUI:
- current: Optional['SuperTuxGUI'] = None
- def __init__(self, flexlay: 'Flexlay') -> None:
- SuperTuxGUI.current = self
- self.use_worldmap: bool = False
- self.tool_context = ToolContext()
- self.level: Optional[Level] = None
- self.sector: Optional[Sector] = None
- self.gui: GUIManager = flexlay.create_gui_manager("SuperTux Editor")
- self.gui.window.setWindowIcon(QIcon("data/images/supertux/supertux-editor.png"))
- self.gui.window.set_on_close(self.on_window_close)
- self.button_panel = SuperTuxButtonPanel(self.gui, self)
- self.toolbox = SuperTuxToolbox(self.gui, self)
- self.editor_map = self.gui.create_editor_map_component()
- self.statusbar = self.gui.create_statusbar()
- self.workspace = self.editor_map.get_workspace()
- # Tools
- self.workspace.set_tool(InputEvent.MOUSE_MIDDLE, WorkspaceMoveTool())
- self.minimap = self.gui.create_minimap(self.editor_map)
- self.objectselector = self.gui.create_object_selector(42, 42)
- self.properties_widget = self.gui.create_properties_view()
- self.editor_map.sig_drop.connect(self.on_object_drop)
- for object_brush in supertux_gameobj_factory.create_object_brushes():
- self.objectselector.add_brush(object_brush)
- self.tileselector = self.gui.create_tile_selector()
- assert SuperTuxTileset.current is not None
- self.gui_set_tileset(SuperTuxTileset.current)
- self.layer_selector: LayerSelector = self.gui.create_layer_selector(self.generate_tilemap_obj)
- # self.worldmapobjectselector = self.gui.create_object_selector(42, 42)
- # if False:
- # self.worldmapobjectselector.sig_drop.connect(self.on_worldmap_object_drop)
- # for obj in worldmap_objects:
- # self.objectselector.add_brush(ObjectBrush(Sprite.from_file(os.path.join(Config.current.datadir, obj[1])),
- # obj[0]))
- # Loading Dialogs
- assert Config.current is not None
- self.load_dialog = OpenLevelFileDialog("Load SuperTux Level")
- self.load_dialog.set_directory(Config.current.datadir, "levels")
- self.save_dialog = SaveLevelFileDialog("Save SuperTux Level As...")
- self.save_dialog.set_directory(Config.current.datadir, "levels")
- self.addon_save_dialog = SaveAddonDialog("Save SuperTux Add-on As...")
- self.addon_save_dialog.set_directory(Config.current.datadir, "addons")
- self.register_keyboard_shortcuts()
- # Popup menu
- # objmap_select_tool.sig_on_right_click().connect(proc{ | x, y |
- # print("Launching Menu at %s, %s" % (x, y))
- # menu=Menu(Point(x, y))
- # menu.add_item(mysprite, "Delete Object(s)", proc{
- # print("Trying to delete
- # {self.workspace.get_map().metadata}
- # {self.workspace.get_map().metadata.objects}")
- # cmd=ObjectDeleteCommand(self.workspace.get_map().metadata.objects)
- # for i in objmap_select_tool.get_selection():
- # cmd.add_object(i)
- # self.workspace.get_map().execute(cmd)
- # objmap_select_tool.clear_selection()
- # })
- # menu.add_item(mysprite, "Edit Properties", proc{
- # for i in objmap_select_tool.get_selection():
- # i.metadata.property_dialog()
- # }
- # })
- # menu.run()
- # })
- # setting initial state
- level = Level.from_size(100, 50)
- self.set_level(level, "main")
- self.set_tilemap_paint_tool()
- # Must be after LayerSelector initialised
- self.menubar = SuperTuxMenuBar(self.gui, self)
- # Command line arguments, when game is run
- self.arguments = SuperTuxArguments()
- def register_keyboard_shortcuts(self) -> None:
- self.editor_map.sig_on_key("f1").connect(lambda x, y: self.gui_toggle_minimap())
- self.editor_map.sig_on_key("m").connect(lambda x, y: self.gui_toggle_minimap())
- self.editor_map.sig_on_key("g").connect(lambda x, y: self.gui_toggle_grid())
- self.editor_map.sig_on_key("+").connect(lambda x, y: self.editor_map.zoom_in(Pointf(x, y)))
- self.editor_map.sig_on_key("-").connect(lambda x, y: self.editor_map.zoom_out(Pointf(x, y)))
- self.editor_map.sig_on_key("Enter").connect(lambda x, y: self.gui_set_zoom(1.0))
- self.editor_map.sig_on_key("i").connect(lambda x, y: self.insert_path_node(x, y))
- self.editor_map.sig_on_key("c").connect(lambda x, y: self.connect_path_nodes())
- self.editor_map.sig_on_key("7").connect(
- cast(Callable[[int, int], None],
- lambda x, y: self.workspace.get_map().metadata.parent.activate_sector("main",
- self.workspace)))
- self.editor_map.sig_on_key("8").connect(
- cast(Callable[[int, int], None],
- lambda x, y: self.workspace.get_map().metadata.parent.activate_sector("another_world",
- self.workspace)))
- self.editor_map.sig_on_key("p").connect(lambda x, y: self.gui_show_object_properties())
- def on_a_key(x: int, y: int) -> None:
- pos = self.editor_map.screen2world(Pointf(x, y))
- rectobj = ObjMapRectObject(Rectf.from_ps(pos,
- Sizef(128, 64)),
- Color(0, 255, 255, 155),
- None)
- self.workspace.get_map().metadata.objects.add_object(rectobj)
- self.editor_map.sig_on_key("a").connect(on_a_key)
- def on_window_close(self, *args: Any) -> bool:
- """Called when window x button is clicked
- Ask whether to save, continue, or just quit.
- :return: boolean whether to close or not. If not boolean, will close.
- """
- assert Workspace.current is not None
- editor_map = Workspace.current.get_map()
- # If the most recent save was the same as the save_pointer index,
- # we can safely quit
- if editor_map.save_pointer == len(editor_map.undo_stack):
- return True
- else:
- choice = QMessageBox.warning(self.gui.window, "Unsaved Changes to Level",
- "The level has been changed since "
- "the last save.",
- QMessageBox.Save | QMessageBox.Cancel | QMessageBox.Discard,
- QMessageBox.Save)
- if choice == QMessageBox.Save:
- dialog_is_cancelled = False
- def after_save(i: int) -> None:
- dialog_is_cancelled = (i == 0) # noqa: F841
- self.save_dialog.file_dialog.finished.connect(after_save)
- self.gui_level_save()
- # If saved, show confirmation dialog to reassure user.
- if not dialog_is_cancelled:
- QMessageBox.information(self.gui.window, "Saved Successfully", "Editor will now quit")
- # If dialog is cancelled, don't quit, as that would lose changes
- return not dialog_is_cancelled
- elif choice == QMessageBox.Cancel:
- return False
- elif choice == QMessageBox.Discard:
- return True
- else:
- assert False, f"unhandled QMessageBox choice value: {choice}"
- def on_object_drop(self, brush: ObjectBrush, pos: Pointf) -> None:
- obj = supertux_gameobj_factory.create_gameobj_at(brush.metadata, pos)
- if obj is None:
- logging.error("Unknown object type dropped: %r" % brush.metadata)
- else:
- cmd = ObjectAddCommand(self.workspace.get_map().metadata.object_layer)
- assert obj.objmap_object is not None
- cmd.add_object(obj.objmap_object)
- self.workspace.get_map().execute(cmd)
- def run(self) -> None:
- self.gui.run()
- def show_objects(self) -> None:
- if False: # GRUMBEL
- self.tileselector.show(False) # type: ignore[unreachable]
- if self.use_worldmap:
- self.objectselector.show(False)
- else:
- self.objectselector.show(True)
- def show_tiles(self) -> None:
- if False: # GRUMBEL
- self.tileselector.show(True) # type: ignore[unreachable]
- self.objectselector.show(False)
- def gui_toggle_minimap(self) -> None:
- if self.minimap.get_widget().isVisible():
- self.minimap.get_widget().hide()
- self.button_panel.minimap_icon.set_up()
- else:
- self.minimap.get_widget().show()
- self.button_panel.minimap_icon.set_down()
- def gui_toggle_grid(self) -> None:
- self.workspace.get_map().draw_grid = not self.workspace.get_map().draw_grid
- if not self.workspace.get_map().draw_grid:
- self.button_panel.grid_icon.set_down()
- else:
- self.button_panel.grid_icon.set_up()
- self.editor_map.editormap_widget.repaint()
- def gui_set_tileset(self, tileset: SuperTuxTileset) -> None:
- self.tileselector.set_tileset(tileset)
- self.tileselector.clear_tilegroups()
- self.tileselector.add_tilegroup("All Tiles", tileset.get_tiles())
- for tilegroup in tileset.tilegroups:
- self.tileselector.add_tilegroup(tilegroup.name, tilegroup.tiles)
- if self.sector is not None:
- for tilemap_layer in [tilemap.tilemap_layer for tilemap in self.sector.tilemaps]:
- assert tilemap_layer is not None
- tilemap_layer.tileset = tileset
- self.editor_map.editormap_widget.repaint()
- if self.level is not None:
- self.level.tileset_path = tileset.filename
- def set_tileset(self, filename: str) -> None:
- """Set tileset from (.strf) filename"""
- if not filename:
- QMessageBox.warning(None, "No Tileset Selected", "No tileset was selected, aborting...")
- tileset = SuperTuxTileset(32)
- tileset.load(filename)
- self.gui_set_tileset(tileset)
- # def gui_change_tileset(self):
- # tileset_dialog = OpenFileDialog("Select Tileset To Open", ("SuperTux Tilesets (*.strf)", "All Files (*)"))
- # tileset_dialog.set_directory(Config.current.datadir, "images")
- # tileset_dialog.run(self.set_tileset)
- def gui_change_tileset(self) -> bool:
- assert Config.current is not None
- filename, _filter = QFileDialog.getOpenFileName(None, "Select Tileset To Open", Config.current.datadir)
- if not filename:
- QMessageBox.warning(None, "No Tileset Selected", "No tileset was selected, aborting...")
- return False
- tileset = SuperTuxTileset(32)
- tileset.load(filename)
- self.gui_set_tileset(tileset)
- return True
- def gui_run_level(self) -> None:
- logging.info("Run this level...")
- level = self.workspace.get_map().metadata.get_level()
- if level.is_worldmap:
- suffix = ".stwm"
- else:
- suffix = ".stl"
- tmpfile = tempfile.mkstemp(suffix=suffix, prefix="flexlay-")
- self.save_level(tmpfile[1], False, True)
- self.arguments.run_level = tmpfile[1]
- # for obj in self.sector.object_layer.get_objects():
- # if isinstance(obj.metadata, Tux):
- # self.arguments.spawn_at = obj.pos
- try:
- thread = threading.Thread(target=self.gui_run_level_thread,
- args=(self.gui_run_level_cleanup,
- self.arguments.get_popen_arg(),
- tmpfile))
- thread.start()
- except FileNotFoundError:
- QMessageBox.warning(None, "No Supertux Binary Found",
- "Press OK to select your Supertux binary")
- assert Config.current is not None
- Config.current.binary = OpenFileDialog("Open Supertux Binary").filename
- if not Config.current.binary:
- raise RuntimeError("binary path missing, use --binary BIN")
- # self.arguments.spawn_at = None
- def gui_run_level_thread(self, postexit_fn: Callable[[str], None], popen_args: list[str], tmpfile: str) -> None:
- subproc = subprocess.Popen(popen_args)
- subproc.wait()
- postexit_fn(tmpfile)
- def gui_run_level_cleanup(self, tmpfile: tuple[int, str]) -> None:
- # Safely get rid of temporary file
- os.close(tmpfile[0])
- os.remove(tmpfile[1])
- def gui_record_level(self) -> None:
- dialog = SaveFileDialog("Choose Record Target File")
- dialog.run(lambda _: None)
- self.arguments.record_demo_file = dialog.get_filename()
- self.gui_run_level()
- self.arguments.record_demo_file = None
- def gui_play_demo(self) -> None:
- QMessageBox.information(None,
- "Select a level file",
- "You must now select a level file - the level of the demo")
- # level = OpenLevelFileDialog("Select The Level")
- QMessageBox.information(None,
- "Select a demo file",
- "You must now select a demo file to play")
- demo = OpenFileDialog("Select The Demo File To Play").filename
- self.arguments.play_demo_file = demo
- subprocess.Popen(self.arguments.get_popen_arg())
- self.arguments.play_demo_file = None
- def gui_watch_example(self) -> None:
- assert Config.current is not None
- level = os.path.join(Config.current.datadir, "levels", "world1", "01 - Welcome to Antarctica.stl")
- demo = os.path.join("data", "supertux", "demos", "karkus476_plays_level_1")
- subprocess.Popen([Config.current.binary, level, "--play-demo", demo])
- def gui_resize_sector(self) -> None:
- sector = self.workspace.get_map().metadata
- assert isinstance(sector, Sector)
- dialog = self.gui.create_generic_dialog("Resize Sector")
- dialog.add_int("Width: ", sector.width)
- dialog.add_int("Height: ", sector.height)
- dialog.add_int("X: ", 0)
- dialog.add_int("Y: ", 0)
- def on_callback(w: int, h: int, x: int, y: int) -> None:
- logging.info("Resize Callback")
- sector.resize(Size(w, h), Point(x, y))
- dialog.add_callback(on_callback)
- def gui_smooth_level_struct(self) -> None:
- logging.info("Smoothing level structure")
- tilemap = self.tool_context.tilemap_layer
- assert tilemap is not None
- data = tilemap.get_data()
- # width = tilemap.width
- #
- # GRUMBEL
- # def get(x, y):
- # return data[y * width + x]
- #
- # def set(x, y, val):
- # data[y * width + x] = val
- #
- # def smooth(x, y):
- # pass # GRUMBEL
- # for ary in itile_conditions:
- # if ((solid_itiles.index(get[x - 1, y - 1]) ? 1: 0) == ary[0]
- # and (solid_itiles.index(get[x, y - 1]) ? 1: 0) == ary[1]
- # and (solid_itiles.index(get[x + 1, y - 1]) ? 1: 0) == ary[2]
- # and (solid_itiles.index(get[x - 1, y]) ? 1: 0) == ary[3]
- # and (solid_itiles.index(get[x, y]) ? 1: 0) == ary[4]
- # and (solid_itiles.index(get[x + 1, y]) ? 1: 0) == ary[5]
- # and (solid_itiles.index(get[x - 1, y + 1]) ? 1: 0) == ary[6]
- # and (solid_itiles.index(get[x, y + 1]) ? 1: 0) == ary[7]
- # and (solid_itiles.index(get[x + 1, y + 1]) ? 1: 0) == ary[8]):
- # set[x, y, ary[9]]
- #
- # rect = self.tilemap_select_tool.get_selection_rect()
- #
- # start_x = rect.left
- # end_x = rect.right
- # start_y = rect.top
- # end_y = rect.bottom
- #
- # GRUMBEL
- # for y in range(start_y, end_y):
- # for x in range(start_x, end_x):
- # smooth(x, y)
- tilemap.set_data(data)
- def gui_resize_sector_to_selection(self) -> None:
- if self.tool_context.tile_selection is not None:
- level = self.workspace.get_map().metadata
- rect = self.tool_context.tile_selection.get_rect()
- if (rect.width > 2 and rect.height > 2):
- level.resize(rect.size, Point(-rect.left, -rect.top))
- def gui_edit_level(self) -> None:
- level = self.workspace.get_map().metadata.get_level()
- dialog = self.gui.create_generic_dialog("Edit Level")
- dialog.add_string("Name:", level.name)
- dialog.add_string("Author:", level.author)
- dialog.add_string("Contact:", level.contact)
- dialog.add_int("Target Time:", level.target_time)
- def on_callback(name: str, author: str, contact: str, target_time: float) -> None:
- level.name = name
- level.author = author
- level.contact = contact
- level.target_time = target_time
- dialog.add_callback(on_callback)
- def gui_edit_sector(self) -> None:
- level = self.workspace.get_map().metadata.get_level()
- dialog = self.gui.create_generic_dialog("Edit Sector")
- assert Config.current is not None
- dialog.add_string("Name: ", level.current_sector.name)
- dialog.add_file("Music: ",
- level.current_sector.music,
- ret_rel_to=Config.current.datadir,
- show_rel_to=os.path.join(Config.current.datadir, "music"),
- open_in=os.path.join(Config.current.datadir, "music"))
- dialog.add_float("Gravity: ", level.current_sector.gravity)
- def on_callback(*args: Any) -> None:
- level.current_sector.name = args[0]
- level.current_sector.music = args[1]
- level.current_sector.gravity = args[2]
- dialog.add_callback(on_callback)
- def gui_zoom_in(self) -> None:
- factor = 2.0
- gc = self.editor_map.get_gc_state()
- zoom = gc.get_zoom()
- self.gui_set_zoom(zoom / pow(1.25, -factor))
- def gui_zoom_out(self) -> None:
- factor = 2.0
- gc = self.editor_map.get_gc_state()
- zoom = gc.get_zoom()
- self.gui_set_zoom(zoom * pow(1.25, -factor))
- def gui_zoom_fit(self) -> None:
- rect = self.workspace.get_map().get_bounding_rect()
- zoom = min(self.editor_map.editormap_widget.width() / rect.width,
- self.editor_map.editormap_widget.height() / rect.height)
- self.gui_set_zoom(zoom, Pointf(rect.width / 2, rect.height / 2))
- def gui_set_zoom(self, zoom: float, pos: Optional[Pointf] = None) -> None:
- gc = self.editor_map.get_gc_state()
- pos = pos or gc.get_pos()
- gc.set_zoom(zoom)
- gc.set_pos(pos)
- self.editor_map.editormap_widget.repaint()
- def gui_remove_sector(self) -> None:
- sector = self.workspace.get_map().metadata
- sector.get_level().remove_sector(sector.name)
- def gui_add_sector(self) -> None:
- level = self.workspace.get_map().metadata.get_level()
- name = "sector"
- uniq_name = name
- i = 2
- while level.get_sectors().index(uniq_name):
- uniq_name = name + "<%d>" % i
- i += 1
- sector = Sector(level)
- sector.new_from_size(uniq_name, 30, 20)
- level.add_sector(sector)
- self.set_level(level, uniq_name)
- self.gui_edit_sector()
- def gui_show_object_properties(self) -> None:
- if self.tool_context.object_selection:
- selection = self.tool_context.object_selection
- if len(selection) > 1:
- logging.warning("Selection too large")
- elif len(selection) == 1:
- obj = selection[0].metadata
- obj.property_dialog(self.gui.window)
- else:
- logging.warning("Selection is empty")
- def undo(self) -> None:
- self.workspace.get_map().undo()
- def redo(self) -> None:
- self.workspace.get_map().redo()
- def on_map_change(self) -> None:
- self.editor_map.editormap_widget.repaint()
- if self.workspace.get_map().undo_stack_size() > 0:
- self.button_panel.undo_icon.enable()
- else:
- self.button_panel.undo_icon.disable()
- if self.workspace.get_map().redo_stack_size() > 0:
- self.button_panel.redo_icon.enable()
- else:
- self.button_panel.redo_icon.disable()
- def gui_level_save_as(self) -> None:
- path = self.save_dialog.get_filename()
- if os.path.isdir(path):
- self.save_dialog.set_directory(path)
- else:
- self.save_dialog.set_directory(os.path.dirname(path) + "/")
- self.save_dialog.run(self.save_level)
- def gui_level_save(self) -> None:
- if self.use_worldmap:
- filename = self.workspace.get_map().metadata.filename
- else:
- filename = self.workspace.get_map().metadata.parent.filename
- logging.info("Save Filename: " + filename)
- if filename:
- self.save_level(filename)
- else:
- filename = self.save_dialog.get_filename()
- if filename[-1] == "/"[0]:
- self.save_dialog.set_directory(filename)
- else:
- self.save_dialog.set_directory(os.path.dirname(filename) + "/")
- self.save_dialog.run(self.save_level)
- def gui_level_new(self) -> None:
- if False:
- dialog = NewLevelWizard(self.gui.window) # type: ignore[unreachable]
- dialog.exec_()
- if dialog.level:
- self.set_level(dialog.level, "main")
- if dialog.level is not None:
- def save_path_chosen(save_path):
- dialog.level.save(save_path)
- self.load_level(save_path)
- self.save_dialog.run(save_path_chosen)
- else:
- level = Level.from_size(100, 25)
- self.set_level(level, "main")
- def gui_addon_new(self) -> None:
- dialog = NewAddonWizard(self.gui.window)
- dialog.exec_()
- if dialog.addon is not None:
- def save_path_chosen(save_path: str) -> None:
- assert dialog.addon is not None
- dialog.addon.save(save_path)
- self.load_addon(dialog.addon, save_path)
- self.addon_save_dialog.run(save_path_chosen)
- pass
- def gui_level_load(self) -> None:
- self.load_dialog.run(self.load_level)
- def insert_path_node(self, x: int, y: int) -> None:
- logging.info("Insert path Node")
- m = self.workspace.get_map().metadata
- pathnode = ObjMapPathNode(self.editor_map.screen2world(Pointf(x, y)),
- "PathNode")
- pathnode.metadata = PathNode(pathnode)
- m.objects.add_object(pathnode)
- def connect_path_nodes(self) -> None:
- logging.info("Connecting path nodes")
- pathnodes: list[ObjMapPathNode] = []
- for i in self.tool_context.object_selection:
- obj = i.metadata
- if isinstance(obj, PathNode):
- pathnodes.append(obj.node)
- last: Optional[ObjMapPathNode] = None
- for i in pathnodes:
- if last is not None:
- last.connect(i)
- last = i
- def gui_set_datadir(self) -> None:
- assert Config.current is not None
- if os.path.isdir(Config.current.datadir):
- dialog = self.gui.create_generic_dialog("Specify the SuperTux data directory and restart")
- dialog.add_label("You need to specify the datadir where SuperTux is located")
- dialog.add_string("SuperTux datadir:", Config.current.datadir)
- def on_callback(datadir: str) -> None:
- assert Config.current is not None
- Config.current.datadir = datadir
- dialog.add_callback(on_callback)
- def load_level(self, filename: str, set_title: bool = True) -> None:
- logging.info("Loading: " + filename)
- # Clear object selection, it's a new level!
- self.tool_context.object_selection.clear()
- assert self.gui.properties_widget is not None
- self.gui.properties_widget.clear_properties()
- # Set title if desired
- if set_title:
- self.gui.window.setWindowTitle("SuperTux Editor: [" + filename + "]")
- # if filename[-5:] == ".stwm":
- # QMessageBox.warning(None, "Opening Worldmap File",
- # "[WARNING] Opening supertux worldmap file:\n'"+filename+"'\n" +
- # "Worldmaps usually use different tilesets to levels.\n"+
- # "Please select a different tileset to use (look for .strf files).")
- # if not self.gui_change_tileset():
- # return
- # print("Loading worldmap")
- level = Level.from_file(filename)
- assert SuperTuxTileset.current is not None
- assert Config.current is not None
- if level.tileset_path != SuperTuxTileset.current.filename:
- tileset = SuperTuxTileset(32)
- tileset.load(os.path.join(Config.current.datadir, level.tileset_path))
- # tileset.load(level.tileset_path)
- self.gui_set_tileset(tileset)
- # Tileset has changed, reload level:
- level = Level.from_file(filename)
- self.set_level(level, "main")
- Config.current.add_recent_file(filename)
- self.menubar.update_recent_files()
- self.minimap.update_minimap()
- # TODO: We don't yet support multiple sectors, so we set the first sector's name.
- self.editor_map.set_sector_tab_label(0, level.sectors[0].name)
- def load_worldmap(self, filename: str) -> None:
- print("Loading Worldmap: {}".format(filename))
- # worldmap = WorldMap(filename)
- # worldmap.activate(self.workspace)
- def save_level(self, filename: str, set_title: bool = True, is_tmp: bool = False) -> None:
- if set_title:
- self.gui.window.setWindowTitle("SuperTux Editor: [" + filename + "]")
- assert Workspace.current is not None
- editor_map = Workspace.current.get_map()
- editor_map.save_pointer = len(editor_map.undo_stack)
- level = self.workspace.get_map().metadata.parent
- assert Config.current is not None
- Config.current.add_recent_file(filename)
- self.menubar.update_recent_files()
- # Do backup save if the file exists and is going to be saved permanently.
- if os.path.isfile(filename) and not is_tmp:
- os.rename(filename, filename + "~")
- level.save(filename)
- level.filename = filename
- def load_addon(self, addon: Addon, dirname: str) -> None:
- print("Add-on dirname is: " + dirname)
- self.gui.project_widget.set_addon(addon)
- self.gui.project_widget.set_project_directory(dirname)
- def load_addon_zip(self, filename: str) -> None:
- print("Add-on zip path is: {}".format(filename))
- def raise_selection(self) -> None:
- for obj in self.tool_context.object_selection:
- self.workspace.get_map().metadata.objects.raise_object(obj)
- self.editor_map.editormap_widget.repaint()
- def lower_selection(self) -> None:
- for obj in self.tool_context.object_selection:
- self.workspace.get_map().metadata.objects.lower_object(obj)
- self.editor_map.editormap_widget.repaint()
- def raise_selection_to_top(self) -> None:
- selection = self.tool_context.object_selection
- self.workspace.get_map().metadata.objects.raise_objects_to_top(selection)
- self.editor_map.editormap_widget.repaint()
- def lower_selection_to_bottom(self) -> None:
- selection = self.tool_context.object_selection
- self.workspace.get_map().metadata.objects.lower_objects_to_bottom(selection)
- self.editor_map.editormap_widget.repaint()
- def set_tilemap_paint_tool(self) -> None:
- self.workspace.set_tool(InputEvent.MOUSE_LEFT, TilePaintTool())
- self.workspace.set_tool(InputEvent.MOUSE_RIGHT, TileBrushCreateTool())
- self.toolbox.set_down(self.toolbox.paint_icon)
- def set_tilemap_replace_tool(self) -> None:
- self.workspace.set_tool(InputEvent.MOUSE_LEFT, TileReplaceTool())
- self.workspace.set_tool(InputEvent.MOUSE_RIGHT, TileBrushCreateTool())
- self.toolbox.set_down(self.toolbox.replace_icon)
- def set_tilemap_fill_tool(self) -> None:
- self.workspace.set_tool(InputEvent.MOUSE_LEFT, TileFillTool())
- self.workspace.set_tool(InputEvent.MOUSE_RIGHT, TileBrushCreateTool())
- self.toolbox.set_down(self.toolbox.fill_icon)
- def set_tilemap_select_tool(self) -> None:
- self.workspace.set_tool(InputEvent.MOUSE_LEFT, TileMapSelectTool())
- self.workspace.set_tool(InputEvent.MOUSE_RIGHT, None)
- self.toolbox.set_down(self.toolbox.select_icon)
- def set_zoom_tool(self) -> None:
- self.workspace.set_tool(InputEvent.MOUSE_LEFT, ZoomTool())
- self.workspace.set_tool(InputEvent.MOUSE_RIGHT, ZoomOutTool())
- self.toolbox.set_down(self.toolbox.zoom_icon)
- def set_objmap_select_tool(self) -> None:
- self.workspace.set_tool(InputEvent.MOUSE_LEFT, ObjMapSelectTool(self.gui))
- self.workspace.set_tool(InputEvent.MOUSE_RIGHT, ObjMapSelectTool(self.gui))
- self.toolbox.set_down(self.toolbox.object_icon)
- def set_level(self, level: Level, sectorname: str) -> None:
- self.level = level
- for sec in self.level.sectors:
- if sec.name == sectorname:
- self.set_sector(sec)
- break
- def set_sector(self, sector: Sector) -> None:
- assert sector is not None
- self.sector = sector
- self.workspace.current_sector = sector
- assert self.sector.editormap is not None
- self.workspace.set_map(self.sector.editormap)
- self.layer_selector.set_map(self.sector.editormap)
- # TODO: We don't yet support multiple sectors, so we set the first sector's name.
- self.editor_map.set_sector_tab_label(0, sector.name)
- assert ToolContext.current is not None
- ToolContext.current.tilemap_layer = self.sector.get_some_solid_tilemap().tilemap_layer
- ToolContext.current.object_layer = self.sector.object_layer
- assert SuperTuxGUI.current is not None
- assert self.sector.editormap is not None
- self.sector.editormap.sig_change.connect(SuperTuxGUI.current.on_map_change)
- def generate_tilemap_obj(self) -> ObjMapTilemapObject:
- """Generate a basic ObjMapTilemapObject with basic parameters
- May later open a dialog.
- :return: ObjMapTilemapObject
- """
- assert self.sector is not None
- tilemap = SuperTuxTileMap.from_size(self.sector.width,
- self.sector.height,
- "<no name>",
- 0, True)
- assert tilemap.tilemap_layer is not None
- tilemap_object = ObjMapTilemapObject(tilemap.tilemap_layer, tilemap)
- return tilemap_object
- def camera_properties(self) -> None:
- assert self.sector is not None
- assert self.sector.camera is not None
- self.sector.camera.property_dialog(self.gui.window)
- # EOF #
|