diff --git a/locale/catalog_export_en.properties b/locale/catalog_export_en.properties
new file mode 100644
index 0000000..9f3c41a
--- /dev/null
+++ b/locale/catalog_export_en.properties
@@ -0,0 +1,2 @@
+# Catalog export tool — extra keys
+mods/catalog_select_mod = Select a mod:
diff --git a/locale/catalog_export_ru.properties b/locale/catalog_export_ru.properties
new file mode 100644
index 0000000..9c721f3
--- /dev/null
+++ b/locale/catalog_export_ru.properties
@@ -0,0 +1,2 @@
+# Catalog export tool — дополнительные ключи
+mods/catalog_select_mod = Выберите мод:
diff --git a/locale/ui_en.properties b/locale/ui_en.properties
index 0e624d4..9ea2844 100644
--- a/locale/ui_en.properties
+++ b/locale/ui_en.properties
@@ -86,6 +86,8 @@ tools/scenario_editor/unsaved_switch_text = You have unsaved changes. Switch fil
tools/unsaved_title = Unsaved changes
tools/unsaved_text = You have unsaved changes. Close without saving?
+mods/catalog_select_mod = Select a mod:
+
tools/scenario_editor/play = ▶ Play
tools/scenario_editor/play_tooltip = Run scenario in test mode
tools/scenario_editor/stop = ⏹ Stop
diff --git a/manifest.json b/manifest.json
index b31c28f..c2f2b18 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,9 +4,9 @@
"version": "1.0.0",
"author": "ct",
"description": "Development tools",
- "category": "tools",
+ "category": "tools, editor",
"sources": [
"src/**/*.cs"
],
- "depends": ["core>=1"]
+ "depends": ["visual_novel_core>=1"]
}
diff --git a/src/CatalogExport/CatalogExportToolProvider.cs b/src/CatalogExport/CatalogExportToolProvider.cs
new file mode 100644
index 0000000..042575e
--- /dev/null
+++ b/src/CatalogExport/CatalogExportToolProvider.cs
@@ -0,0 +1,23 @@
+using Cthangover.Core.UI.Tool;
+using Godot;
+
+namespace Cthangover.Core.UI.Tool.CatalogExport
+{
+ ///
+ /// Registers the catalog export window as a developer tool. Moved here from
+ /// the old mods menu when mod management UI was delegated to the
+ /// mod_manager mod — exporting a catalog entry is an authoring task,
+ /// not part of day-to-day mod management.
+ ///
+ public class CatalogExportToolProvider : IToolProvider
+ {
+ /// Unique tool identifier.
+ public string Id => "catalog_export";
+
+ /// Translation key for the tool's display name.
+ public string LocaleKey => "mods/catalog_title";
+
+ /// Creates the export window instance.
+ public Window CreateWindow() => new CatalogExportWindow();
+ }
+}
diff --git a/src/CatalogExport/CatalogExportWindow.cs b/src/CatalogExport/CatalogExportWindow.cs
new file mode 100644
index 0000000..c009ad9
--- /dev/null
+++ b/src/CatalogExport/CatalogExportWindow.cs
@@ -0,0 +1,499 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using Cthangover.Core.Mods;
+using Cthangover.Core.UI.Theming;
+using Cthangover.Core.UI.Tool;
+using Cthangover.Core.Utils;
+using Godot;
+
+namespace Cthangover.Core.UI.Tool.CatalogExport
+{
+ ///
+ /// Developer tool that builds a catalog entry (JSON for the online mod
+ /// catalog) from a locally installed mod's manifest. Ported out of the old
+ /// mods menu so that mod-management UI can live in the mod_manager
+ /// mod while this authoring utility stays a tool.
+ ///
+ /// The window lets the author pick any discovered mod, fill in the
+ /// catalog-only metadata (download URL, category, tags, changelog, status,
+ /// minimum game version, icon URL, localized names/descriptions), preview
+ /// the resulting JSON, copy it to the clipboard, or export it next to the
+ /// mod's zip via .
+ ///
+ public partial class CatalogExportWindow : ToolWindow
+ {
+ private static readonly string[] Statuses = { "stable", "beta", "deprecated", "archived" };
+ private static readonly CategoryType[] Categories = Enum.GetValues();
+
+ private OptionButton _modSelect;
+ private VBoxContainer _formHost;
+
+ private ModInfo _modInfo;
+ private ModManifest _manifest;
+ private LineEdit _urlEdit;
+ private ItemList _categoryList;
+ private LineEdit _tagsEdit;
+ private TextEdit _changelogEdit;
+ private OptionButton _statusOpt;
+ private LineEdit _minGameVerEdit;
+ private LineEdit _iconUrlEdit;
+ private VBoxContainer _localeContainer;
+ private TextEdit _previewEdit;
+
+ public CatalogExportWindow() : base("mods/catalog_title")
+ {
+ Size = new Vector2I(620, 720);
+ CthangoverTheme.Apply(this);
+
+ var vbox = CreateFillContainer();
+ vbox.AddThemeConstantOverride("separation", 8);
+ AddChild(vbox);
+
+ var selectRow = new HBoxContainer();
+ selectRow.AddThemeConstantOverride("separation", 8);
+ var selectLabel = new Label { Text = TranslationServer.Translate("mods/catalog_select_mod") };
+ selectLabel.AddThemeColorOverride("font_color", UiTokens.Muted);
+ selectRow.AddChild(selectLabel);
+
+ _modSelect = new OptionButton
+ {
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ };
+ _modSelect.ItemSelected += OnModSelected;
+ selectRow.AddChild(_modSelect);
+ vbox.AddChild(selectRow);
+
+ var scroll = new ScrollContainer
+ {
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ SizeFlagsVertical = Control.SizeFlags.ExpandFill,
+ HorizontalScrollMode = ScrollContainer.ScrollMode.Disabled,
+ };
+ vbox.AddChild(scroll);
+
+ _formHost = new VBoxContainer
+ {
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ };
+ _formHost.AddThemeConstantOverride("separation", 8);
+ scroll.AddChild(_formHost);
+
+ PopulateModList();
+ }
+
+ private void PopulateModList()
+ {
+ var ordered = ModRegistry.Instance.GetOrderedModIds(filterDisabled: false);
+ foreach (var id in ordered)
+ {
+ var info = ModRegistry.Instance.GetMod(id);
+ if (info == null)
+ continue;
+
+ _modSelect.AddItem(info.DisplayTitle);
+ _modSelect.SetItemMetadata(_modSelect.ItemCount - 1, id);
+ }
+
+ if (_modSelect.ItemCount > 0)
+ {
+ _modSelect.Selected = 0;
+ BuildForm(_modSelect.GetItemMetadata(0).AsString());
+ }
+ }
+
+ private void OnModSelected(long index)
+ {
+ BuildForm(_modSelect.GetItemMetadata((int)index).AsString());
+ }
+
+ private void BuildForm(string modId)
+ {
+ foreach (var child in _formHost.GetChildren())
+ child.QueueFree();
+
+ _modInfo = ModRegistry.Instance.GetMod(modId) as ModInfo;
+ _manifest = _modInfo?.Manifest;
+ if (_modInfo == null || _manifest == null)
+ return;
+
+ AddSectionLabel(_formHost, "Mod Info");
+ AddReadOnlyField(_formHost, "mods/id", _modInfo.Id);
+ AddReadOnlyField(_formHost, "mods/name", _modInfo.DisplayTitle);
+ AddReadOnlyField(_formHost, "mods/version", _modInfo.Version ?? "—");
+ AddReadOnlyField(_formHost, "mods/author", _modInfo.Author ?? "—");
+
+ AddSectionLabel(_formHost, "Catalog Metadata");
+ _urlEdit = AddLineEdit(_formHost, "mods/catalog_download_url");
+
+ _categoryList = new ItemList
+ {
+ CustomMinimumSize = new Vector2(0, 130),
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ SelectMode = ItemList.SelectModeEnum.Toggle,
+ };
+ var preselected = _manifest.Category ?? CategoryType.Other;
+ for (var i = 0; i < Categories.Length; i++)
+ {
+ _categoryList.AddItem(GetCategoryDisplayName(Categories[i]));
+ if ((preselected & Categories[i]) != 0)
+ _categoryList.Select(i, false);
+ }
+ AddLabeledControl(_formHost, "mods/catalog_category", _categoryList);
+
+ _tagsEdit = AddLineEdit(_formHost, "mods/catalog_tags");
+
+ _changelogEdit = new TextEdit
+ {
+ CustomMinimumSize = new Vector2(0, 60),
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ WrapMode = TextEdit.LineWrappingMode.Boundary,
+ };
+ AddLabeledControl(_formHost, "mods/catalog_changelog", _changelogEdit);
+
+ _statusOpt = AddOptionButton(_formHost, "mods/catalog_status", Statuses);
+
+ _minGameVerEdit = AddLineEdit(_formHost, "mods/catalog_min_game_version");
+ _iconUrlEdit = AddLineEdit(_formHost, "mods/catalog_icon_url");
+
+ AddSectionLabel(_formHost, "JSON Preview");
+ _previewEdit = new TextEdit
+ {
+ CustomMinimumSize = new Vector2(0, 160),
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ Editable = false,
+ WrapMode = TextEdit.LineWrappingMode.None,
+ };
+ _previewEdit.AddThemeFontSizeOverride("font_size", UiTokens.FontSmall);
+ _formHost.AddChild(_previewEdit);
+
+ var btnRow = new HBoxContainer
+ {
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ };
+ btnRow.AddThemeConstantOverride("separation", 8);
+
+ var copyBtn = new Button
+ {
+ Text = TranslationServer.Translate("mods/catalog_copy"),
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ };
+ copyBtn.Pressed += OnCopyPressed;
+ btnRow.AddChild(copyBtn);
+
+ var exportBtn = new Button
+ {
+ Text = TranslationServer.Translate("mods/catalog_export_file"),
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ };
+ exportBtn.Pressed += OnExportPressed;
+ btnRow.AddChild(exportBtn);
+
+ var closeBtn = new Button
+ {
+ Text = TranslationServer.Translate("mods/catalog_close"),
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ };
+ closeBtn.Pressed += () => QueueFree();
+ btnRow.AddChild(closeBtn);
+
+ _formHost.AddChild(btnRow);
+
+ AddSectionLabel(_formHost, "Locales");
+ _localeContainer = new VBoxContainer
+ {
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ };
+ _localeContainer.AddThemeConstantOverride("separation", 4);
+ _formHost.AddChild(_localeContainer);
+
+ var addLocaleBtn = new Button
+ {
+ Text = TranslationServer.Translate("mods/catalog_add_locale"),
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ };
+ addLocaleBtn.Pressed += () => AddLocaleRow(string.Empty, string.Empty, string.Empty);
+ _formHost.AddChild(addLocaleBtn);
+
+ _urlEdit.TextChanged += _ => UpdatePreview();
+ _categoryList.ItemSelected += _ => UpdatePreview();
+ _categoryList.MultiSelected += (_, _) => UpdatePreview();
+ _tagsEdit.TextChanged += _ => UpdatePreview();
+ _changelogEdit.TextChanged += UpdatePreview;
+ _statusOpt.ItemSelected += _ => UpdatePreview();
+ _minGameVerEdit.TextChanged += _ => UpdatePreview();
+ _iconUrlEdit.TextChanged += _ => UpdatePreview();
+
+ UpdatePreview();
+ }
+
+ private static string GetCategoryDisplayName(CategoryType cat) => cat switch
+ {
+ CategoryType.Story => TranslationServer.Translate("mods/category_story"),
+ CategoryType.Characters => TranslationServer.Translate("mods/category_characters"),
+ CategoryType.Gameplay => TranslationServer.Translate("mods/category_gameplay"),
+ CategoryType.Visual => TranslationServer.Translate("mods/category_visual"),
+ CategoryType.Audio => TranslationServer.Translate("mods/category_audio"),
+ CategoryType.UI => TranslationServer.Translate("mods/category_ui"),
+ CategoryType.Locale => TranslationServer.Translate("mods/category_locale"),
+ CategoryType.Library => TranslationServer.Translate("mods/category_library"),
+ CategoryType.Engine => TranslationServer.Translate("mods/category_engine"),
+ CategoryType.Overhaul => TranslationServer.Translate("mods/category_overhaul"),
+ CategoryType.Tools => TranslationServer.Translate("mods/category_tools"),
+ CategoryType.Editor => TranslationServer.Translate("mods/category_editor"),
+ CategoryType.Debug => TranslationServer.Translate("mods/category_debug"),
+ CategoryType.Other => TranslationServer.Translate("mods/category_other"),
+ _ => cat.ToString(),
+ };
+
+ private void AddLocaleRow(string lang, string name, string description)
+ {
+ var row = new HBoxContainer
+ {
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ };
+ row.AddThemeConstantOverride("separation", 4);
+
+ var langEdit = new LineEdit
+ {
+ CustomMinimumSize = new Vector2(44, 0),
+ PlaceholderText = "en",
+ MaxLength = 5,
+ Text = lang,
+ };
+ row.AddChild(langEdit);
+
+ var nameEdit = new LineEdit
+ {
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ PlaceholderText = TranslationServer.Translate("mods/catalog_locale_name"),
+ Text = name,
+ };
+ row.AddChild(nameEdit);
+
+ var descEdit = new LineEdit
+ {
+ SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
+ PlaceholderText = TranslationServer.Translate("mods/catalog_locale_desc"),
+ Text = description,
+ };
+ row.AddChild(descEdit);
+
+ var removeBtn = new Button { Text = "×", CustomMinimumSize = new Vector2(28, 0) };
+ removeBtn.Pressed += () => RemoveLocaleRow(row);
+ row.AddChild(removeBtn);
+
+ langEdit.TextChanged += _ => UpdatePreview();
+ nameEdit.TextChanged += _ => UpdatePreview();
+ descEdit.TextChanged += _ => UpdatePreview();
+
+ _localeContainer.AddChild(row);
+ }
+
+ private void RemoveLocaleRow(HBoxContainer row)
+ {
+ _localeContainer.RemoveChild(row);
+ row.QueueFree();
+ UpdatePreview();
+ }
+
+ private void UpdatePreview()
+ {
+ if (_previewEdit == null || _modInfo == null || _manifest == null)
+ return;
+
+ var entry = BuildEntryFromManifest(_manifest);
+ entry.Downloads = new List
+ {
+ new()
+ {
+ Version = entry.Version,
+ Urls = new List { _urlEdit.Text.Trim() },
+ SizeBytes = 0,
+ ArchiveSha256 = "—",
+ InstallSha256 = "—",
+ },
+ };
+ entry.Category = CollectSelectedCategories(_categoryList, Categories);
+ entry.Tags = ParseTags(_tagsEdit.Text);
+ entry.Downloads[0].Changelog = NullIfEmpty(_changelogEdit.Text);
+ entry.Status = Statuses[_statusOpt.Selected];
+ entry.MinGameVersion = NullIfEmpty(_minGameVerEdit.Text);
+ entry.IconUrl = NullIfEmpty(_iconUrlEdit.Text);
+ entry.Locale = CollectLocales();
+ _previewEdit.Text = JsonSerializer.Serialize(entry, JsonOptionsProvider.WriteIndentedIgnoreNullOptions);
+ }
+
+ private List CollectLocales()
+ {
+ var list = new List();
+ foreach (var child in _localeContainer.GetChildren())
+ {
+ if (child is not HBoxContainer row || row.GetChildCount() < 3)
+ continue;
+
+ var lang = ((LineEdit)row.GetChild(0)).Text.Trim();
+ var name = ((LineEdit)row.GetChild(1)).Text.Trim();
+ var description = ((LineEdit)row.GetChild(2)).Text.Trim();
+ if (lang.Length == 0)
+ continue;
+
+ list.Add(new CatalogLocaleEntry
+ {
+ Lang = lang,
+ Name = NullIfEmpty(name),
+ Description = NullIfEmpty(description),
+ });
+ }
+
+ if (list.Count == 0 && _manifest != null)
+ {
+ list.Add(new CatalogLocaleEntry
+ {
+ Lang = "en",
+ Name = _manifest.Name,
+ Description = _manifest.Description,
+ });
+ }
+
+ return list;
+ }
+
+ private CatalogExportEntry BuildExportEntry()
+ {
+ var entry = BuildEntryFromManifest(_manifest);
+ entry.Downloads = new List
+ {
+ new()
+ {
+ Version = entry.Version,
+ Urls = new List { _urlEdit.Text.Trim() },
+ SizeBytes = 0,
+ },
+ };
+ entry.Category = CollectSelectedCategories(_categoryList, Categories);
+ entry.Tags = ParseTags(_tagsEdit.Text);
+ entry.Downloads[0].Changelog = NullIfEmpty(_changelogEdit.Text);
+ entry.Status = Statuses[_statusOpt.Selected];
+ entry.MinGameVersion = NullIfEmpty(_minGameVerEdit.Text);
+ entry.IconUrl = NullIfEmpty(_iconUrlEdit.Text);
+ entry.Locale = CollectLocales();
+ return entry;
+ }
+
+ private void OnCopyPressed()
+ {
+ DisplayServer.ClipboardSet(_previewEdit.Text);
+ GameLogger.Log("CATALOG_EXPORT", $"Copied catalog JSON for '{_modInfo?.Id}' to clipboard");
+ }
+
+ private void OnExportPressed()
+ {
+ if (_modInfo == null)
+ return;
+
+ var entry = BuildExportEntry();
+ var modsRoot = ModRegistry.ModsPath;
+ var isZip = _modInfo.FileProvider.RootPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase);
+ if (isZip)
+ ModCatalogExporter.ExportFromZip(_modInfo, entry, modsRoot);
+ else
+ ModCatalogExporter.ExportFromFolder(_modInfo, entry, modsRoot);
+
+ QueueFree();
+ }
+
+ private static List ParseTags(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text))
+ return null;
+ return text.Split(',').Select(t => t.Trim()).Where(t => t.Length > 0).ToList();
+ }
+
+ private static string NullIfEmpty(string text)
+ {
+ var trimmed = text?.Trim();
+ return string.IsNullOrEmpty(trimmed) ? null : trimmed;
+ }
+
+ private static CatalogExportEntry BuildEntryFromManifest(ModManifest manifest)
+ {
+ var depends = manifest.Depends?.Select(d => d.ToString()).Where(s => s.Length > 0).ToList();
+
+ return new CatalogExportEntry
+ {
+ Id = manifest.Id,
+ Name = manifest.Name,
+ Author = manifest.Author,
+ Version = manifest.Version,
+ Description = manifest.Description,
+ Category = manifest.Category ?? CategoryType.Other,
+ Depends = depends,
+ ConflictsWith = manifest.ConflictsWith,
+ };
+ }
+
+ private static CategoryType CollectSelectedCategories(ItemList list, CategoryType[] categories)
+ {
+ var selected = (CategoryType)0;
+ for (var i = 0; i < categories.Length && i < list.ItemCount; i++)
+ {
+ if (list.IsSelected(i))
+ selected |= categories[i];
+ }
+ return selected != 0 ? selected : CategoryType.Other;
+ }
+
+ private static void AddLabeledControl(Container parent, string labelKey, Control control)
+ {
+ var box = new VBoxContainer { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
+ var label = new Label
+ {
+ Text = TranslationServer.Translate(labelKey),
+ };
+ label.AddThemeFontSizeOverride("font_size", UiTokens.FontBodySmall);
+ label.AddThemeColorOverride("font_color", UiTokens.Muted);
+ box.AddChild(label);
+ box.AddChild(control);
+ parent.AddChild(box);
+ }
+
+ private static void AddReadOnlyField(Container parent, string labelKey, string value)
+ {
+ var label = new Label
+ {
+ Text = $"{TranslationServer.Translate(labelKey)}: {value}",
+ };
+ label.AddThemeFontSizeOverride("font_size", UiTokens.FontBody);
+ label.AddThemeColorOverride("font_color", UiTokens.Body);
+ parent.AddChild(label);
+ }
+
+ private static LineEdit AddLineEdit(Container parent, string labelKey)
+ {
+ var edit = new LineEdit { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
+ AddLabeledControl(parent, labelKey, edit);
+ return edit;
+ }
+
+ private static OptionButton AddOptionButton(Container parent, string labelKey, string[] items)
+ {
+ var opt = new OptionButton { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
+ foreach (var item in items)
+ opt.AddItem(item);
+ AddLabeledControl(parent, labelKey, opt);
+ return opt;
+ }
+
+ private static void AddSectionLabel(Container parent, string text)
+ {
+ var sep = new HSeparator();
+ parent.AddChild(sep);
+ var label = new Label { Text = text };
+ label.AddThemeFontSizeOverride("font_size", UiTokens.FontBodyLarge);
+ label.AddThemeColorOverride("font_color", UiTokens.Ink);
+ parent.AddChild(label);
+ }
+ }
+}
diff --git a/src/InteractiveEditor/InteractiveEditorWindow.cs b/src/InteractiveEditor/InteractiveEditorWindow.cs
index 362e935..7c7ead4 100644
--- a/src/InteractiveEditor/InteractiveEditorWindow.cs
+++ b/src/InteractiveEditor/InteractiveEditorWindow.cs
@@ -1,9 +1,10 @@
-using System;
+using System;
using System.Collections.Generic;
using Cthangover.Core.Interactive;
using Cthangover.Core.Mods;
using Cthangover.Core.Mods.Resolvers;
using Cthangover.Core.Factories.Impls;
+using Cthangover.Core.UI.Theming;
using Cthangover.Core.UI.Tool;
using Cthangover.Core.Utils;
using Cthangover.Tools.Services;
@@ -13,13 +14,13 @@ namespace Cthangover.Tools.InteractiveEditor
{
public partial class InteractiveEditorWindow : ToolWindow
{
- private static readonly Color _rectColor = new(0f, 1f, 0f, 0.6f);
- private static readonly Color _circleColor = new(0f, 0.5f, 1f, 0.6f);
- private static readonly Color _vertexColor = new(1f, 0.8f, 0f, 0.8f);
- private static readonly Color _colliderOverlayColor = new(0f, 1f, 0f, 0.15f);
- private static readonly Color _colliderCircleOverlayColor = new(0f, 0.5f, 1f, 0.15f);
- private static readonly Color _colliderPolyOverlayColor = new(1f, 0.5f, 0f, 0.15f);
- private static readonly Color _colliderPolyOutlineColor = new(1f, 0.5f, 0f, 0.6f);
+ private static readonly Color _rectColor = UiTokens.Alpha(UiTokens.Success, 0.6f);
+ private static readonly Color _circleColor = UiTokens.Alpha(UiTokens.Info, 0.6f);
+ private static readonly Color _vertexColor = UiTokens.Alpha(UiTokens.AccentBright, 0.8f);
+ private static readonly Color _colliderOverlayColor = UiTokens.Alpha(UiTokens.Success, 0.15f);
+ private static readonly Color _colliderCircleOverlayColor = UiTokens.Alpha(UiTokens.Info, 0.15f);
+ private static readonly Color _colliderPolyOverlayColor = UiTokens.Alpha(UiTokens.Accent, 0.15f);
+ private static readonly Color _colliderPolyOutlineColor = UiTokens.Alpha(UiTokens.Accent, 0.6f);
private InteractiveEditorController _controller = new();
@@ -56,6 +57,7 @@ namespace Cthangover.Tools.InteractiveEditor
public InteractiveEditorWindow() : base("tools/interactive_editor/title")
{
Size = new Vector2I(1280, 820);
+ CthangoverTheme.Apply(this);
var outer = CreateFillContainer();
AddChild(outer);
@@ -215,7 +217,7 @@ namespace Cthangover.Tools.InteractiveEditor
AddLabel(parent, "tools/interactive_editor/label_highlight_color");
_highlightColorBtn = new ColorPickerButton
{
- Color = new Color(1f, 1f, 0f, 0.2f),
+ Color = UiTokens.Alpha(UiTokens.Accent, 0.2f),
CustomMinimumSize = new Vector2(40, 30)
};
_highlightColorBtn.ColorChanged += _ => OnParamChanged();
@@ -262,7 +264,7 @@ namespace Cthangover.Tools.InteractiveEditor
private void AddLabel(Control parent, string locKey)
{
var label = new Label { Text = Tr(locKey) };
- label.AddThemeColorOverride("font_color", new Color(0.7f, 0.7f, 0.7f));
+ label.AddThemeColorOverride("font_color", UiTokens.Muted);
parent.AddChild(label);
}
@@ -850,3 +852,4 @@ namespace Cthangover.Tools.InteractiveEditor
}
}
}
+
diff --git a/src/LightEditor/LightEditorController.cs b/src/LightEditor/LightEditorController.cs
index 7c9c56d..28919aa 100644
--- a/src/LightEditor/LightEditorController.cs
+++ b/src/LightEditor/LightEditorController.cs
@@ -3,6 +3,7 @@ using System.Text.Json;
using Cthangover.Core.Mods;
using Cthangover.Core.Mods.Resolvers;
using Cthangover.Core.UI.Lights;
+using Cthangover.Core.UI.Theming;
using Godot;
namespace Cthangover.Core.UI.Tool.LightEditor
@@ -36,7 +37,7 @@ namespace Cthangover.Core.UI.Tool.LightEditor
previewPositions[i] = new Vector2(-1000f, -1000f);
previewRadii[i] = 0f;
previewInfluence[i] = 0f;
- previewColors[i] = Colors.White;
+ previewColors[i] = UiTokens.White;
}
}
@@ -122,7 +123,7 @@ namespace Cthangover.Core.UI.Tool.LightEditor
previewPositions[i] = new Vector2(-1000f, -1000f);
previewRadii[i] = 0f;
previewInfluence[i] = 0f;
- previewColors[i] = Colors.White;
+ previewColors[i] = UiTokens.White;
}
int count = Mathf.Min(Lights.Count, 10);
diff --git a/src/LightEditor/LightEditorWindow.cs b/src/LightEditor/LightEditorWindow.cs
index c37131d..6cdd0e4 100644
--- a/src/LightEditor/LightEditorWindow.cs
+++ b/src/LightEditor/LightEditorWindow.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Text.Json;
using Cthangover.Core.Factories.Impls;
using Cthangover.Core.UI.Lights;
+using Cthangover.Core.UI.Theming;
using Cthangover.Core.Utils;
using Cthangover.Tools.Services;
using Godot;
@@ -40,6 +41,7 @@ namespace Cthangover.Core.UI.Tool.LightEditor
public LightEditorWindow() : base("tools/light_editor/title")
{
controller = new LightEditorController();
+ CthangoverTheme.Apply(this);
BuildUI();
LoadBackgroundList();
}
diff --git a/src/ScenarioEditor/ScenarioEditorWindow.cs b/src/ScenarioEditor/ScenarioEditorWindow.cs
index f2c8973..050c211 100644
--- a/src/ScenarioEditor/ScenarioEditorWindow.cs
+++ b/src/ScenarioEditor/ScenarioEditorWindow.cs
@@ -1,5 +1,6 @@
using System;
using Cthangover.Core.Scenes;
+using Cthangover.Core.UI.Theming;
using Cthangover.Core.Utils;
using Cthangover.Tools.Services;
using Godot;
@@ -27,6 +28,7 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor
public ScenarioEditorWindow() : base("tools/scenario_editor/title")
{
+ CthangoverTheme.Apply(this);
BuildUI();
}
diff --git a/src/ScenarioEditor/ScenarioFileTreePanel.cs b/src/ScenarioEditor/ScenarioFileTreePanel.cs
index b36d6ff..8d68d16 100644
--- a/src/ScenarioEditor/ScenarioFileTreePanel.cs
+++ b/src/ScenarioEditor/ScenarioFileTreePanel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Cthangover.Core.UI.Theming;
using Cthangover.Tools.Services;
using Godot;
@@ -36,7 +37,7 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor
var modItem = _fileTree.CreateItem(root);
modItem.SetText(0, $"[{mod.ModId}]");
modItem.SetSelectable(0, false);
- modItem.SetCustomColor(0, new Color(0.5f, 0.75f, 1f));
+ modItem.SetCustomColor(0, UiTokens.Info);
foreach (var group in mod.Groups)
{
diff --git a/src/ScenarioEditor/ScenarioGraphView.cs b/src/ScenarioEditor/ScenarioGraphView.cs
index 8df0d16..45b5e9a 100644
--- a/src/ScenarioEditor/ScenarioGraphView.cs
+++ b/src/ScenarioEditor/ScenarioGraphView.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Cthangover.Core.UI.Theming;
using Cthangover.Tools.Services;
using Godot;
@@ -102,18 +103,18 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor
(bgCount > 1 ? $" +{bgCount - 1}" : "") : "")
});
- var color = Colors.White;
+ var color = UiTokens.White;
if (nodeData.IsStartScene)
{
- color = new Color(0.3f, 1f, 0.3f);
- gn.AddThemeStyleboxOverride("titlebar", CreateTitlebarStyle(new Color(0.2f, 0.5f, 0.15f)));
+ color = UiTokens.Success;
+ gn.AddThemeStyleboxOverride("titlebar", CreateTitlebarStyle(UiTokens.Alpha(UiTokens.Success, 0.3f)));
}
else if (nodeData.HasErrors)
{
- color = new Color(1f, 0.3f, 0.3f);
- gn.AddThemeStyleboxOverride("titlebar", CreateTitlebarStyle(new Color(0.5f, 0.1f, 0.1f)));
+ color = UiTokens.DangerText;
+ gn.AddThemeStyleboxOverride("titlebar", CreateTitlebarStyle(UiTokens.Alpha(UiTokens.Danger, 0.5f)));
}
- gn.SetSlot(0, true, 0, Colors.White, true, 0, color);
+ gn.SetSlot(0, true, 0, UiTokens.White, true, 0, color);
_graph.AddChild(gn);
_nodes[key] = gn;
@@ -148,14 +149,10 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor
private static StyleBoxFlat CreateTitlebarStyle(Color bgColor)
{
- return new StyleBoxFlat
- {
- BgColor = bgColor,
- CornerRadiusTopLeft = 6,
- CornerRadiusTopRight = 6,
- CornerRadiusBottomLeft = 0,
- CornerRadiusBottomRight = 0
- };
+ var style = UiKit.Frame(UiTokens.RadiusMd, bgColor);
+ style.CornerRadiusBottomLeft = 0;
+ style.CornerRadiusBottomRight = 0;
+ return style;
}
private void ClearGraph()
diff --git a/src/SceneBuilder/InspectorPanel.cs b/src/SceneBuilder/InspectorPanel.cs
index 8025099..ed8133d 100644
--- a/src/SceneBuilder/InspectorPanel.cs
+++ b/src/SceneBuilder/InspectorPanel.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Cthangover.Core.UI;
+using Cthangover.Core.UI.Theming;
using Godot;
namespace Cthangover.Core.UI.Tool.SceneBuilder
@@ -65,7 +66,7 @@ namespace Cthangover.Core.UI.Tool.SceneBuilder
_banner = new Label();
_banner.AutowrapMode = TextServer.AutowrapMode.WordSmart;
_banner.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
- _banner.Modulate = new Color(1f, 0.75f, 0.3f);
+ _banner.Modulate = UiTokens.Warning;
_banner.Visible = false;
root.AddChild(_banner);
@@ -125,7 +126,7 @@ namespace Cthangover.Core.UI.Tool.SceneBuilder
foreach (var entry in InspectorPropertyCatalog.Build(node, insideContainer))
{
var categoryLabel = new Label { Text = TranslationServer.Translate(entry.CategoryKey) };
- categoryLabel.Modulate = new Color(1f, 0.85f, 0.4f);
+ categoryLabel.Modulate = UiTokens.AccentBright;
_rows.AddChild(categoryLabel);
foreach (var property in entry.Properties)
@@ -367,7 +368,7 @@ namespace Cthangover.Core.UI.Tool.SceneBuilder
private Color ReadColor(string key)
{
var v = _node.Get(key);
- return v.VariantType == Variant.Type.Nil ? Colors.White : v.AsColor();
+ return v.VariantType == Variant.Type.Nil ? UiTokens.White : v.AsColor();
}
private Vector2 ReadVector2(string key)
diff --git a/src/SceneBuilder/SceneBuilderController.cs b/src/SceneBuilder/SceneBuilderController.cs
index a546142..d424ba7 100644
--- a/src/SceneBuilder/SceneBuilderController.cs
+++ b/src/SceneBuilder/SceneBuilderController.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using Cthangover.Core.Mods;
using Cthangover.Core.Scenes;
+using Cthangover.Core.UI.Theming;
using Godot;
namespace Cthangover.Core.UI.Tool.SceneBuilder
@@ -240,7 +241,7 @@ namespace Cthangover.Core.UI.Tool.SceneBuilder
private void AttachHighlight(Node root)
{
_highlight = new ColorRect();
- _highlight.Color = new Color(1f, 0.7f, 0f, 0.35f);
+ _highlight.Color = UiTokens.Alpha(UiTokens.Accent, 0.35f);
_highlight.Visible = false;
_highlight.MouseFilter = Control.MouseFilterEnum.Ignore;
_highlight.ZIndex = 4096;
@@ -335,7 +336,7 @@ namespace Cthangover.Core.UI.Tool.SceneBuilder
_highlight.Position = pos;
_highlight.Size = new Vector2(20, 20);
- _highlight.Color = new Color(1f, 0.3f, 0.3f, 0.5f);
+ _highlight.Color = UiTokens.Alpha(UiTokens.Danger, 0.5f);
_highlight.Visible = true;
}
}
@@ -424,7 +425,7 @@ namespace Cthangover.Core.UI.Tool.SceneBuilder
{
if (_highlight != null)
_highlight.Visible = false;
- _highlight.Color = new Color(1f, 0.7f, 0f, 0.35f);
+ _highlight.Color = UiTokens.Alpha(UiTokens.Accent, 0.35f);
}
}
}
diff --git a/src/SceneBuilder/SceneBuilderWindow.cs b/src/SceneBuilder/SceneBuilderWindow.cs
index e9d9191..dc4dba1 100644
--- a/src/SceneBuilder/SceneBuilderWindow.cs
+++ b/src/SceneBuilder/SceneBuilderWindow.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Cthangover.Core.UI.Theming;
using Cthangover.Core.Utils;
using Godot;
@@ -56,6 +57,7 @@ namespace Cthangover.Core.UI.Tool.SceneBuilder
/// Constructs the window, creates the controller, builds UI, and populates selectors.
public SceneBuilderWindow() : base("tools/scene_builder/title")
{
+ CthangoverTheme.Apply(this);
_controller = new SceneBuilderController();
BuildUI();
PopulateBaseList();
diff --git a/src/SceneBuilder/SelectionOverlay.cs b/src/SceneBuilder/SelectionOverlay.cs
index 9d3cb04..e90769b 100644
--- a/src/SceneBuilder/SelectionOverlay.cs
+++ b/src/SceneBuilder/SelectionOverlay.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Cthangover.Core.UI;
+using Cthangover.Core.UI.Theming;
using Godot;
namespace Cthangover.Core.UI.Tool.SceneBuilder
@@ -87,13 +88,13 @@ namespace Cthangover.Core.UI.Tool.SceneBuilder
Resized += Refresh;
for (var i = 0; i < 4; i++)
- _borderRects.Add(CreateMarker(new Color(1f, 0.85f, 0.35f, 0.95f)));
+ _borderRects.Add(CreateMarker(UiTokens.Alpha(UiTokens.AccentBright, 0.95f)));
for (var i = 0; i < ResizeMarkerCount; i++)
- _handleRects.Add(CreateMarker(new Color(1f, 0.62f, 0.1f, 1f)));
+ _handleRects.Add(CreateMarker(UiTokens.Accent));
for (var i = 0; i < AnchorDotCount * 2; i++)
- _anchorLines.Add(CreateMarker(new Color(0.35f, 0.75f, 1f, 0.55f)));
+ _anchorLines.Add(CreateMarker(UiTokens.Alpha(UiTokens.Info, 0.55f)));
for (var i = 0; i < AnchorDotCount; i++)
- _anchorDots.Add(CreateMarker(new Color(0.45f, 0.85f, 1f, 1f)));
+ _anchorDots.Add(CreateMarker(UiTokens.Info));
GuiInput += OnOverlayGui;
}
diff --git a/src/Services/ScenarioSyntaxService.cs b/src/Services/ScenarioSyntaxService.cs
index 08468d9..725c711 100644
--- a/src/Services/ScenarioSyntaxService.cs
+++ b/src/Services/ScenarioSyntaxService.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Factories.Impls;
+using Cthangover.Core.UI.Theming;
using Godot;
namespace Cthangover.Tools.Services
@@ -18,19 +19,19 @@ namespace Cthangover.Tools.Services
{
///
/// Builds a for scenario DSL syntax.
- /// Comments (lines starting with #) are green, strings are orange,
- /// command names are blue, and argument keywords (e.g. key,
- /// wait, quest_id) are amber.
+ /// Comments (lines starting with #) are green, strings are bright
+ /// gold, command names are blue, and argument keywords (e.g. key,
+ /// wait, quest_id) are amber — all from .
///
public static CodeHighlighter CreateHighlighter()
{
var hl = new CodeHighlighter();
- hl.AddColorRegion("#", null, new Color(0.45f, 0.75f, 0.45f), true);
- hl.AddColorRegion("\"", "\"", new Color(1f, 0.6f, 0.4f), false);
- hl.NumberColor = new Color(0.7f, 0.7f, 1f);
- hl.SymbolColor = new Color(0.8f, 0.8f, 0.8f);
+ hl.AddColorRegion("#", null, UiTokens.Success, true);
+ hl.AddColorRegion("\"", "\"", UiTokens.AccentBright, false);
+ hl.NumberColor = UiTokens.Info;
+ hl.SymbolColor = UiTokens.Muted;
- var cmdColor = new Color(0.4f, 0.7f, 1f);
+ var cmdColor = UiTokens.Info;
hl.KeywordColors = new Godot.Collections.Dictionary();
foreach (var cmd in ScenarioCommandStrategyFactory.GetAllCommandNames())
@@ -40,7 +41,7 @@ namespace Cthangover.Tools.Services
"option", "select"
}) hl.KeywordColors[cmd] = cmdColor;
- var argColor = new Color(0.8f, 0.6f, 0.3f);
+ var argColor = UiTokens.Accent;
foreach (var kw in new[] {
"key", "first", "second", "hide_color", "wait", "speed", "hidden",
"sprites", "loop", "target", "quest_id", "show", "hide", "duration"