diff --git a/Yafc.Model/Model/Project.cs b/Yafc.Model/Model/Project.cs index 07eb46d2..24b7164f 100644 --- a/Yafc.Model/Model/Project.cs +++ b/Yafc.Model/Model/Project.cs @@ -33,9 +33,12 @@ public partial class Project : ModelObject { private int autosaveIndex; private const int AutosaveRollingLimit = 5; + public event Action? saveStateChanged; + public Project() : base(new UndoSystem()) { settings = new ProjectSettings(this); preferences = new ProjectPreferences(this); + base.undo.versionChanged += () => saveStateChanged?.Invoke(unsavedChangesCount > 0); } public event Action? metaInfoChanged; @@ -188,6 +191,8 @@ public void Save(string fileName) { attachedFileName = fileName; lastSavedVersion = projectVersion; + + saveStateChanged?.Invoke(false); } public void Save(Stream stream) { diff --git a/Yafc.Model/Serialization/UndoSystem.cs b/Yafc.Model/Serialization/UndoSystem.cs index 3aed6205..d1080ace 100644 --- a/Yafc.Model/Serialization/UndoSystem.cs +++ b/Yafc.Model/Serialization/UndoSystem.cs @@ -6,7 +6,17 @@ namespace Yafc.Model; public class UndoSystem { - public uint version { get; private set; } = 2; + + public uint version { + get => _version; + private set { + if (_version != value) { + _version = value; + versionChanged?.Invoke(); + } + } + } + public event Action? versionChanged; private bool undoBatchVisualOnly = true; private readonly List currentUndoBatch = []; private readonly List changedList = []; @@ -14,6 +24,8 @@ public class UndoSystem { private readonly Stack redo = new Stack(); private bool suspended; private bool scheduled; + private uint _version = 2; + internal void CreateUndoSnapshot(ModelObject target, bool visualOnly) { if (SerializationMap.IsDeserializing) { throw new InvalidOperationException("Do not record an undo event while deserializing."); @@ -82,13 +94,13 @@ public void Resume() { } public void PerformUndo() { - if (undo.Count == 0 || changedList.Count > 0) { - return; + if (CanUndo) { + redo.Push(undo.Pop().Restore(++version)); } - - redo.Push(undo.Pop().Restore(++version)); } + public bool CanUndo => !(undo.Count == 0 || changedList.Count > 0); + public void PerformRedo() { if (redo.Count == 0 || changedList.Count > 0) { return; diff --git a/Yafc.UI/Core/Window.cs b/Yafc.UI/Core/Window.cs index 882a0452..10082852 100644 --- a/Yafc.UI/Core/Window.cs +++ b/Yafc.UI/Core/Window.cs @@ -90,6 +90,8 @@ internal static int CalculateUnitsToPixels(int display) { return desiredUnitsToPixels; } + protected internal void SetWindowTitle(string value) => SDL.SDL_SetWindowTitle(window, value); + protected internal virtual void WindowResize() { rootGui.MarkEverythingForRebuild(); rootGui.Rebuild(); diff --git a/Yafc/Data/locale/en/yafc.cfg b/Yafc/Data/locale/en/yafc.cfg index 0f168d9e..1d2a8acd 100644 --- a/Yafc/Data/locale/en/yafc.cfg +++ b/Yafc/Data/locale/en/yafc.cfg @@ -247,6 +247,7 @@ menu-check-for-updates=Check for updates menu-about=About YAFC alert-unsaved-changes=You have __1__ unsaved changes alert-unsaved-changes-in-file=You have __1__ unsaved changes to __2__ +untitled-project=*Untitled query-save-changes=Save unsaved changes? dont-save=Don't save new-version-available=New version available! diff --git a/Yafc/Windows/MainScreen.cs b/Yafc/Windows/MainScreen.cs index 0911bc40..99e0ae61 100644 --- a/Yafc/Windows/MainScreen.cs +++ b/Yafc/Windows/MainScreen.cs @@ -72,6 +72,7 @@ public MainScreen(int display, Project project) : base(default, Preferences.Inst private void SetProject(Project project) { if (this.project != null) { this.project.metaInfoChanged -= ProjectOnMetaInfoChanged; + this.project.saveStateChanged -= ProjectSaveStateChanged; this.project.settings.changed -= ProjectSettingsChanged; } Project.current = project; @@ -90,7 +91,10 @@ private void SetProject(Project project) { SetActivePage(project.FindPage(project.displayPages[0])); project.metaInfoChanged += ProjectOnMetaInfoChanged; project.settings.changed += ProjectSettingsChanged; + project.saveStateChanged += ProjectSaveStateChanged; + _ = InputSystem.Instance.SetDefaultKeyboardFocus(this); + UpdateWindowTitle(project.unsavedChangesCount > 0); } private void ProjectSettingsChanged(bool visualOnly) { @@ -145,6 +149,10 @@ private void ProjectOnMetaInfoChanged() { } } + private void ProjectSaveStateChanged(bool unsavedChanges) { + UpdateWindowTitle(unsavedChanges); + } + private void ChangePage(ref ProjectPage? activePage, ProjectPage? newPage, ref ProjectPageView? activePageView, ProjectPageView? newPageView) { activePageView?.SetModel(null); activePage?.SetInactive(); @@ -247,7 +255,7 @@ private void BuildTabBar(ImGui gui) { using (gui.EnterRow()) { gui.spacing = 0f; if (gui.BuildButton(Icon.Menu)) { - gui.ShowDropDown(gui.lastRect, SettingsDropdown, new Padding(0f, 0f, 0f, 0.5f)); + gui.ShowDropDown(gui.lastRect, MainDropdown, new Padding(0f, 0f, 0f, 0.5f)); } if (gui.BuildButton(Icon.Plus).WithTooltip(gui, LSs.CreateProductionSheet.L(ImGuiUtils.ScanToString(SDL.SDL_Scancode.SDL_SCANCODE_T)))) { @@ -365,13 +373,13 @@ private void BuildSearch(ImGui gui) { searchBoxRect = gui.lastRect; } - private void SettingsDropdown(ImGui gui) { + private void MainDropdown(ImGui gui) { gui.boxColor = SchemeColor.Background; - if (gui.BuildContextMenuButton(LSs.Undo, LSs.ShortcutCtrlX.L(ImGuiUtils.ScanToString(SDL.SDL_Scancode.SDL_SCANCODE_Z))) && gui.CloseDropdown()) { + if (gui.BuildContextMenuButton(LSs.Undo, LSs.ShortcutCtrlX.L(ImGuiUtils.ScanToString(SDL.SDL_Scancode.SDL_SCANCODE_Z)), disabled: !project.undo.CanUndo) && gui.CloseDropdown()) { project.undo.PerformUndo(); } - if (gui.BuildContextMenuButton(LSs.Save, LSs.ShortcutCtrlX.L(ImGuiUtils.ScanToString(SDL.SDL_Scancode.SDL_SCANCODE_S))) && gui.CloseDropdown()) { + if (gui.BuildContextMenuButton(LSs.Save, LSs.ShortcutCtrlX.L(ImGuiUtils.ScanToString(SDL.SDL_Scancode.SDL_SCANCODE_S)), disabled: project.unsavedChangesCount == 0) && gui.CloseDropdown()) { SaveProject().CaptureException(); } @@ -388,7 +396,7 @@ private void SettingsDropdown(ImGui gui) { } if (gui.BuildContextMenuButton(LSs.ReturnToWelcomeScreen) && gui.CloseDropdown()) { - LoadProjectHeavy(); + ReturnToWelcomeScreen(); } BuildSubHeader(gui, LSs.MenuHeaderTools); @@ -479,6 +487,13 @@ public void ForceClose() { base.Close(); } + private void UpdateWindowTitle(bool unsavedChanges) { + var projectName = string.IsNullOrEmpty(project.attachedFileName) + ? LSs.UntitledProject + : (unsavedChanges ? "*" : string.Empty) + Path.GetFileNameWithoutExtension(project.attachedFileName); + SetWindowTitle($"{projectName} - {LSs.FullNameWithVersion.L(YafcLib.version.ToString(3))}"); + } + private async Task ConfirmUnsavedChanges() { string unsavedCount; if (string.IsNullOrEmpty(project.attachedFileName)) { @@ -648,6 +663,7 @@ private async Task SaveProjectAs() { FilesystemScreen.Mode.SelectOrCreateFile, LSs.DefaultFileName, this, null, "yafc", "Enter Project Name"); if (projectPath != null) { project.Save(projectPath); + UpdateWindowTitle(false); Preferences.Instance.AddProject(DataUtils.dataPath, DataUtils.modsPath, projectPath, DataUtils.expensiveRecipes, DataUtils.netProduction); return true; } @@ -694,7 +710,7 @@ private async void LoadProjectLight() { } } - private async void LoadProjectHeavy() { + private async void ReturnToWelcomeScreen() { if (project.unsavedChangesCount > 0 && !await ConfirmUnsavedChanges()) { return; } diff --git a/changelog.txt b/changelog.txt index e69f7668..5f706727 100644 --- a/changelog.txt +++ b/changelog.txt @@ -23,7 +23,12 @@ Version: Date: Features: - Improve early-startup error logging + - Extended main window title - append current project name (with * indicator for unsaved changes). + - Main dropdown - disable Save/Undo when there is nothing to do. Fixes: + Internal changes: + - Rename SettingsDropdown to MainDropdown. + - Rename LoadProjectHeavy to ReturnToWelcomeScreen. ---------------------------------------------------------------------------------------------------------------------- Version: 2.18.1 Date: April 21st 2026