Jelajahi Sumber

tools: add texture settings editor

Fixes: #175
Daniele Bartolini 1 tahun lalu
induk
melakukan
4cfcb213da

+ 24 - 0
tools/level_editor/level_editor.vala

@@ -516,6 +516,7 @@ public class LevelEditorApplication : Gtk.Application
 		{ "create-script",    on_create_script,    "(ssb)", null },
 		{ "create-unit",      on_create_unit,      "(ss)",  null },
 		{ "open-containing",  on_open_containing,  "s",     null },
+		{ "texture-settings", on_texture_settings, "s",     null }
 	};
 
 	private const GLib.ActionEntry[] action_entries_package =
@@ -593,6 +594,7 @@ public class LevelEditorApplication : Gtk.Application
 	private PropertiesView _properties_view;
 	private PreferencesDialog _preferences_dialog;
 	private DeployDialog _deploy_dialog;
+	private TextureSettingsDialog _texture_settings_dialog;
 	private ResourceChooser _resource_chooser;
 	private Gtk.Popover _resource_popover;
 	private Gtk.Overlay _editor_view_overlay;
@@ -2182,6 +2184,25 @@ public class LevelEditorApplication : Gtk.Application
 		_deploy_dialog.present();
 	}
 
+	private void on_texture_settings(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		string texture_name = param.get_string();
+
+		if (_texture_settings_dialog == null) {
+			_texture_settings_dialog = new TextureSettingsDialog(_project, _project_store, _database);
+			_texture_settings_dialog.set_transient_for(this.active_window);
+			_texture_settings_dialog.set_position(Gtk.WindowPosition.CENTER_ON_PARENT);
+			_texture_settings_dialog.delete_event.connect(_texture_settings_dialog.hide_on_delete);
+			_texture_settings_dialog.texture_saved.connect(() => {
+						_data_compiler.compile.begin(_project.data_dir(), _project.platform());
+					});
+		}
+
+		_texture_settings_dialog.set_texture(texture_name);
+		_texture_settings_dialog.show_all();
+		_texture_settings_dialog.present();
+	}
+
 	private int run_level_changed_dialog(Gtk.Window? parent)
 	{
 		Gtk.MessageDialog md = new Gtk.MessageDialog(parent
@@ -2270,6 +2291,9 @@ public class LevelEditorApplication : Gtk.Application
 		if (resource_type == "level") {
 			activate_action("open-level", resource_name);
 			return;
+		} else if (resource_type == "texture") {
+			activate_action("texture-settings", resource_name);
+			return;
 		}
 
 		GLib.AppInfo? app = null;

+ 361 - 0
tools/level_editor/texture_settings_dialog.vala

@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2012-2024 Daniele Bartolini et al.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+namespace Crown
+{
+public enum TextureFormat
+{
+	BC1,
+	BC2,
+	BC3,
+	BC4,
+	BC5,
+	PTC14,
+	RGB8,
+	RGBA8,
+
+	COUNT
+}
+
+const string texture_formats[] =
+{
+	"BC1",
+	"BC2",
+	"BC3",
+	"BC4",
+	"BC5",
+	"PTC14",
+	"RGB8",
+	"RGBA8"
+};
+
+public class TextureSettingsDialog : Gtk.Dialog
+{
+	public Project _project;
+	public Database _database;
+	public Database _texture_database;
+	public Guid _texture_id;
+	public ProjectStore _store;
+	public PropertyGridSet _texture_set;
+	public Gtk.ListStore _platforms_store;
+	public Gtk.TreeView _platforms;
+	public Gtk.Stack _stack;
+	public bool _never_opened_before;
+	public string _texture_path;
+	// Input page.
+	public ResourceChooserButton _texture_name;
+	public EntryText _source;
+	// Output page.
+	public ComboBoxMap _format;
+	public CheckBox _generate_mips;
+	public EntryDouble _mip_skip_smallest;
+	public CheckBox _normal_map;
+	public Gtk.Box _box;
+
+	public signal void texture_saved();
+
+	private void text_func(Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
+	{
+		Value? platform;
+		model.get_value(iter, 0, out platform);
+
+		cell.set_property("text", ((TargetPlatform)platform).to_label());
+	}
+
+	public TextureSettingsDialog(Project project, ProjectStore store, Database database)
+	{
+		_project = project;
+		_database = database;
+		_texture_database = new Database(project);
+		_texture_id = GUID_ZERO;
+		_store = store;
+
+		_platforms_store = new Gtk.ListStore(1
+			, typeof(TargetPlatform) // platform name
+			);
+		for (int p = 0; p < TargetPlatform.COUNT; ++p) {
+			Gtk.TreeIter iter;
+			_platforms_store.insert_with_values(out iter, -1, 0, (TargetPlatform)p, -1);
+		}
+
+		Gtk.CellRendererText text_renderer = new Gtk.CellRendererText();
+		Gtk.TreeViewColumn column = new Gtk.TreeViewColumn.with_attributes("Target Platform", text_renderer, null);
+		column.set_cell_data_func(text_renderer, text_func);
+
+		_platforms = new Gtk.TreeView.with_model(_platforms_store);
+		_platforms.append_column(column);
+		_platforms.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE);
+		_platforms.get_selection().changed.connect(on_platforms_selection_changed);
+
+		this.title = "Texture Settings";
+		this.border_width = 0;
+		this.set_icon_name(CROWN_ICON_NAME);
+
+		_texture_set = new PropertyGridSet();
+		_texture_set.border_width = 12;
+
+		_texture_path = "";
+
+		// Input grid.
+		_texture_name = new ResourceChooserButton(_store, "texture");
+		_texture_name.value_changed.connect(on_texture_resource_value_changed);
+
+		_source = new EntryText();
+		_source.sensitive = false;
+
+		PropertyGrid cv;
+		cv = new PropertyGrid();
+		cv.column_homogeneous = true;
+		cv.add_row("Name", _texture_name);
+		_texture_set.add_property_grid(cv, "Texture");
+
+		cv = new PropertyGrid();
+		cv.column_homogeneous = true;
+		cv.add_row("Source", _source);
+		_texture_set.add_property_grid(cv, "Input");
+
+		// Output grid.
+		_format = new ComboBoxMap(TextureFormat.BC1
+			, texture_formats
+			, texture_formats
+			);
+		_format.value_changed.connect(on_format_value_changed);
+
+		_generate_mips = new CheckBox();
+		_generate_mips.value = true;
+		_generate_mips.value_changed.connect(on_generate_mips_value_changed);
+
+		_mip_skip_smallest = new EntryDouble(0, 0, 32);
+		_mip_skip_smallest.value_changed.connect(on_mip_skip_smallest_value_changed);
+
+		_normal_map = new CheckBox();
+		_normal_map.value = false;
+		_normal_map.value_changed.connect(on_normal_map_value_changed);
+
+		cv = new PropertyGrid();
+		cv.column_homogeneous = true;
+		cv.add_row("Format", _format);
+		cv.add_row("Generate Mips", _generate_mips);
+		cv.add_row("Skip Smallest Mips", _mip_skip_smallest);
+		cv.add_row("Normal Map", _normal_map);
+		_texture_set.add_property_grid(cv, "Output");
+
+		_stack = new Gtk.Stack();
+		_stack.add_named(new Gtk.Label("Select one or more platforms to change its settings"), "none-selected");
+		_stack.add_named(_texture_set, "some-selected");
+
+		_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
+		_box.pack_start(_platforms, false, true, 0);
+		_box.pack_start(_stack, false, true, 0);
+		_box.vexpand = true;
+
+		this.get_content_area().border_width = 0;
+		this.get_content_area().add(_box);
+
+		this.delete_event.connect(on_delete_event);
+
+		_never_opened_before = true;
+		_stack.map.connect(on_stack_map);
+	}
+
+	public void on_stack_map()
+	{
+		if (_never_opened_before) {
+			_never_opened_before = false;
+
+			TargetPlatform host_platform = TargetPlatform.COUNT;
+#if CROWN_PLATFORM_WINDOWS
+			host_platform = TargetPlatform.WINDOWS;
+#elif CROWN_PLATFORM_LINUX
+			host_platform = TargetPlatform.LINUX;
+#endif
+
+			if (host_platform == TargetPlatform.COUNT) {
+				_platforms.get_selection().select_path(new Gtk.TreePath.first());
+			} else {
+				_platforms_store.foreach((model, path, iter) => {
+						Value platform;
+						model.get_value(iter, 0, out platform);
+
+						if (((TargetPlatform)platform) == host_platform) {
+							_platforms.get_selection().select_iter(iter);
+							return true;
+						}
+
+						return false;
+					});
+			}
+		}
+
+		_platforms.grab_focus();
+	}
+
+	public int load_texture(string texture_name)
+	{
+		string new_texture_path = texture_name + ".texture";
+
+		if (_texture_path != new_texture_path)
+			save();
+
+		_texture_database.reset();
+		if (_texture_database.add_from_resource_path(out _texture_id, new_texture_path) != 0) {
+			_texture_id = GUID_ZERO;
+			return -1;
+		}
+
+		_texture_path = new_texture_path;
+
+		on_platforms_selection_changed();
+		return 0;
+	}
+
+	public void set_texture(string texture_name)
+	{
+		if (load_texture(texture_name) == 0)
+			_texture_name.value = texture_name;
+	}
+
+	public void on_texture_resource_value_changed()
+	{
+		load_texture(_texture_name.value);
+	}
+
+	public bool are_values_equal(Value? a, Value? b)
+	{
+		if (a.type() != b.type())
+			return false;
+
+		if (a.holds(typeof(bool))) {
+			return (bool)a == (bool)b;
+		} else if (a.holds(typeof(double))) {
+			return (double)a == (double)b;
+		} else if (a.holds(typeof(string))) {
+			return (string)a == (string)b;
+		} else if (a == null && b == null) {
+			return true;
+		}
+
+		return false;
+	}
+
+	public void on_platforms_selection_changed()
+	{
+		if (_texture_id == GUID_ZERO)
+			return;
+
+		if (_platforms.get_selection().count_selected_rows() > 0) {
+			_stack.set_visible_child_full("some-selected", Gtk.StackTransitionType.NONE);
+		} else {
+			_stack.set_visible_child_full("none-selected", Gtk.StackTransitionType.NONE);
+			return;
+		}
+
+		string property_names[] = { "source", "format", "generate_mips", "mip_skip_smallest", "normal_map" };
+		Property properties[] = { _source, _format, _generate_mips, _mip_skip_smallest, _normal_map };
+
+		for (int i = 0; i < properties.length; ++i)
+			properties[i].set_data("init", false);
+
+		for (int i = 0; i < properties.length; ++i) {
+			_platforms.get_selection().selected_foreach((model, path, iter) => {
+					Value? platform;
+					model.get_value(iter, 0, out platform);
+					string key = platform_property(((TargetPlatform)platform).to_key(), property_names[i]);
+					bool init = properties[i].get_data<bool>("init");
+
+					// Try <platform>.<property> first. Fallback to <property>.
+					if (!_texture_database.has_property(_texture_id, key))
+						key = property_names[i];
+
+					if (_texture_database.has_property(_texture_id, key)) {
+						Value? val = _texture_database.get_property(_texture_id, key);
+
+						if (!init) {
+							properties[i].set_data("init", true);
+							properties[i].set_generic_value(val);
+							properties[i].set_inconsistent(false);
+						} else if (!are_values_equal(val, properties[i].generic_value())) {
+							properties[i].set_inconsistent(true);
+						}
+					} else {
+						properties[i].set_inconsistent(true);
+
+						if (!init) {
+							properties[i].set_data("init", true);
+						}
+					}
+				});
+		}
+	}
+
+	public void on_format_value_changed()
+	{
+		on_property_value_changed("format", _format);
+	}
+
+	public void on_generate_mips_value_changed()
+	{
+		on_property_value_changed("generate_mips", _generate_mips);
+	}
+
+	public void on_mip_skip_smallest_value_changed()
+	{
+		on_property_value_changed("mip_skip_smallest", _mip_skip_smallest);
+	}
+
+	public void on_normal_map_value_changed()
+	{
+		on_property_value_changed("normal_map", _normal_map);
+	}
+
+	public void on_property_value_changed(string property_name, Property property_value)
+	{
+		if (_texture_id == GUID_ZERO)
+			return;
+
+		Value? val = property_value.generic_value();
+
+		// For backward compatibility.
+		if (property_name == "generate_mips" || property_name == "normal_map") {
+			if (_texture_database.has_property(_texture_id, property_name))
+				_texture_database.set_property(_texture_id, property_name, val);
+		}
+
+		_platforms.get_selection().selected_foreach((model, path, iter) => {
+				Value? platform;
+				model.get_value(iter, 0, out platform);
+
+				string key = platform_property(((TargetPlatform)platform).to_key(), property_name);
+
+				_texture_database.set_property(_texture_id, key, val);
+			});
+	}
+
+	public void save()
+	{
+		if (_texture_id == GUID_ZERO)
+			return;
+
+		_texture_database.dump(_project.absolute_path(_texture_path), _texture_id);
+		texture_saved();
+	}
+
+	public bool on_delete_event(Gdk.EventAny ev)
+	{
+		save();
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	public string platform_property(string platform_name, string property)
+	{
+		return "output."
+			+ platform_name
+			+ "."
+			+ property
+			;
+	}
+}
+
+} /* namespace Crown */