Add autosave plugin
This commit is contained in:
parent
57551683b6
commit
a19a1fd19f
84
addons/autosaver_editor/AutoSaverEditorPlugin.cs
Normal file
84
addons/autosaver_editor/AutoSaverEditorPlugin.cs
Normal file
@ -0,0 +1,84 @@
|
||||
#if TOOLS
|
||||
using System.Text;
|
||||
using AutoSaverPlugin.UI;
|
||||
using AutoSaverPlugin.UI.GDComponent;
|
||||
using AutoSaverPlugin.Contracts;
|
||||
using AutoSaverPlugin.Shared;
|
||||
using Godot;
|
||||
using static AutoSaverPlugin.Shared.CommonUtils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
[Tool]
|
||||
public partial class AutoSaverEditorPlugin : EditorPlugin
|
||||
{
|
||||
private IAutoSaveManager _autoSaveManager;
|
||||
private IConfigurationManager _configManager;
|
||||
|
||||
private PanelSettingsControlNode _panelConfigNode;
|
||||
private AutoSaveToggleMenuBuilder _menuTopBuilder;
|
||||
private CheckButton _menuAutoSaveToggle;
|
||||
|
||||
public override string _GetPluginName() => _configManager.PluginFullName;
|
||||
|
||||
|
||||
public override Texture2D _GetPluginIcon()
|
||||
{
|
||||
return ResourceLoader.Load<Texture2D>(_configManager.PluginIConResourcePath);
|
||||
}
|
||||
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
InitializeDependencies();
|
||||
_autoSaveManager.Initialize(this);
|
||||
SetupAutoSaveToggle();
|
||||
SetupSettingsPanel();
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
DetachEvents();
|
||||
CleanupUI();
|
||||
}
|
||||
|
||||
private void InitializeDependencies()
|
||||
{
|
||||
ServiceProvider.Initialize();
|
||||
|
||||
_autoSaveManager = ServiceProvider.GetService<IAutoSaveManager>();
|
||||
_configManager = ServiceProvider.GetService<IConfigurationManager>();
|
||||
}
|
||||
|
||||
private void SetupSettingsPanel()
|
||||
{
|
||||
_panelConfigNode = new PanelSettingsControlNode();
|
||||
AddControlToDock(DockSlot.LeftUr, _panelConfigNode);
|
||||
}
|
||||
|
||||
private void SetupAutoSaveToggle()
|
||||
{
|
||||
_menuTopBuilder = new AutoSaveToggleMenuBuilder();
|
||||
_menuAutoSaveToggle = _menuTopBuilder.AutoSaveToggleButton;
|
||||
AddControlToContainer(CustomControlContainer.Toolbar, _menuAutoSaveToggle);
|
||||
_configManager.AutoSaverStateChanged += _menuTopBuilder.UpdateToggleStateFromSettings;
|
||||
}
|
||||
|
||||
private void DetachEvents()
|
||||
{
|
||||
_menuTopBuilder.DetachAutoSaveToggleEvents();
|
||||
_configManager.AutoSaverStateChanged -= _menuTopBuilder.UpdateToggleStateFromSettings;
|
||||
}
|
||||
|
||||
private void CleanupUI()
|
||||
{
|
||||
_autoSaveManager.Deactivate();
|
||||
|
||||
RemoveControlFromDocks(_panelConfigNode);
|
||||
_panelConfigNode?.QueueFree();
|
||||
|
||||
RemoveControlFromContainer(CustomControlContainer.Toolbar, _menuAutoSaveToggle);
|
||||
_menuAutoSaveToggle?.Free();
|
||||
}
|
||||
}
|
||||
#endif //TOOLS
|
||||
14
addons/autosaver_editor/Contracts/IAutoSaveManager.cs
Normal file
14
addons/autosaver_editor/Contracts/IAutoSaveManager.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Text;
|
||||
|
||||
namespace AutoSaverPlugin.Contracts;
|
||||
|
||||
internal interface IAutoSaveManager
|
||||
{
|
||||
void Initialize(AutoSaverEditorPlugin plugin);
|
||||
|
||||
void Activate();
|
||||
|
||||
void Reactivate();
|
||||
|
||||
void Deactivate();
|
||||
}
|
||||
47
addons/autosaver_editor/Contracts/IConfigurationManager.cs
Normal file
47
addons/autosaver_editor/Contracts/IConfigurationManager.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using AutoSaverPlugin.Shared;
|
||||
|
||||
namespace AutoSaverPlugin.Contracts;
|
||||
|
||||
internal interface IConfigurationManager
|
||||
{
|
||||
event Action<bool> AutoSaverStateChanged;
|
||||
|
||||
string PluginFullName { get; }
|
||||
string PluginShortName { get; }
|
||||
string PluginVersion { get; }
|
||||
|
||||
string PluginIConResourcePath { get; }
|
||||
VerboseLevel VerboseLevelSetting { get; }
|
||||
int AutoSaverIntervalSetting { get; }
|
||||
int PostponeTimeSetting { get; }
|
||||
int ActivityCheckWindowSetting { get; }
|
||||
bool IsOptionSaveScenesEnabled { get; }
|
||||
bool IsOptionSaveScriptsEnabled { get; }
|
||||
bool IsAutoSaverEnabled { get; }
|
||||
|
||||
bool UseGDEditorSaveOnFocusLoss { get; }
|
||||
bool UseGDEditorAutosaveIntervalSecs { get; }
|
||||
bool GDEditor_save_on_focus_loss { get; }
|
||||
int GDEditor_autosave_interval_secs { get; }
|
||||
bool HasGDEditorAutosaveEnabled { get; }
|
||||
|
||||
void SetEditorSaveOnFocusLoss(bool enabled);
|
||||
|
||||
void SetEditorAutosaveIntervalSecs(int seconds);
|
||||
|
||||
void LoadSettings();
|
||||
|
||||
void SaveSettings();
|
||||
|
||||
void SetAutoSaverEnabled(bool enabled, bool noEmitSignal = false);
|
||||
|
||||
void SetSaverInterval(int seconds);
|
||||
|
||||
void SetVerboseLevel(VerboseLevel level);
|
||||
|
||||
void SetSceneEnabled(bool enabled);
|
||||
|
||||
void SetScriptEnabled(bool enabled);
|
||||
}
|
||||
16
addons/autosaver_editor/Contracts/IStatusReporter.cs
Normal file
16
addons/autosaver_editor/Contracts/IStatusReporter.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace AutoSaverPlugin.Contracts;
|
||||
|
||||
internal interface IStatusReporter
|
||||
{
|
||||
List<string> FetchModifiedItems();
|
||||
}
|
||||
|
||||
internal interface ISceneStatusReporter : IStatusReporter
|
||||
{ }
|
||||
|
||||
internal interface IGDScriptStatusReporter : IStatusReporter
|
||||
{ }
|
||||
|
||||
15
addons/autosaver_editor/Contracts/ITimerService.cs
Normal file
15
addons/autosaver_editor/Contracts/ITimerService.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AutoSaverPlugin.Contracts;
|
||||
|
||||
public interface ITimerService
|
||||
{
|
||||
ITimerService AttachTo(AutoSaverEditorPlugin pluginCaller);
|
||||
ITimerService OnTimeout(Action onAutosaveTimerTimeout, bool oneShot = false);
|
||||
ITimerService Begin(float intervalSec);
|
||||
ITimerService End();
|
||||
}
|
||||
21
addons/autosaver_editor/LICENSE
Normal file
21
addons/autosaver_editor/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 vrravalos
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
BIN
addons/autosaver_editor/README.pdf
Normal file
BIN
addons/autosaver_editor/README.pdf
Normal file
Binary file not shown.
260
addons/autosaver_editor/Services/AutoSaveManager.cs
Normal file
260
addons/autosaver_editor/Services/AutoSaveManager.cs
Normal file
@ -0,0 +1,260 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AutoSaverPlugin.Contracts;
|
||||
using AutoSaverPlugin.Services.GDComponent;
|
||||
using AutoSaverPlugin.Shared;
|
||||
using Godot;
|
||||
using static AutoSaverPlugin.Shared.CommonUtils;
|
||||
|
||||
namespace AutoSaverPlugin.Services;
|
||||
|
||||
internal sealed class AutoSaveManager : IAutoSaveManager
|
||||
{
|
||||
private readonly EditorInterface _editorInterface = EditorInterface.Singleton;
|
||||
private ScriptEditor _scriptEditor => _editorInterface.GetScriptEditor();
|
||||
private readonly ISceneStatusReporter _sceneReporter;
|
||||
private readonly IGDScriptStatusReporter _gdScriptReporter;
|
||||
private readonly ITimerService _timerAutoSaver;
|
||||
private readonly ITimerService _timerActivityUserCheck;
|
||||
private readonly IConfigurationManager _configManager;
|
||||
private readonly ILoggerService _logger;
|
||||
private AutoSaverEditorPlugin _plugin;
|
||||
private UserActivityMonitorNode _activityMonitor;
|
||||
|
||||
public AutoSaveManager(ISceneStatusReporter sceneStatusReporter, IGDScriptStatusReporter scriptStatusReporter, IConfigurationManager configManager,
|
||||
ILoggerService loggerService, ITimerService timerAutoSaver, ITimerService timerActivity)
|
||||
{
|
||||
_sceneReporter = sceneStatusReporter ?? throw new ArgumentNullException(nameof(sceneStatusReporter));
|
||||
_gdScriptReporter = scriptStatusReporter ?? throw new ArgumentNullException(nameof(scriptStatusReporter));
|
||||
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
|
||||
_logger = loggerService ?? throw new ArgumentNullException(nameof(loggerService));
|
||||
_timerAutoSaver = timerAutoSaver ?? throw new ArgumentNullException(nameof(timerAutoSaver));
|
||||
_timerActivityUserCheck = timerActivity ?? throw new ArgumentNullException(nameof(timerActivity));
|
||||
}
|
||||
|
||||
public void Initialize(AutoSaverEditorPlugin plugin)
|
||||
{
|
||||
LoadingConfiguration();
|
||||
_plugin = plugin ?? throw new ArgumentNullException(nameof(plugin));
|
||||
Activate();
|
||||
}
|
||||
|
||||
private void LoadingConfiguration()
|
||||
{
|
||||
_configManager.LoadSettings();
|
||||
}
|
||||
|
||||
public void Activate() => SetupAutoSave(restart: false);
|
||||
|
||||
public void Reactivate() => SetupAutoSave(restart: true);
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
_logger.LogDiagnostic("Stopping autosaver service..");
|
||||
_timerAutoSaver.End();
|
||||
_timerActivityUserCheck.End();
|
||||
RemoveActivityMonitor();
|
||||
PrintStatus();
|
||||
}
|
||||
|
||||
private void SetupAutoSave(bool restart)
|
||||
{
|
||||
_logger.LogDiagnostic($"{(restart ? "Restarting" : "Initializing")} autosaver service..");
|
||||
_configManager.LoadSettings();
|
||||
SetTimers();
|
||||
ManageActivityMonitor(add: true);
|
||||
PrintStatus();
|
||||
}
|
||||
|
||||
private void SetTimers()
|
||||
{
|
||||
_logger.LogDebug("Setting timers..");
|
||||
|
||||
int intervalSec = _configManager.AutoSaverIntervalSetting;
|
||||
int timeToStartCheck = Math.Clamp(intervalSec - _configManager.ActivityCheckWindowSetting, 1, intervalSec);
|
||||
|
||||
_timerAutoSaver.End().AttachTo(_plugin).OnTimeout(OnAutosaveTimerTimeout);
|
||||
_timerActivityUserCheck.End().AttachTo(_plugin).OnTimeout(StartMonitoringUserActivity, oneShot: true);
|
||||
|
||||
if (_configManager.IsAutoSaverEnabled)
|
||||
{
|
||||
_timerAutoSaver.Begin(intervalSec);
|
||||
_timerActivityUserCheck.Begin(timeToStartCheck);
|
||||
}
|
||||
}
|
||||
|
||||
private void ManageActivityMonitor(bool add)
|
||||
{
|
||||
if (add)
|
||||
{
|
||||
_activityMonitor = new UserActivityMonitorNode();
|
||||
_activityMonitor.UserActivityDetected += OnUserActivityDetected;
|
||||
_plugin.AddChild(_activityMonitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_activityMonitor != null)
|
||||
{
|
||||
_activityMonitor.UserActivityDetected -= OnUserActivityDetected;
|
||||
|
||||
if (_activityMonitor.IsInsideTree())
|
||||
_plugin?.RemoveChild(_activityMonitor);
|
||||
|
||||
if (!_activityMonitor.IsQueuedForDeletion())
|
||||
_activityMonitor.QueueFree();
|
||||
|
||||
_activityMonitor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveActivityMonitor() => ManageActivityMonitor(add: false);
|
||||
|
||||
private void OnUserActivityDetected(string eventName, float lastActivityTimeSec) =>
|
||||
_logger.LogDebug($"User activity detected: {eventName} at last activity time: {lastActivityTimeSec}ms");
|
||||
|
||||
private void StartMonitoringUserActivity()
|
||||
{
|
||||
_logger.LogDebug($"Starting user activity monitoring..@{GetCurrentTimestamp()}");
|
||||
_activityMonitor.StartMonitoring();
|
||||
}
|
||||
|
||||
private void OnAutosaveTimerTimeout()
|
||||
{
|
||||
_logger.LogDiagnostic($"Running autosaver @{GetCurrentTimestamp()}");
|
||||
PerformAutoSaveIfNeeded();
|
||||
}
|
||||
|
||||
private void PerformAutoSaveIfNeeded()
|
||||
{
|
||||
const float inTheLastMilliSecs = 500f; // 0.5 sec
|
||||
if (!_activityMonitor.IsMonitoring || _activityMonitor.NoActivityTriggered(thresholdMillisec: inTheLastMilliSecs))
|
||||
{
|
||||
PerformAutoSave();
|
||||
}
|
||||
else
|
||||
{
|
||||
PostponeAutoSave();
|
||||
}
|
||||
}
|
||||
|
||||
private void PerformAutoSave()
|
||||
{
|
||||
_activityMonitor.StopMonitoring();
|
||||
|
||||
var modifiedScenes = _configManager.IsOptionSaveScenesEnabled ? GetModifiedItems(_sceneReporter.FetchModifiedItems()) : new List<string>();
|
||||
var modifiedScripts = _configManager.IsOptionSaveScriptsEnabled ? GetModifiedItems(_gdScriptReporter.FetchModifiedItems()) : new List<string>();
|
||||
|
||||
if (modifiedScenes.Count == 0 && modifiedScripts.Count == 0)
|
||||
{
|
||||
_logger.LogDiagnostic("No modified items. Skipping autosave.");
|
||||
return;
|
||||
}
|
||||
|
||||
// save all scenes (also save all scripts)
|
||||
bool savedAll = SaveScenes(modifiedScenes);
|
||||
|
||||
if (!savedAll && !_configManager.HasGDEditorAutosaveEnabled)
|
||||
SaveFiles(modifiedScripts);
|
||||
|
||||
LogAutosaveResult(modifiedScenes.Count + modifiedScripts.Count, modifiedScenes.Concat(modifiedScripts).ToList());
|
||||
SetTimers(); // Restart timers
|
||||
}
|
||||
|
||||
private bool SaveScenes(List<string> modifiedScenes)
|
||||
{
|
||||
List<string> savedFiles = new();
|
||||
var openScenes = _editorInterface.GetOpenScenes();
|
||||
var editedScene = _editorInterface.GetEditedSceneRoot();
|
||||
var fnSceneRoot = Path.GetFileName(editedScene.SceneFilePath).Split('.')[0];
|
||||
bool saveAllAtOnce = false;
|
||||
int numFilesSaved = 0;
|
||||
|
||||
foreach (string scenePath in openScenes)
|
||||
{
|
||||
var fileNameScenePath = Path.GetFileName(scenePath).Split('.')[0];
|
||||
if (modifiedScenes.Contains(fileNameScenePath))
|
||||
{
|
||||
numFilesSaved++;
|
||||
savedFiles.Add(scenePath);
|
||||
saveAllAtOnce = saveAllAtOnce || fnSceneRoot != fileNameScenePath;
|
||||
}
|
||||
}
|
||||
|
||||
if (saveAllAtOnce)
|
||||
{
|
||||
_editorInterface.SaveAllScenes();
|
||||
}
|
||||
else if (numFilesSaved == 1)
|
||||
{
|
||||
var err = _editorInterface.SaveScene();
|
||||
if (err != Error.Ok)
|
||||
{
|
||||
_logger.LogError($"Failed to autoSave scene: {fnSceneRoot}. Error: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
return saveAllAtOnce;
|
||||
}
|
||||
|
||||
private void SaveFiles(List<string> modifiedFiles)
|
||||
{
|
||||
if (modifiedFiles.Count > 0)
|
||||
_editorInterface.SaveAllScenes();
|
||||
}
|
||||
|
||||
private void PostponeAutoSave()
|
||||
{
|
||||
_logger.LogDebug($"Postponing autoSave for {_configManager.PostponeTimeSetting}sec..");
|
||||
_timerAutoSaver.End().OnTimeout(OnAutosaveTimerTimeout).Begin(_configManager.PostponeTimeSetting);
|
||||
}
|
||||
|
||||
private static List<string> GetModifiedItems(List<string> items)
|
||||
{
|
||||
var modifiedScripts = new List<string>();
|
||||
|
||||
foreach (var i in items)
|
||||
{
|
||||
if (i.Contains("*"))
|
||||
{
|
||||
modifiedScripts.Add(i.Replace("(*)", ""));
|
||||
}
|
||||
}
|
||||
|
||||
return modifiedScripts;
|
||||
}
|
||||
|
||||
private void LogAutosaveResult(int numFilesSaved, List<string> savedFiles)
|
||||
{
|
||||
string currentTimestamp = GetCurrentTimestamp();
|
||||
if (numFilesSaved > 0)
|
||||
{
|
||||
_logger.LogInfo($"Autosave executed at {currentTimestamp}. {numFilesSaved} file(s) saved:");
|
||||
foreach (var file in savedFiles)
|
||||
{
|
||||
_logger.LogInfo($"- {file}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDiagnostic($"Autosave completed at {currentTimestamp}: No files saved.");
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintStatus()
|
||||
{
|
||||
string timestamp = GetCurrentTimestamp();
|
||||
string pluginSetTimestamp = $"Plugin set @{timestamp}.";
|
||||
string autosaveScene = $"scenes ({(_configManager.IsOptionSaveScenesEnabled ? "ON" : "OFF")})";
|
||||
string autosaveScript = $"GDScript files ({(_configManager.IsOptionSaveScriptsEnabled ? "ON" : "OFF")})";
|
||||
string verboseLevelMessage = $"Verbose level: {_configManager.VerboseLevelSetting}.";
|
||||
string editorMessage = $"[Editor] Autosave Interval: {_configManager.GDEditor_autosave_interval_secs}sec, [Editor] Save on focus loss: {_configManager.GDEditor_save_on_focus_loss}";
|
||||
|
||||
string statusAutosaving = _configManager.IsAutoSaverEnabled ? "Autosaving every {_autoSaveConfig.AutoSaverIntervalSetting} seconds: {autosaveScene} and {autosaveScript}." : "Autosaving disabled.";
|
||||
|
||||
_logger.LogInfo($"{pluginSetTimestamp} {statusAutosaving}");
|
||||
_logger.LogDiagnostic($"{verboseLevelMessage} {editorMessage}");
|
||||
}
|
||||
}
|
||||
252
addons/autosaver_editor/Services/ConfigurationManager.cs
Normal file
252
addons/autosaver_editor/Services/ConfigurationManager.cs
Normal file
@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using AutoSaverPlugin.Contracts;
|
||||
using AutoSaverPlugin.Shared;
|
||||
using Godot;
|
||||
|
||||
namespace AutoSaverPlugin.Services;
|
||||
|
||||
public sealed class ConfigurationManager : IConfigurationManager
|
||||
{
|
||||
// default autosaver settings
|
||||
private const int AS_AUTOSAVER_INTERVAL = 60;
|
||||
|
||||
private const VerboseLevel AS_VERBOSE = VerboseLevel.OFF;
|
||||
private const int AS_POSTPONE_TIME = 5;
|
||||
private const int AS_ACTIVITY_CHECK_WINDOW = 1;
|
||||
private const bool AS_AUTOSAVE_SCENE = true;
|
||||
private const bool AS_AUTOSAVE_GDSCRIPT = true;
|
||||
|
||||
// default Godot editor settings
|
||||
private const bool GDEDITOR_SAVE_ON_FOCUS_LOSS_DEFAULT = false;
|
||||
|
||||
private const int GDEDITOR_AUTOSAVE_INTERVAL_SECS_DEFAULT = 0;
|
||||
|
||||
// defines the use or not of Godot editor settings
|
||||
private const bool USE_GDEDITOR_SAVE_ON_FOCUS_LOSS = false;
|
||||
|
||||
private const bool USE_GDEDITOR_AUTOSAVE_INTERVAL_SECS = true;
|
||||
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly ConfigFile _configFile = new ConfigFile();
|
||||
private readonly string _configFilePath;
|
||||
private EditorSettings _gdEditorSettings => EditorInterface.Singleton.GetEditorSettings();
|
||||
|
||||
private VerboseLevel _verboseLevel = AS_VERBOSE;
|
||||
|
||||
public VerboseLevel VerboseLevelSetting
|
||||
{
|
||||
get => _verboseLevel;
|
||||
private set
|
||||
{
|
||||
_verboseLevel = value;
|
||||
_logger?.SetOutput(value);
|
||||
}
|
||||
}
|
||||
|
||||
public int AutoSaverIntervalSetting { get; private set; } = AS_AUTOSAVER_INTERVAL;
|
||||
public int PostponeTimeSetting { get; private set; } = AS_POSTPONE_TIME;
|
||||
public int ActivityCheckWindowSetting { get; private set; } = AS_ACTIVITY_CHECK_WINDOW;
|
||||
public bool IsOptionSaveScenesEnabled { get; private set; } = AS_AUTOSAVE_SCENE;
|
||||
public bool IsOptionSaveScriptsEnabled { get; private set; } = AS_AUTOSAVE_GDSCRIPT;
|
||||
public bool IsAutoSaverEnabled { get; private set; } = true;
|
||||
public bool GDEditor_save_on_focus_loss { get; private set; } = GDEDITOR_SAVE_ON_FOCUS_LOSS_DEFAULT;
|
||||
public int GDEditor_autosave_interval_secs { get; private set; } = GDEDITOR_AUTOSAVE_INTERVAL_SECS_DEFAULT;
|
||||
|
||||
public string PluginFullName => PluginInfo.FullName;
|
||||
public string PluginShortName => PluginInfo.NameShort;
|
||||
public string PluginVersion { get; } = CommonUtils.GetPluginVersion();
|
||||
|
||||
public string PluginIConResourcePath => PluginInfo.BaseResourcePath + PluginInfo.PluginIcon;
|
||||
|
||||
public bool HasGDEditorAutosaveEnabled => GDEditor_autosave_interval_secs > 0;
|
||||
|
||||
public bool UseGDEditorSaveOnFocusLoss { get; private set; } = USE_GDEDITOR_SAVE_ON_FOCUS_LOSS;
|
||||
|
||||
public bool UseGDEditorAutosaveIntervalSecs { get; private set; } = USE_GDEDITOR_AUTOSAVE_INTERVAL_SECS;
|
||||
|
||||
public event Action<bool> AutoSaverStateChanged;
|
||||
|
||||
public ConfigurationManager(ILoggerService logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_configFilePath = DetermineConfigFilePath();
|
||||
}
|
||||
|
||||
public void SetSaverInterval(int seconds) => AutoSaverIntervalSetting = seconds;
|
||||
|
||||
public void SetVerboseLevel(VerboseLevel level)
|
||||
{
|
||||
VerboseLevelSetting = level;
|
||||
}
|
||||
|
||||
public void SetEditorSaveOnFocusLoss(bool enabled)
|
||||
{
|
||||
GDEditor_save_on_focus_loss = enabled;
|
||||
_gdEditorSettings.SetSetting("interface/editor/save_on_focus_loss", enabled);
|
||||
}
|
||||
|
||||
public void SetEditorAutosaveIntervalSecs(int seconds)
|
||||
{
|
||||
GDEditor_autosave_interval_secs = seconds;
|
||||
_gdEditorSettings.SetSetting("text_editor/behavior/files/autosave_interval_secs", seconds);
|
||||
}
|
||||
|
||||
public void SetSceneEnabled(bool enabled) => IsOptionSaveScenesEnabled = enabled;
|
||||
|
||||
public void SetScriptEnabled(bool enabled) => IsOptionSaveScriptsEnabled = enabled;
|
||||
|
||||
public void LoadSettings()
|
||||
{
|
||||
LoadFromGodotEditorSettings();
|
||||
|
||||
LoadFromConfigFile();
|
||||
|
||||
SyncGodotEditorSettings();
|
||||
}
|
||||
|
||||
public void ResetSettings()
|
||||
{
|
||||
ResetGodotEditorSettings();
|
||||
|
||||
AutoSaverIntervalSetting = AS_AUTOSAVER_INTERVAL;
|
||||
VerboseLevelSetting = AS_VERBOSE;
|
||||
IsAutoSaverEnabled = true;
|
||||
PostponeTimeSetting = AS_POSTPONE_TIME;
|
||||
ActivityCheckWindowSetting = AS_ACTIVITY_CHECK_WINDOW;
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
SaveToConfigFile();
|
||||
|
||||
if (IsAutoSaverEnabled)
|
||||
{
|
||||
SyncGodotEditorSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetGodotEditorSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncGodotEditorSettings()
|
||||
{
|
||||
_logger.LogDiagnostic($"Syncing Godot editor settings.. UseGDEditorAutosaveIntervalSecs={UseGDEditorAutosaveIntervalSecs}:{AutoSaverIntervalSetting}, SetEditorSaveOnFocusLoss={UseGDEditorSaveOnFocusLoss}");
|
||||
|
||||
// update Godot editor settings from config file
|
||||
if (UseGDEditorAutosaveIntervalSecs)
|
||||
SetEditorAutosaveIntervalSecs(seconds: AutoSaverIntervalSetting);
|
||||
|
||||
if (UseGDEditorSaveOnFocusLoss)
|
||||
{
|
||||
SetEditorSaveOnFocusLoss(enabled: IsAutoSaverEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetGodotEditorSettings()
|
||||
{
|
||||
_logger.LogDiagnostic($"Resetting Godot editor settings.. UseGDEditorAutosaveIntervalSecs={UseGDEditorAutosaveIntervalSecs}, SetEditorSaveOnFocusLoss={UseGDEditorSaveOnFocusLoss}");
|
||||
|
||||
// update Godot editor settings from default values
|
||||
if (UseGDEditorAutosaveIntervalSecs)
|
||||
SetEditorAutosaveIntervalSecs(seconds: GDEDITOR_AUTOSAVE_INTERVAL_SECS_DEFAULT);
|
||||
|
||||
if (UseGDEditorSaveOnFocusLoss)
|
||||
SetEditorSaveOnFocusLoss(enabled: GDEDITOR_SAVE_ON_FOCUS_LOSS_DEFAULT);
|
||||
}
|
||||
|
||||
private void LoadFromGodotEditorSettings()
|
||||
{
|
||||
GDEditor_autosave_interval_secs = (int)_gdEditorSettings.GetSetting("text_editor/behavior/files/autosave_interval_secs");
|
||||
GDEditor_save_on_focus_loss = (bool)_gdEditorSettings.GetSetting("interface/editor/save_on_focus_loss");
|
||||
}
|
||||
|
||||
public void SetAutoSaverEnabled(bool enabled, bool noEmitSignal = false)
|
||||
{
|
||||
IsAutoSaverEnabled = enabled;
|
||||
|
||||
SaveSettings();
|
||||
|
||||
if (!noEmitSignal)
|
||||
{
|
||||
AutoSaverStateChanged?.Invoke(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
private string DetermineConfigFilePath()
|
||||
{
|
||||
var debugConfigPath = FindConfigFile(".debug.ini");
|
||||
if (!string.IsNullOrEmpty(debugConfigPath))
|
||||
{
|
||||
_logger.LogDiagnostic($"Using debug config file: {debugConfigPath}");
|
||||
return debugConfigPath;
|
||||
}
|
||||
|
||||
var standardConfigPath = FindConfigFile(".ini");
|
||||
if (!string.IsNullOrEmpty(standardConfigPath))
|
||||
{
|
||||
_logger.LogDiagnostic($"Using standard config file: {standardConfigPath}");
|
||||
return standardConfigPath;
|
||||
}
|
||||
|
||||
_logger.LogDiagnostic("Config file not found, using project settings.");
|
||||
return null;
|
||||
}
|
||||
|
||||
private string FindConfigFile(string extension)
|
||||
{
|
||||
var fileName = PluginInfo.SettingsFileName.Replace(".ini", extension);
|
||||
return CommonUtils.GetAllProjectFiles(extension).Find(f => f.Contains(fileName));
|
||||
}
|
||||
|
||||
private bool LoadFromConfigFile()
|
||||
{
|
||||
_logger.LogDiagnostic($"Loading config file: {_configFilePath}");
|
||||
Error error = _configFile.Load(_configFilePath);
|
||||
if (error != Error.Ok)
|
||||
{
|
||||
_logger.LogError($"Failed to load config file: {_configFilePath}, error: {error}");
|
||||
ResetSettings();
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoSaverIntervalSetting = (int)_configFile.GetValue(PluginInfo.RootSettings, PluginInfo.KeyIntervalSec, AS_AUTOSAVER_INTERVAL);
|
||||
|
||||
VerboseLevelSetting = Enum.TryParse((string)_configFile.GetValue(PluginInfo.RootSettings, PluginInfo.KeyVerbose, AS_VERBOSE.ToString()),
|
||||
out VerboseLevel result)
|
||||
? result
|
||||
: AS_VERBOSE;
|
||||
|
||||
IsAutoSaverEnabled = (bool)_configFile.GetValue(PluginInfo.RootSettings, PluginInfo.KeyEnabled, true);
|
||||
PostponeTimeSetting = (int)_configFile.GetValue(PluginInfo.RootSettings, PluginInfo.KeyPostponeTimeSec, AS_POSTPONE_TIME);
|
||||
ActivityCheckWindowSetting = (int)_configFile.GetValue(PluginInfo.RootSettings, PluginInfo.KeyActivityCheckWindowSec, AS_ACTIVITY_CHECK_WINDOW);
|
||||
IsOptionSaveScenesEnabled = (bool)_configFile.GetValue(PluginInfo.RootSettings, PluginInfo.KeyAutosaveScene, AS_AUTOSAVE_SCENE);
|
||||
IsOptionSaveScriptsEnabled = (bool)_configFile.GetValue(PluginInfo.RootSettings, PluginInfo.KeyAutosaveGDScript, AS_AUTOSAVE_GDSCRIPT);
|
||||
UseGDEditorAutosaveIntervalSecs = (bool)_configFile.GetValue(PluginInfo.RootSettings, PluginInfo.KeyUseGDEditorAutosaveInterval, USE_GDEDITOR_AUTOSAVE_INTERVAL_SECS);
|
||||
UseGDEditorSaveOnFocusLoss = (bool)_configFile.GetValue(PluginInfo.RootSettings, PluginInfo.KeyUseGDEditorSaveOnFocusLoss, USE_GDEDITOR_SAVE_ON_FOCUS_LOSS);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SaveToConfigFile()
|
||||
{
|
||||
_logger.LogDiagnostic($"Saving settings to config file: {_configFilePath}");
|
||||
_configFile.SetValue(PluginInfo.RootSettings, PluginInfo.KeyIntervalSec, AutoSaverIntervalSetting);
|
||||
_configFile.SetValue(PluginInfo.RootSettings, PluginInfo.KeyVerbose, VerboseLevelSetting.ToString());
|
||||
_configFile.SetValue(PluginInfo.RootSettings, PluginInfo.KeyEnabled, IsAutoSaverEnabled);
|
||||
_configFile.SetValue(PluginInfo.RootSettings, PluginInfo.KeyPostponeTimeSec, PostponeTimeSetting);
|
||||
_configFile.SetValue(PluginInfo.RootSettings, PluginInfo.KeyActivityCheckWindowSec, ActivityCheckWindowSetting);
|
||||
_configFile.SetValue(PluginInfo.RootSettings, PluginInfo.KeyAutosaveScene, IsOptionSaveScenesEnabled);
|
||||
_configFile.SetValue(PluginInfo.RootSettings, PluginInfo.KeyAutosaveGDScript, IsOptionSaveScriptsEnabled);
|
||||
_configFile.SetValue(PluginInfo.RootSettings, PluginInfo.KeyUseGDEditorAutosaveInterval, UseGDEditorAutosaveIntervalSecs);
|
||||
_configFile.SetValue(PluginInfo.RootSettings, PluginInfo.KeyUseGDEditorSaveOnFocusLoss, UseGDEditorSaveOnFocusLoss);
|
||||
|
||||
var error = _configFile.Save(_configFilePath);
|
||||
if (error != Error.Ok)
|
||||
{
|
||||
_logger.LogError($"Failed to save config file: {_configFilePath}, error: {error}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using AutoSaverPlugin.Shared;
|
||||
using Godot;
|
||||
|
||||
namespace AutoSaverPlugin.Services.GDComponent;
|
||||
|
||||
internal partial class UserActivityMonitorNode : Node
|
||||
{
|
||||
public event Action<string, float> UserActivityDetected;
|
||||
|
||||
private bool _isMonitoring = false;
|
||||
private DateTime? _lastActivityTimeUtc;
|
||||
private readonly ILoggerService _logger = ServiceProvider.GetService<ILoggerService>();
|
||||
|
||||
public bool IsMonitoring => _isMonitoring;
|
||||
public bool AnyUserActivityDetected => _lastActivityTimeUtc.HasValue;
|
||||
|
||||
public UserActivityMonitorNode()
|
||||
{
|
||||
}
|
||||
|
||||
public void StartMonitoring() => _isMonitoring = true;
|
||||
|
||||
public void StopMonitoring() => _isMonitoring = false;
|
||||
|
||||
public override void _Input(InputEvent @event)
|
||||
{
|
||||
if (!_isMonitoring || !(@event is InputEventMouseMotion || @event is InputEventKey)) return;
|
||||
|
||||
_lastActivityTimeUtc = DateTime.UtcNow;
|
||||
|
||||
string eventType = @event is InputEventMouseMotion ? "MouseMotion" : "Key";
|
||||
_logger.LogDebug($"{nameof(UserActivityMonitorNode)}: User activity detected: {eventType}, LastDetectedActivityTimeInMillisec = {LastDetectedActivityTimeInMillisec()}");
|
||||
|
||||
UserActivityDetected?.Invoke(eventType, LastDetectedActivityTimeInMillisec());
|
||||
}
|
||||
|
||||
internal float LastDetectedActivityTimeInMillisec()
|
||||
{
|
||||
return _lastActivityTimeUtc.HasValue
|
||||
? (float)((DateTime.UtcNow - _lastActivityTimeUtc.Value).TotalMilliseconds)
|
||||
: 0;
|
||||
}
|
||||
|
||||
public bool NoActivityTriggered(float thresholdMillisec)
|
||||
{
|
||||
_logger.LogDebug($"{nameof(UserActivityMonitorNode)}: Checking for no activity in the last {thresholdMillisec}ms. Last activity was {LastDetectedActivityTimeInMillisec()}ms ago.");
|
||||
|
||||
return LastDetectedActivityTimeInMillisec() > thresholdMillisec;
|
||||
}
|
||||
}
|
||||
62
addons/autosaver_editor/Services/GDScriptStatusReporter.cs
Normal file
62
addons/autosaver_editor/Services/GDScriptStatusReporter.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoSaverPlugin.Contracts;
|
||||
using AutoSaverPlugin.Shared;
|
||||
using Godot;
|
||||
|
||||
namespace AutoSaverPlugin.Services
|
||||
{
|
||||
internal sealed class GDScriptStatusReporter : IGDScriptStatusReporter
|
||||
{
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
public GDScriptStatusReporter(ILoggerService loggerService)
|
||||
{
|
||||
_logger = loggerService ?? throw new ArgumentNullException(nameof(loggerService));
|
||||
}
|
||||
|
||||
public List<string> FetchModifiedItems()
|
||||
{
|
||||
_logger.LogDebug("Fetching modified scripts...");
|
||||
ScriptEditor editor = EditorInterface.Singleton.GetScriptEditor();
|
||||
|
||||
List<string> listItemText = new();
|
||||
|
||||
ItemList itemList = FindItemList(editor);
|
||||
|
||||
if (itemList == null)
|
||||
{
|
||||
return listItemText;
|
||||
}
|
||||
|
||||
for (int i = 0; i < itemList.ItemCount; i++)
|
||||
{
|
||||
var item = itemList.GetItemText(i);
|
||||
_logger.LogDiagnostic($"Script file[{i}]: {item}");
|
||||
listItemText.Add(item);
|
||||
}
|
||||
|
||||
return listItemText;
|
||||
}
|
||||
|
||||
private ItemList FindItemList(Node root)
|
||||
{
|
||||
if (root is ItemList itemList)
|
||||
{
|
||||
return itemList;
|
||||
}
|
||||
|
||||
foreach (Node child in root.GetChildren())
|
||||
{
|
||||
var result = FindItemList(child);
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
addons/autosaver_editor/Services/SceneTabStatusReporter.cs
Normal file
60
addons/autosaver_editor/Services/SceneTabStatusReporter.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AutoSaverPlugin.Contracts;
|
||||
using AutoSaverPlugin.Shared;
|
||||
using Godot;
|
||||
|
||||
namespace AutoSaverPlugin.Services
|
||||
{
|
||||
internal sealed class SceneTabStatusReporter : ISceneStatusReporter
|
||||
{
|
||||
private readonly EditorInterface _editorInterface = EditorInterface.Singleton;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
public SceneTabStatusReporter(ILoggerService loggerService)
|
||||
{
|
||||
_logger = loggerService ?? throw new ArgumentNullException(nameof(loggerService));
|
||||
}
|
||||
|
||||
public List<string> FetchModifiedItems()
|
||||
{
|
||||
_logger.LogDebug("Fetching modified scenes...");
|
||||
var tabTitles = new List<string>();
|
||||
var tabBar = FindTabBar(_editorInterface.GetBaseControl());
|
||||
|
||||
if (tabBar == null)
|
||||
{
|
||||
_logger.LogError("Scene tab bar not found.");
|
||||
return tabTitles;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tabBar.TabCount; i++)
|
||||
{
|
||||
var title = tabBar.GetTabTitle(i);
|
||||
_logger.LogDiagnostic($"Scene tab[{i}]: {title}");
|
||||
tabTitles.Add(title);
|
||||
}
|
||||
|
||||
return tabTitles;
|
||||
}
|
||||
|
||||
private TabBar FindTabBar(Node root)
|
||||
{
|
||||
if (root is TabBar tabBar)
|
||||
{
|
||||
return tabBar;
|
||||
}
|
||||
|
||||
foreach (Node child in root.GetChildren())
|
||||
{
|
||||
var result = FindTabBar(child);
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
91
addons/autosaver_editor/Services/TimerService.cs
Normal file
91
addons/autosaver_editor/Services/TimerService.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using AutoSaverPlugin.Contracts;
|
||||
using AutoSaverPlugin.Shared;
|
||||
using Godot;
|
||||
|
||||
namespace AutoSaverPlugin.Services
|
||||
{
|
||||
internal sealed class TimerService : ITimerService
|
||||
{
|
||||
private Timer _timer;
|
||||
private AutoSaverEditorPlugin _plugin;
|
||||
private Action _timeoutAction;
|
||||
private ILoggerService _logger;
|
||||
|
||||
public TimerService(ILoggerService loggerService)
|
||||
{
|
||||
_logger = loggerService ?? throw new ArgumentNullException(nameof(loggerService));
|
||||
}
|
||||
|
||||
public ITimerService AttachTo(AutoSaverEditorPlugin plugin)
|
||||
{
|
||||
_plugin = plugin ?? throw new ArgumentNullException(nameof(plugin));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ITimerService OnTimeout(Action action, bool oneShot = false)
|
||||
{
|
||||
_timeoutAction = action ?? throw new ArgumentNullException(nameof(action));
|
||||
SetupTimer(oneShot);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ITimerService Begin(float intervalSeconds)
|
||||
{
|
||||
if (_timer == null)
|
||||
{
|
||||
_logger.LogError("Timer is not initialized. Call OnTimeout first.");
|
||||
return this;
|
||||
}
|
||||
|
||||
_timer.WaitTime = intervalSeconds;
|
||||
|
||||
if (!_timer.IsInsideTree())
|
||||
{
|
||||
_plugin.AddChild(_timer);
|
||||
}
|
||||
|
||||
_timer.Start();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ITimerService End()
|
||||
{
|
||||
DisposeTimer();
|
||||
return this;
|
||||
}
|
||||
|
||||
private void SetupTimer(bool oneShot)
|
||||
{
|
||||
DisposeTimer();
|
||||
|
||||
_timer = new Timer
|
||||
{
|
||||
OneShot = oneShot
|
||||
};
|
||||
|
||||
_timer.Timeout += OnTimerTimeout;
|
||||
}
|
||||
|
||||
private void OnTimerTimeout()
|
||||
{
|
||||
_timeoutAction?.Invoke();
|
||||
}
|
||||
|
||||
private void DisposeTimer()
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer.Stop();
|
||||
if (_timer.IsInsideTree())
|
||||
{
|
||||
_plugin.RemoveChild(_timer);
|
||||
}
|
||||
|
||||
_timer.Timeout -= OnTimerTimeout;
|
||||
_timer.QueueFree();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
addons/autosaver_editor/Shared/CommonUtils.cs
Normal file
42
addons/autosaver_editor/Shared/CommonUtils.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace AutoSaverPlugin.Shared
|
||||
{
|
||||
internal static class CommonUtils
|
||||
{
|
||||
internal static string GetCurrentTimestamp() => DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
internal static string GetPluginVersion() => $"{PluginInfo.PluginVersion}";
|
||||
|
||||
internal static List<string> GetAllProjectFiles(string extFileFilter = null)
|
||||
{
|
||||
List<string> files = new List<string>();
|
||||
EditorInterface editorInterface = EditorInterface.Singleton;
|
||||
EditorFileSystem fileSystem = editorInterface.GetResourceFilesystem();
|
||||
|
||||
fileSystem.Scan();
|
||||
GetFilesRecursive(fileSystem.GetFilesystem(), files, extFileFilter);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private static void GetFilesRecursive(EditorFileSystemDirectory directory, List<string> files, string extensionFilter = null)
|
||||
{
|
||||
for (int i = 0; i < directory.GetFileCount(); i++)
|
||||
{
|
||||
string filePath = directory.GetFilePath(i);
|
||||
if (string.IsNullOrEmpty(extensionFilter) || filePath.EndsWith(extensionFilter))
|
||||
{
|
||||
files.Add(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < directory.GetSubdirCount(); i++)
|
||||
{
|
||||
GetFilesRecursive(directory.GetSubdir(i), files, extensionFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
addons/autosaver_editor/Shared/Logger.cs
Normal file
84
addons/autosaver_editor/Shared/Logger.cs
Normal file
@ -0,0 +1,84 @@
|
||||
using Godot;
|
||||
|
||||
namespace AutoSaverPlugin.Shared;
|
||||
|
||||
public enum VerboseLevel
|
||||
{
|
||||
OFF = 0,
|
||||
MIN = 1,
|
||||
MAX = 2,
|
||||
SECRET = 3
|
||||
}
|
||||
|
||||
public enum LogType
|
||||
{
|
||||
MINOR = 0,
|
||||
MAJOR = 1,
|
||||
WARN_ERR = 2,
|
||||
DEBUG = 3
|
||||
}
|
||||
|
||||
public interface ILoggerService
|
||||
{
|
||||
bool IsLogInfoEnable { get; }
|
||||
void SetOutput(VerboseLevel verboseLevel);
|
||||
void Log(string message, LogType logLevel);
|
||||
void LogDiagnostic(string message);
|
||||
void LogInfo(string message);
|
||||
void LogError(string message);
|
||||
void LogDebug(string message);
|
||||
}
|
||||
|
||||
internal sealed class Logger : ILoggerService
|
||||
{
|
||||
private VerboseLevel _configuredVerboseLevel = VerboseLevel.OFF;
|
||||
|
||||
public VerboseLevel VerboseLevel => _configuredVerboseLevel;
|
||||
|
||||
public bool IsLogInfoEnable => _configuredVerboseLevel >= VerboseLevel.MIN;
|
||||
|
||||
public Logger() { }
|
||||
|
||||
public void SetOutput(VerboseLevel verboseLevel)
|
||||
{
|
||||
_configuredVerboseLevel = verboseLevel;
|
||||
}
|
||||
|
||||
// +----------------------+---------------+---------------------+---------------------+
|
||||
// | LogType\VerboseLevel | MIN | MAX | SECRET |
|
||||
// +----------------------+---------------+---------------------+---------------------+
|
||||
// | DEBUG | - | - | GD.Print("[DEBUG]") |
|
||||
// | MINOR | - | GD.Print("[INFO]") | GD.Print("[INFO]") |
|
||||
// | MAJOR | GD.Print() | GD.Print() | GD.Print() |
|
||||
// | ERROR | GD.PrintErr() | GD.PrintErr() | GD.PrintErr() |
|
||||
// +----------------------+---------------+---------------------+---------------------+
|
||||
|
||||
public void Log(string message, LogType logType)
|
||||
{
|
||||
if (_configuredVerboseLevel == VerboseLevel.OFF) return;
|
||||
|
||||
string prefix = $"[{PluginInfo.NameShort}]";
|
||||
switch (logType)
|
||||
{
|
||||
case LogType.DEBUG:
|
||||
if (_configuredVerboseLevel == VerboseLevel.SECRET)
|
||||
GD.Print($"{prefix}[DEBUG] {message}");
|
||||
break;
|
||||
case LogType.MINOR:
|
||||
if (_configuredVerboseLevel >= VerboseLevel.MAX)
|
||||
GD.Print($"{prefix} {message}"); // [INFO]
|
||||
break;
|
||||
case LogType.MAJOR:
|
||||
GD.Print($"{prefix} {message}");
|
||||
break;
|
||||
case LogType.WARN_ERR:
|
||||
GD.PrintErr($"{prefix}[ERROR] {message}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void LogDebug(string message) => Log(message, LogType.DEBUG);
|
||||
public void LogDiagnostic(string message) => Log(message, LogType.MINOR);
|
||||
public void LogInfo(string message) => Log(message, LogType.MAJOR);
|
||||
public void LogError(string message) => Log(message, LogType.WARN_ERR);
|
||||
}
|
||||
35
addons/autosaver_editor/Shared/PluginInfo.cs
Normal file
35
addons/autosaver_editor/Shared/PluginInfo.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AutoSaverPlugin.Shared
|
||||
{
|
||||
internal static class PluginInfo
|
||||
{
|
||||
internal const string NameShort = "AutoSaver";
|
||||
internal const string FullName = "AutoSaver Toggle for Godot Editor (C#)";
|
||||
internal const string Description = "Auto Saver for Godot Editor: a peace of mind toggle to automatically save your workspace";
|
||||
internal const string Author = "Victor R. R. Avalos";
|
||||
internal const string RootSettings = "autosaver_editor";
|
||||
internal const string BaseFolderName = "autosaver_editor";
|
||||
internal const string BaseResourcePath = "res://addons/autosaver_editor/";
|
||||
internal const string SettingsFileName = "settings.ini";
|
||||
internal const string PluginVersion = "0.1.0";
|
||||
internal const string PluginIcon = "icon_autosaver.png";
|
||||
|
||||
|
||||
// settings
|
||||
internal const string KeyEnabled = "enabled";
|
||||
internal const string KeyIntervalSec = "interval";
|
||||
internal const string KeyVerbose = "verbose";
|
||||
internal const string KeyPostponeTimeSec = "postpone_time";
|
||||
internal const string KeyActivityCheckWindowSec = "activity_check";
|
||||
internal const string KeyAutosaveScene = "autosave_scene";
|
||||
internal const string KeyAutosaveGDScript = "autosave_gdscript";
|
||||
|
||||
internal const string KeyUseGDEditorAutosaveInterval = "use_gd_editor_autosave_interval";
|
||||
internal const string KeyUseGDEditorSaveOnFocusLoss = "use_gd_editor_save_on_focus_loss";
|
||||
}
|
||||
}
|
||||
80
addons/autosaver_editor/Shared/ServiceProvider.cs
Normal file
80
addons/autosaver_editor/Shared/ServiceProvider.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoSaverPlugin.Contracts;
|
||||
using AutoSaverPlugin.Services;
|
||||
|
||||
namespace AutoSaverPlugin.Shared;
|
||||
|
||||
public static class ServiceProvider
|
||||
{
|
||||
private static readonly Dictionary<Type, Func<object>> _services = new();
|
||||
private static readonly Dictionary<Type, object> _singletonInstances = new();
|
||||
|
||||
public static void RegisterServiceAsTransient<TInterface, TImplementation>() where TImplementation : class, TInterface
|
||||
{
|
||||
_services[typeof(TInterface)] = () => CreateInstance<TImplementation>();
|
||||
}
|
||||
|
||||
public static void RegisterServiceAsSingleton<TInterface, TImplementation>() where TImplementation : class, TInterface, new()
|
||||
{
|
||||
_services[typeof(TInterface)] = () => GetOrCreateSingletonInstance(typeof(TInterface), () => new TImplementation());
|
||||
}
|
||||
|
||||
public static void RegisterServiceAsSingleton<TInterface>(Func<TInterface> factory)
|
||||
{
|
||||
_services[typeof(TInterface)] = () => GetOrCreateSingletonInstance(typeof(TInterface), () => factory());
|
||||
}
|
||||
|
||||
public static T GetService<T>() where T : class
|
||||
{
|
||||
return (T)GetService(typeof(T));
|
||||
}
|
||||
|
||||
private static object GetService(Type type)
|
||||
{
|
||||
if (_services.TryGetValue(type, out var serviceFactory))
|
||||
{
|
||||
return serviceFactory();
|
||||
}
|
||||
throw new InvalidOperationException($"Service of type {type} is not registered.");
|
||||
}
|
||||
|
||||
private static T CreateInstance<T>() where T : class
|
||||
{
|
||||
var type = typeof(T);
|
||||
var constructor = type.GetConstructors()[0];
|
||||
var parameters = constructor.GetParameters();
|
||||
var parameterInstances = parameters.Select(p => GetService(p.ParameterType)).ToArray();
|
||||
return (T)constructor.Invoke(parameterInstances);
|
||||
}
|
||||
|
||||
private static object GetOrCreateSingletonInstance(Type type, Func<object> factory)
|
||||
{
|
||||
if (!_singletonInstances.TryGetValue(type, out var instance))
|
||||
{
|
||||
instance = factory();
|
||||
_singletonInstances[type] = instance;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
RegisterServiceAsSingleton<ILoggerService, Logger>();
|
||||
RegisterServiceAsSingleton<IConfigurationManager>(() => new ConfigurationManager(GetService<ILoggerService>()));
|
||||
RegisterServiceAsSingleton<ISceneStatusReporter>(() => new SceneTabStatusReporter(GetService<ILoggerService>()));
|
||||
RegisterServiceAsSingleton<IGDScriptStatusReporter>(() => new GDScriptStatusReporter(GetService<ILoggerService>()));
|
||||
|
||||
RegisterServiceAsTransient<ITimerService, TimerService>();
|
||||
|
||||
RegisterServiceAsSingleton<IAutoSaveManager>(() => new AutoSaveManager(
|
||||
GetService<ISceneStatusReporter>(),
|
||||
GetService<IGDScriptStatusReporter>(),
|
||||
GetService<IConfigurationManager>(),
|
||||
GetService<ILoggerService>(),
|
||||
GetService<ITimerService>(),
|
||||
GetService<ITimerService>()
|
||||
));
|
||||
}
|
||||
}
|
||||
86
addons/autosaver_editor/UI/AutoSaveToggleMenuBuilder.cs
Normal file
86
addons/autosaver_editor/UI/AutoSaveToggleMenuBuilder.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using System.Text;
|
||||
using AutoSaverPlugin.Contracts;
|
||||
|
||||
using AutoSaverPlugin.Shared;
|
||||
using Godot;
|
||||
|
||||
namespace AutoSaverPlugin.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Builds and manages the toggle button for the Auto Save feature in the top menu.
|
||||
/// </summary>
|
||||
public class AutoSaveToggleMenuBuilder
|
||||
{
|
||||
private CheckButton _autoSaveToggleButton;
|
||||
private readonly IAutoSaveManager _autoSaveManager = ServiceProvider.GetService<IAutoSaveManager>();
|
||||
private readonly IConfigurationManager _configManager = ServiceProvider.GetService<IConfigurationManager>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoSaveToggleMenuBuilder"/> class.
|
||||
/// </summary>
|
||||
public AutoSaveToggleMenuBuilder()
|
||||
{
|
||||
Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Auto Save toggle button with configured settings and event handlers.
|
||||
/// </summary>
|
||||
public CheckButton AutoSaveToggleButton => Build();
|
||||
|
||||
/// <summary>
|
||||
/// Creates and initializes the Auto Save toggle button for the top menu.
|
||||
/// </summary>
|
||||
/// <returns>The initialized Auto Save toggle button.</returns>
|
||||
private CheckButton Build()
|
||||
{
|
||||
if (_autoSaveToggleButton != null)
|
||||
return _autoSaveToggleButton;
|
||||
|
||||
_autoSaveToggleButton = new CheckButton
|
||||
{
|
||||
Text = "Auto Save"
|
||||
};
|
||||
_autoSaveToggleButton.Toggled += HandleAutoSaveToggleChanged;
|
||||
_autoSaveToggleButton.SetPressedNoSignal(_configManager.IsAutoSaverEnabled);
|
||||
|
||||
return _autoSaveToggleButton;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detaches the toggle event subscriptions to prevent memory leaks.
|
||||
/// </summary>
|
||||
internal void DetachAutoSaveToggleEvents()
|
||||
{
|
||||
_autoSaveToggleButton.Toggled -= HandleAutoSaveToggleChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles changes to the Auto Save toggle, updating settings and service state accordingly.
|
||||
/// </summary>
|
||||
/// <param name="toggledOn">Indicates whether the feature is being enabled or disabled.</param>
|
||||
private void HandleAutoSaveToggleChanged(bool toggledOn)
|
||||
{
|
||||
_configManager.SetAutoSaverEnabled(enabled: toggledOn);
|
||||
_configManager.SaveSettings();
|
||||
|
||||
if (toggledOn)
|
||||
{
|
||||
_autoSaveManager.Reactivate();
|
||||
}
|
||||
else
|
||||
{
|
||||
_autoSaveManager.Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the toggle state based on external settings invocation.
|
||||
/// </summary>
|
||||
/// <param name="trace">The trace builder for logging.</param>
|
||||
/// <param name="enabled">Indicates the new enabled state.</param>
|
||||
internal void UpdateToggleStateFromSettings(bool enabled)
|
||||
{
|
||||
_autoSaveToggleButton.SetPressedNoSignal(enabled);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using AutoSaverPlugin.Contracts;
|
||||
using AutoSaverPlugin.Shared;
|
||||
using Godot;
|
||||
|
||||
namespace AutoSaverPlugin.UI.GDComponent;
|
||||
|
||||
[Tool]
|
||||
public partial class PanelSettingsControlNode : Control
|
||||
{
|
||||
private readonly IAutoSaveManager _autoSaveManager = ServiceProvider.GetService<IAutoSaveManager>();
|
||||
private readonly IConfigurationManager _configManager = ServiceProvider.GetService<IConfigurationManager>();
|
||||
private readonly ILoggerService _logger = ServiceProvider.GetService<ILoggerService>();
|
||||
|
||||
private SpinBox _intervalSpinBox;
|
||||
private OptionButton _verboseLevelOption;
|
||||
private Button _saveButton;
|
||||
private CheckButton _enableToggle;
|
||||
private CheckBox _autosaveSceneCheckBox;
|
||||
private CheckBox _autosaveGDScriptCheckBox;
|
||||
private Label _lblStatus;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
Name = _configManager.PluginFullName;
|
||||
|
||||
SetupUI();
|
||||
LoadPanelSettings();
|
||||
_configManager.AutoSaverStateChanged += OnInvokeBySettings;
|
||||
}
|
||||
|
||||
private void SetupUI()
|
||||
{
|
||||
var vbox = new VBoxContainer();
|
||||
AddChild(vbox);
|
||||
|
||||
SetupEnableToggle(vbox);
|
||||
vbox.AddChild(new HSeparator());
|
||||
SetupIntervalSpinBox(vbox);
|
||||
|
||||
if (_configManager.VerboseLevelSetting == VerboseLevel.SECRET)
|
||||
{
|
||||
SetupAutosaveSceneCheckBox(vbox);
|
||||
SetupAutosaveGDScriptCheckBox(vbox);
|
||||
SetupVerboseLevelOption(vbox);
|
||||
}
|
||||
|
||||
SetupSaveButton(vbox);
|
||||
SetupStatusLabel(vbox);
|
||||
vbox.AddChild(new HSeparator());
|
||||
SetupFooter(vbox);
|
||||
}
|
||||
|
||||
private void SetupAutosaveSceneCheckBox(VBoxContainer vbox)
|
||||
{
|
||||
var autosaveSceneHBox = new HBoxContainer();
|
||||
vbox.AddChild(autosaveSceneHBox);
|
||||
autosaveSceneHBox.AddChild(new Label { Text = "Autosave modified scenes" });
|
||||
_autosaveSceneCheckBox = new CheckBox();
|
||||
autosaveSceneHBox.AddChild(_autosaveSceneCheckBox);
|
||||
}
|
||||
|
||||
private void SetupAutosaveGDScriptCheckBox(VBoxContainer vbox)
|
||||
{
|
||||
var autosaveGDScriptHBox = new HBoxContainer();
|
||||
vbox.AddChild(autosaveGDScriptHBox);
|
||||
autosaveGDScriptHBox.AddChild(new Label { Text = "Autosave modified script files" });
|
||||
_autosaveGDScriptCheckBox = new CheckBox();
|
||||
autosaveGDScriptHBox.AddChild(_autosaveGDScriptCheckBox);
|
||||
}
|
||||
|
||||
private void SetupEnableToggle(VBoxContainer vbox)
|
||||
{
|
||||
var enableHBox = new HBoxContainer();
|
||||
vbox.AddChild(enableHBox);
|
||||
enableHBox.AddChild(new Label { Text = "Enable AutoSaver:" });
|
||||
_enableToggle = new CheckButton();
|
||||
enableHBox.AddChild(_enableToggle);
|
||||
_enableToggle.Toggled += OnPanelAutoSaveToggled;
|
||||
}
|
||||
|
||||
private void SetupIntervalSpinBox(VBoxContainer vbox)
|
||||
{
|
||||
var intervalHBox = new HBoxContainer();
|
||||
vbox.AddChild(intervalHBox);
|
||||
intervalHBox.AddChild(new Label { Text = "Autosave interval (seconds):" });
|
||||
_intervalSpinBox = new SpinBox { MinValue = 5, MaxValue = 300, Value = 60, Step = 5 };
|
||||
intervalHBox.AddChild(_intervalSpinBox);
|
||||
}
|
||||
|
||||
private void SetupVerboseLevelOption(VBoxContainer vbox)
|
||||
{
|
||||
var verboseHBox = new HBoxContainer();
|
||||
vbox.AddChild(verboseHBox);
|
||||
verboseHBox.AddChild(new Label { Text = "Verbose level:" });
|
||||
_verboseLevelOption = new OptionButton();
|
||||
|
||||
_verboseLevelOption.AddItem("OFF", (int)VerboseLevel.OFF);
|
||||
_verboseLevelOption.AddItem("MIN", (int)VerboseLevel.MIN);
|
||||
_verboseLevelOption.AddItem("MAX", (int)VerboseLevel.MAX);
|
||||
_verboseLevelOption.AddItem("SECRET", (int)VerboseLevel.SECRET);
|
||||
|
||||
verboseHBox.AddChild(_verboseLevelOption);
|
||||
}
|
||||
|
||||
private void SetupSaveButton(VBoxContainer vbox)
|
||||
{
|
||||
_saveButton = new Button { Text = "Update Settings" };
|
||||
vbox.AddChild(_saveButton);
|
||||
_saveButton.Pressed += OnSaveButtonPressed;
|
||||
}
|
||||
|
||||
private void SetupStatusLabel(VBoxContainer vbox)
|
||||
{
|
||||
_lblStatus = new Label();
|
||||
vbox.AddChild(_lblStatus);
|
||||
_lblStatus.Text = "Settings loaded @" + CommonUtils.GetCurrentTimestamp();
|
||||
}
|
||||
|
||||
private void SetupFooter(VBoxContainer vbox)
|
||||
{
|
||||
string footerText = $"AutoSaver for Godot Editor v.{_configManager.PluginVersion} by Victor R. R. Avalos";
|
||||
vbox.AddChild(new LinkButton { Text = footerText, Uri = "https://github.com/vrravalos" });
|
||||
}
|
||||
|
||||
private void OnInvokeBySettings(bool enabled)
|
||||
{
|
||||
_logger.LogDebug($"{{Panel: {nameof(OnInvokeBySettings)}}} AutoSaver enabled state changed..");
|
||||
_enableToggle.SetPressedNoSignal(enabled);
|
||||
UpdateUIState();
|
||||
}
|
||||
|
||||
private void LoadPanelSettings()
|
||||
{
|
||||
_logger.LogDiagnostic("Loading panel settings..");
|
||||
|
||||
_enableToggle.SetPressedNoSignal(_configManager.IsAutoSaverEnabled);
|
||||
_intervalSpinBox.SetValueNoSignal(_configManager.AutoSaverIntervalSetting);
|
||||
_verboseLevelOption?.Select((int)_configManager.VerboseLevelSetting);
|
||||
_autosaveSceneCheckBox?.SetPressedNoSignal(_configManager.IsOptionSaveScenesEnabled);
|
||||
_autosaveGDScriptCheckBox?.SetPressedNoSignal(_configManager.IsOptionSaveScriptsEnabled);
|
||||
UpdateUIState();
|
||||
}
|
||||
|
||||
private void OnSaveButtonPressed()
|
||||
{
|
||||
_logger.LogDebug("Saving settings..");
|
||||
SaveSettings(noSignal: true);
|
||||
UpdateUIState();
|
||||
}
|
||||
|
||||
public void OnPanelAutoSaveToggled(bool buttonPressed)
|
||||
{
|
||||
SaveSettings();
|
||||
UpdateUIState();
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
_enableToggle.Toggled -= OnPanelAutoSaveToggled;
|
||||
_configManager.AutoSaverStateChanged -= OnInvokeBySettings;
|
||||
}
|
||||
|
||||
private void UpdateUIState()
|
||||
{
|
||||
bool isEnabled = _enableToggle.ButtonPressed;
|
||||
_intervalSpinBox.Editable = isEnabled;
|
||||
if (_verboseLevelOption != null)
|
||||
_verboseLevelOption.Disabled = !isEnabled;
|
||||
|
||||
if (_autosaveSceneCheckBox != null)
|
||||
_autosaveSceneCheckBox.Disabled = !isEnabled;
|
||||
|
||||
if (_autosaveGDScriptCheckBox != null)
|
||||
_autosaveGDScriptCheckBox.Disabled = !isEnabled;
|
||||
|
||||
_saveButton.Disabled = !isEnabled;
|
||||
_lblStatus.Text = "Settings updated @" + CommonUtils.GetCurrentTimestamp();
|
||||
}
|
||||
|
||||
private void SaveSettings(bool noSignal = false)
|
||||
{
|
||||
SaveSettings(noEmitSignal: noSignal,
|
||||
interval: (int?)_intervalSpinBox.Value,
|
||||
verboseLevel: _verboseLevelOption?.Selected != null ? (VerboseLevel?)_verboseLevelOption.Selected : null,
|
||||
sceneEnabled: _autosaveSceneCheckBox?.ButtonPressed,
|
||||
scriptEnabled: _autosaveGDScriptCheckBox?.ButtonPressed,
|
||||
autoSaverEnabled: _enableToggle.ButtonPressed);
|
||||
}
|
||||
|
||||
private void SaveSettings(bool noEmitSignal, int? interval = null,
|
||||
VerboseLevel? verboseLevel = null, bool? sceneEnabled = null, bool? scriptEnabled = null,
|
||||
bool? autoSaverEnabled = null)
|
||||
{
|
||||
if (interval.HasValue)
|
||||
{
|
||||
_configManager.SetSaverInterval(interval.Value);
|
||||
}
|
||||
|
||||
if (verboseLevel.HasValue)
|
||||
{
|
||||
_configManager.SetVerboseLevel(verboseLevel.Value);
|
||||
}
|
||||
|
||||
if (sceneEnabled.HasValue)
|
||||
{
|
||||
_configManager.SetSceneEnabled(enabled: sceneEnabled.Value);
|
||||
}
|
||||
|
||||
if (scriptEnabled.HasValue)
|
||||
{
|
||||
_configManager.SetScriptEnabled(enabled: scriptEnabled.Value);
|
||||
}
|
||||
|
||||
if (autoSaverEnabled.HasValue)
|
||||
{
|
||||
_configManager.SetAutoSaverEnabled(enabled: autoSaverEnabled.Value, noEmitSignal: noEmitSignal);
|
||||
}
|
||||
|
||||
_configManager.SaveSettings();
|
||||
|
||||
if (_configManager.IsAutoSaverEnabled)
|
||||
{
|
||||
_autoSaveManager.Reactivate();
|
||||
}
|
||||
else
|
||||
{
|
||||
_autoSaveManager.Deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
addons/autosaver_editor/icon_autosaver.png
Normal file
BIN
addons/autosaver_editor/icon_autosaver.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
34
addons/autosaver_editor/icon_autosaver.png.import
Normal file
34
addons/autosaver_editor/icon_autosaver.png.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b1v64q61ia0mg"
|
||||
path="res://.godot/imported/icon_autosaver.png-55c98d7fbebfa4f3682998528b7c512a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/autosaver_editor/icon_autosaver.png"
|
||||
dest_files=["res://.godot/imported/icon_autosaver.png-55c98d7fbebfa4f3682998528b7c512a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
6
addons/autosaver_editor/plugin.cfg
Normal file
6
addons/autosaver_editor/plugin.cfg
Normal file
@ -0,0 +1,6 @@
|
||||
[plugin]
|
||||
name="AutoSaver Toggle for Godot Editor (C#)"
|
||||
description="Auto Saver Toggle for Godot Editor: a peace of mind toggle to save your workspace automatically"
|
||||
author="Victor R. R. Avalos"
|
||||
version="0.1.0"
|
||||
script="AutoSaverEditorPlugin.cs"
|
||||
11
addons/autosaver_editor/settings.ini
Normal file
11
addons/autosaver_editor/settings.ini
Normal file
@ -0,0 +1,11 @@
|
||||
[autosaver_editor]
|
||||
|
||||
interval=60
|
||||
verbose="OFF"
|
||||
enabled=false
|
||||
postpone_time=1
|
||||
activity_check=1
|
||||
autosave_scene=true
|
||||
autosave_gdscript=true
|
||||
use_gd_editor_autosave_interval=true
|
||||
use_gd_editor_save_on_focus_loss=false
|
||||
@ -11,7 +11,8 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="导师模拟器"
|
||||
config/features=PackedStringArray("4.3", "GL Compatibility")
|
||||
run/main_scene="res://node_2d.tscn"
|
||||
config/features=PackedStringArray("4.3", "C#", "GL Compatibility")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
@ -34,7 +35,7 @@ version_control/autoload_on_startup=true
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/dialogic/plugin.cfg")
|
||||
enabled=PackedStringArray("res://addons/autosaver_editor/plugin.cfg", "res://addons/dialogic/plugin.cfg")
|
||||
|
||||
[input]
|
||||
|
||||
|
||||
8
导师模拟器.csproj
Normal file
8
导师模拟器.csproj
Normal file
@ -0,0 +1,8 @@
|
||||
<Project Sdk="Godot.NET.Sdk/4.3.0">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
|
||||
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
8
导师模拟器.csproj.old
Normal file
8
导师模拟器.csproj.old
Normal file
@ -0,0 +1,8 @@
|
||||
<Project Sdk="Godot.NET.Sdk/4.3.0-limboai-v1.2.2.gha">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
|
||||
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
19
导师模拟器.sln
Normal file
19
导师模拟器.sln
Normal file
@ -0,0 +1,19 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "导师模拟器", "导师模拟器.csproj", "{9E17CC7B-7EE8-4BE9-AE6F-CB46D9D366A7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9E17CC7B-7EE8-4BE9-AE6F-CB46D9D366A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9E17CC7B-7EE8-4BE9-AE6F-CB46D9D366A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9E17CC7B-7EE8-4BE9-AE6F-CB46D9D366A7}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||
{9E17CC7B-7EE8-4BE9-AE6F-CB46D9D366A7}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||
{9E17CC7B-7EE8-4BE9-AE6F-CB46D9D366A7}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||
{9E17CC7B-7EE8-4BE9-AE6F-CB46D9D366A7}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Loading…
Reference in New Issue
Block a user