From 7f5add1a0de3d9c63f429e5ccc87abdf8bc7626c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 11 Jan 2026 12:06:11 +0000
Subject: [PATCH 1/5] Initial plan
From 14d0088ac1f57526d00f2d11f7514eed74e09a34 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 11 Jan 2026 12:14:23 +0000
Subject: [PATCH 2/5] Add FileWatcher class and stylesheet hotloading support
in DEBUG builds
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
---
src/Fazor.UI/FazorApplication.cs | 9 +
src/Fazor.UI/NativeWindow.cs | 3 +
src/Sandbox.UI/Panel/RootPanel.cs | 18 ++
src/Sandbox.UI/Styles/StyleSheet.cs | 63 ++++-
src/Sandbox.UI/Utility/FileWatcher.cs | 323 ++++++++++++++++++++++++++
5 files changed, 415 insertions(+), 1 deletion(-)
create mode 100644 src/Sandbox.UI/Utility/FileWatcher.cs
diff --git a/src/Fazor.UI/FazorApplication.cs b/src/Fazor.UI/FazorApplication.cs
index 2ba8ff82..f577c084 100644
--- a/src/Fazor.UI/FazorApplication.cs
+++ b/src/Fazor.UI/FazorApplication.cs
@@ -245,6 +245,15 @@ public static void Run(RootPanel rootPanel, int width = 1280, int height = 720,
nativeWindow.RootPanel = rootPanel;
+#if DEBUG
+ // Set up hotloading: when stylesheets change, dirty all styles on the root panel
+ StyleSheet.OnStyleSheetsHotloaded += () =>
+ {
+ Console.WriteLine("[Hotload] Stylesheets changed - refreshing all panel styles");
+ rootPanel.DirtyAllStyles();
+ };
+#endif
+
// Initialize window manager for multi-window support (create once globally)
if (_windowManager == null)
{
diff --git a/src/Fazor.UI/NativeWindow.cs b/src/Fazor.UI/NativeWindow.cs
index 3ec292ff..f6897913 100644
--- a/src/Fazor.UI/NativeWindow.cs
+++ b/src/Fazor.UI/NativeWindow.cs
@@ -233,6 +233,9 @@ private void OnRender(double delta)
{
if (RootPanel == null) return;
+ // Process file watcher notifications (DEBUG builds only - handles stylesheet/Razor hotloading)
+ FileWatcher.Tick((float)delta);
+
// Update panel time for transitions and animations
PanelRealTime.Update(delta);
RealTime.Update(delta);
diff --git a/src/Sandbox.UI/Panel/RootPanel.cs b/src/Sandbox.UI/Panel/RootPanel.cs
index e6414e36..bb601a23 100644
--- a/src/Sandbox.UI/Panel/RootPanel.cs
+++ b/src/Sandbox.UI/Panel/RootPanel.cs
@@ -341,4 +341,22 @@ internal void BuildStyleRules()
styleRuleUpdates.Clear();
}
+
+ ///
+ /// Dirty all styles on this root panel and its children.
+ /// Called when stylesheets are hotloaded to force a complete style rebuild.
+ ///
+ internal void DirtyAllStyles()
+ {
+ DirtyStylesRecursive();
+ }
+
+ ///
+ /// Call OnHotloaded on this panel and all children.
+ /// Called when razor files or stylesheets are hotloaded.
+ ///
+ internal void TriggerHotload()
+ {
+ OnHotloaded();
+ }
}
diff --git a/src/Sandbox.UI/Styles/StyleSheet.cs b/src/Sandbox.UI/Styles/StyleSheet.cs
index 3e2abc94..37cac413 100644
--- a/src/Sandbox.UI/Styles/StyleSheet.cs
+++ b/src/Sandbox.UI/Styles/StyleSheet.cs
@@ -4,6 +4,12 @@ public class StyleSheet
{
public static List Loaded { get; internal set; } = new List();
+ ///
+ /// Event raised when any stylesheet is hotloaded.
+ /// Used by the UI system to invalidate all panel styles.
+ ///
+ public static event Action? OnStyleSheetsHotloaded;
+
///
/// Between sessions we clear the stylesheets, so one gamemode can't accidentally
/// use cached values from another.
@@ -20,6 +26,7 @@ internal static void InitStyleSheets()
public List Nodes { get; set; } = new List();
public string FileName { get; internal set; }
+ internal FileWatcher? Watcher { get; private set; }
public List IncludedFiles { get; set; } = new List();
public Dictionary Variables;
public Dictionary KeyFrames = new Dictionary( StringComparer.OrdinalIgnoreCase );
@@ -29,7 +36,8 @@ internal static void InitStyleSheets()
///
public void Release()
{
- // File watching not implemented in this port
+ Watcher?.Dispose();
+ Watcher = null;
}
public static StyleSheet FromFile( string filename, IEnumerable<(string key, string value)> variables = null, bool failSilently = false )
@@ -46,6 +54,7 @@ public static StyleSheet FromFile( string filename, IEnumerable<(string key, str
sheet.AddVariables( variables );
sheet.FileName = filename;
+ sheet.AddWatcher( filename );
Loaded.Add( sheet );
@@ -55,6 +64,7 @@ public static StyleSheet FromFile( string filename, IEnumerable<(string key, str
internal void AddFilename( string filename )
{
IncludedFiles.Add( filename );
+ Watcher?.AddFile( filename );
}
public static StyleSheet FromString( string styles, string filename = "none", IEnumerable<(string key, string value)> variables = null )
@@ -211,4 +221,55 @@ public void AddKeyFrames( KeyFrames frames )
{
KeyFrames[frames.Name] = frames;
}
+
+ ///
+ /// Set up file watching for this stylesheet (DEBUG builds only).
+ ///
+ private void AddWatcher( string name )
+ {
+#if DEBUG
+ Watcher?.Dispose();
+ Watcher = null;
+
+ var fullPath = name;
+ if ( !System.IO.Path.IsPathRooted( fullPath ) )
+ {
+ fullPath = System.IO.Path.GetFullPath( name );
+ }
+
+ if ( !System.IO.File.Exists( fullPath ) )
+ return;
+
+ // Store the filename for reloading
+ var watchFileName = fullPath;
+
+ Watcher = new FileWatcher();
+ Watcher.AddFile( fullPath );
+
+ // Add any included files to the watcher
+ foreach ( var file in IncludedFiles )
+ {
+ var includePath = file;
+ if ( !System.IO.Path.IsPathRooted( includePath ) )
+ {
+ // Try relative to the main stylesheet
+ var dir = System.IO.Path.GetDirectoryName( fullPath );
+ if ( !string.IsNullOrEmpty( dir ) )
+ {
+ includePath = System.IO.Path.Combine( dir, file );
+ }
+ }
+ Watcher.AddFile( includePath );
+ }
+
+ Watcher.OnChanges += ( watcher ) =>
+ {
+ Console.WriteLine( $"[StyleSheet] Hotloading: {watchFileName}" );
+ UpdateFromFile( watchFileName, true );
+
+ // Notify the UI system that stylesheets have changed
+ OnStyleSheetsHotloaded?.Invoke();
+ };
+#endif
+ }
}
diff --git a/src/Sandbox.UI/Utility/FileWatcher.cs b/src/Sandbox.UI/Utility/FileWatcher.cs
new file mode 100644
index 00000000..b5a8c3fa
--- /dev/null
+++ b/src/Sandbox.UI/Utility/FileWatcher.cs
@@ -0,0 +1,323 @@
+namespace Sandbox.UI;
+
+///
+/// Watch files and dispatch events when they change.
+/// Based on S&box's FileWatch pattern for stylesheet and Razor hotloading.
+/// Only active in DEBUG builds to avoid overhead in release.
+///
+public sealed class FileWatcher : IDisposable
+{
+#if DEBUG
+ private static readonly object _lock = new();
+ private static readonly List _watchers = new();
+ private static readonly HashSet _changedFiles = new(StringComparer.OrdinalIgnoreCase);
+ private static readonly Dictionary _directoryWatchers = new();
+ private static float _timeSinceLastChange = 0f;
+ private static float _debounceTime = 0.2f; // 200ms debounce to batch rapid file changes
+ private static bool _hasChanges = false;
+
+ private readonly List _watchedFiles = new();
+ private bool _disposed;
+
+ ///
+ /// Event raised when any watched file changes.
+ /// The FileWatcher argument contains the list of changed files.
+ ///
+ public event Action? OnChanges;
+
+ ///
+ /// Event raised for each individual file that changed.
+ ///
+ public event Action? OnChangedFile;
+
+ ///
+ /// The files that changed since the last callback.
+ ///
+ public List Changes { get; private set; } = new();
+
+ ///
+ /// Whether this watcher is enabled.
+ ///
+ public bool Enabled { get; set; } = true;
+
+ ///
+ /// Creates a new file watcher.
+ ///
+ public FileWatcher()
+ {
+ lock (_lock)
+ {
+ _watchers.Add(this);
+ }
+ }
+
+ ///
+ /// Add a file to watch for changes.
+ ///
+ public void AddFile(string filePath)
+ {
+ if (string.IsNullOrWhiteSpace(filePath))
+ return;
+
+ // Normalize the path
+ filePath = NormalizePath(filePath);
+
+ if (!_watchedFiles.Contains(filePath))
+ {
+ _watchedFiles.Add(filePath);
+ }
+
+ // Ensure we have a watcher for this file's directory
+ EnsureDirectoryWatcher(filePath);
+ }
+
+ ///
+ /// Remove a file from being watched.
+ ///
+ public void RemoveFile(string filePath)
+ {
+ if (string.IsNullOrWhiteSpace(filePath))
+ return;
+
+ filePath = NormalizePath(filePath);
+ _watchedFiles.Remove(filePath);
+ }
+
+ ///
+ /// Check if this watcher is interested in a specific file.
+ ///
+ private bool InterestedInFile(string file)
+ {
+ if (_watchedFiles.Count == 0)
+ return true; // No specific files = watch all
+
+ return _watchedFiles.Any(f =>
+ string.Equals(f, file, StringComparison.OrdinalIgnoreCase) ||
+ file.EndsWith(f, StringComparison.OrdinalIgnoreCase));
+ }
+
+ ///
+ /// Ensure we have a FileSystemWatcher for the directory containing this file.
+ ///
+ private static void EnsureDirectoryWatcher(string filePath)
+ {
+ var directory = Path.GetDirectoryName(filePath);
+ if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory))
+ return;
+
+ lock (_lock)
+ {
+ if (_directoryWatchers.ContainsKey(directory))
+ return;
+
+ try
+ {
+ var watcher = new FileSystemWatcher(directory)
+ {
+ NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size,
+ IncludeSubdirectories = false,
+ EnableRaisingEvents = true
+ };
+
+ watcher.Changed += OnFileChanged;
+ watcher.Created += OnFileChanged;
+ watcher.Renamed += OnFileRenamed;
+ watcher.Error += OnWatcherError;
+
+ _directoryWatchers[directory] = watcher;
+
+ Console.WriteLine($"[FileWatcher] Started watching: {directory}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[FileWatcher] Failed to watch directory '{directory}': {ex.Message}");
+ }
+ }
+ }
+
+ private static void OnFileChanged(object sender, FileSystemEventArgs e)
+ {
+ AddChangedFile(e.FullPath);
+ }
+
+ private static void OnFileRenamed(object sender, RenamedEventArgs e)
+ {
+ AddChangedFile(e.OldFullPath);
+ AddChangedFile(e.FullPath);
+ }
+
+ private static void OnWatcherError(object sender, ErrorEventArgs e)
+ {
+ Console.WriteLine($"[FileWatcher] Error: {e.GetException()?.Message}");
+ }
+
+ private static void AddChangedFile(string filePath)
+ {
+ filePath = NormalizePath(filePath);
+
+ // Ignore temp files
+ if (filePath.EndsWith(".tmp", StringComparison.OrdinalIgnoreCase) ||
+ filePath.EndsWith("~", StringComparison.OrdinalIgnoreCase))
+ return;
+
+ lock (_lock)
+ {
+ _changedFiles.Add(filePath);
+ _timeSinceLastChange = 0f;
+ _hasChanges = true;
+ }
+ }
+
+ private static string NormalizePath(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ return path;
+
+ // Get full path and normalize separators
+ try
+ {
+ path = Path.GetFullPath(path);
+ }
+ catch
+ {
+ // If GetFullPath fails, just normalize slashes
+ }
+
+ return path.Replace('\\', '/');
+ }
+
+ ///
+ /// Must be called from the main thread each frame to process file change notifications.
+ /// This is typically called from the render loop.
+ ///
+ /// Time since last tick in seconds
+ public static void Tick(float deltaTime)
+ {
+ if (!_hasChanges)
+ return;
+
+ _timeSinceLastChange += deltaTime;
+
+ // Wait for debounce period to batch rapid file saves
+ if (_timeSinceLastChange < _debounceTime)
+ return;
+
+ List? changedFiles = null;
+ List? watchers = null;
+
+ lock (_lock)
+ {
+ if (_changedFiles.Count == 0)
+ {
+ _hasChanges = false;
+ return;
+ }
+
+ changedFiles = _changedFiles.ToList();
+ _changedFiles.Clear();
+ _hasChanges = false;
+
+ watchers = _watchers.ToList();
+ }
+
+ // Log what changed
+ foreach (var file in changedFiles)
+ {
+ Console.WriteLine($"[FileWatcher] File changed: {file}");
+ }
+
+ // Notify watchers
+ foreach (var watcher in watchers)
+ {
+ if (!watcher.Enabled || watcher._disposed)
+ continue;
+
+ watcher.TriggerCallback(changedFiles);
+ }
+ }
+
+ private void TriggerCallback(List changedFiles)
+ {
+ Changes.Clear();
+
+ foreach (var file in changedFiles)
+ {
+ if (InterestedInFile(file))
+ {
+ Changes.Add(file);
+ }
+ }
+
+ if (Changes.Count == 0)
+ return;
+
+ try
+ {
+ OnChanges?.Invoke(this);
+
+ foreach (var file in Changes)
+ {
+ OnChangedFile?.Invoke(file);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[FileWatcher] Error in callback: {ex.Message}");
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+ OnChanges = null;
+ OnChangedFile = null;
+
+ lock (_lock)
+ {
+ _watchers.Remove(this);
+ }
+ }
+
+ ///
+ /// Dispose all directory watchers. Call on application shutdown.
+ ///
+ public static void DisposeAll()
+ {
+ lock (_lock)
+ {
+ foreach (var watcher in _directoryWatchers.Values)
+ {
+ try
+ {
+ watcher.EnableRaisingEvents = false;
+ watcher.Dispose();
+ }
+ catch { }
+ }
+ _directoryWatchers.Clear();
+
+ foreach (var w in _watchers.ToList())
+ {
+ w.Dispose();
+ }
+ _watchers.Clear();
+ _changedFiles.Clear();
+ }
+ }
+#else
+ // Release build stubs - no-op implementations
+ public event Action? OnChanges;
+ public event Action? OnChangedFile;
+ public List Changes { get; } = new();
+ public bool Enabled { get; set; } = false;
+
+ public void AddFile(string filePath) { }
+ public void RemoveFile(string filePath) { }
+ public void Dispose() { }
+ public static void Tick(float deltaTime) { }
+ public static void DisposeAll() { }
+#endif
+}
From f4646950fab760ac85c5d0d43bac1421ff93c8d3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 11 Jan 2026 12:19:20 +0000
Subject: [PATCH 3/5] Address code review feedback - improve file matching and
use Log class
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
---
src/Sandbox.UI/Utility/FileWatcher.cs | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/src/Sandbox.UI/Utility/FileWatcher.cs b/src/Sandbox.UI/Utility/FileWatcher.cs
index b5a8c3fa..a390c432 100644
--- a/src/Sandbox.UI/Utility/FileWatcher.cs
+++ b/src/Sandbox.UI/Utility/FileWatcher.cs
@@ -85,15 +85,21 @@ public void RemoveFile(string filePath)
///
/// Check if this watcher is interested in a specific file.
+ /// Uses exact path matching to avoid false positives.
///
private bool InterestedInFile(string file)
{
+ // If no specific files registered, require explicit file registration
+ // to avoid excessive callbacks. This is a safety measure.
if (_watchedFiles.Count == 0)
- return true; // No specific files = watch all
+ return false;
+ // Normalize for comparison
+ file = NormalizePath(file);
+
+ // Use exact path matching only
return _watchedFiles.Any(f =>
- string.Equals(f, file, StringComparison.OrdinalIgnoreCase) ||
- file.EndsWith(f, StringComparison.OrdinalIgnoreCase));
+ string.Equals(f, file, StringComparison.OrdinalIgnoreCase));
}
///
@@ -126,11 +132,11 @@ private static void EnsureDirectoryWatcher(string filePath)
_directoryWatchers[directory] = watcher;
- Console.WriteLine($"[FileWatcher] Started watching: {directory}");
+ Log.Info($"[FileWatcher] Started watching: {directory}");
}
catch (Exception ex)
{
- Console.WriteLine($"[FileWatcher] Failed to watch directory '{directory}': {ex.Message}");
+ Log.Warning($"[FileWatcher] Failed to watch directory '{directory}': {ex.Message}");
}
}
}
@@ -148,7 +154,7 @@ private static void OnFileRenamed(object sender, RenamedEventArgs e)
private static void OnWatcherError(object sender, ErrorEventArgs e)
{
- Console.WriteLine($"[FileWatcher] Error: {e.GetException()?.Message}");
+ Log.Warning($"[FileWatcher] Error: {e.GetException()?.Message}");
}
private static void AddChangedFile(string filePath)
@@ -223,7 +229,7 @@ public static void Tick(float deltaTime)
// Log what changed
foreach (var file in changedFiles)
{
- Console.WriteLine($"[FileWatcher] File changed: {file}");
+ Log.Info($"[FileWatcher] File changed: {file}");
}
// Notify watchers
@@ -262,7 +268,7 @@ private void TriggerCallback(List changedFiles)
}
catch (Exception ex)
{
- Console.WriteLine($"[FileWatcher] Error in callback: {ex.Message}");
+ Log.Warning($"[FileWatcher] Error in callback: {ex.Message}");
}
}
From fce6c525c24ab4493432c20d4a783dc1d2f53870 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 11 Jan 2026 12:22:22 +0000
Subject: [PATCH 4/5] Add FileWatcher tests and fix pre-existing test namespace
issue
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
---
tests/Sandbox.UI.Tests/FileWatcherTests.cs | 85 ++++++++++++++++++++++
tests/Sandbox.UI.Tests/StyleSheetTests.cs | 1 +
2 files changed, 86 insertions(+)
create mode 100644 tests/Sandbox.UI.Tests/FileWatcherTests.cs
diff --git a/tests/Sandbox.UI.Tests/FileWatcherTests.cs b/tests/Sandbox.UI.Tests/FileWatcherTests.cs
new file mode 100644
index 00000000..46a12ff2
--- /dev/null
+++ b/tests/Sandbox.UI.Tests/FileWatcherTests.cs
@@ -0,0 +1,85 @@
+using Sandbox.UI;
+using Xunit;
+
+namespace Sandbox.UI.Tests;
+
+///
+/// Tests for the FileWatcher class used for stylesheet/Razor hotloading.
+/// Note: FileWatcher is only active in DEBUG builds.
+///
+public class FileWatcherTests
+{
+#if DEBUG
+ [Fact]
+ public void FileWatcher_AddFile_RegistersFileForWatching()
+ {
+ // Arrange
+ var watcher = new FileWatcher();
+ var testPath = "/tmp/test.scss";
+
+ // Act
+ watcher.AddFile(testPath);
+
+ // Assert - watcher should be created without error
+ Assert.True(watcher.Enabled);
+
+ // Cleanup
+ watcher.Dispose();
+ }
+
+ [Fact]
+ public void FileWatcher_Dispose_CleansUpResources()
+ {
+ // Arrange
+ var watcher = new FileWatcher();
+ watcher.AddFile("/tmp/test.scss");
+
+ // Act
+ watcher.Dispose();
+
+ // Assert - no exceptions thrown during dispose
+ Assert.True(true);
+ }
+
+ [Fact]
+ public void FileWatcher_Tick_ProcessesWithoutError()
+ {
+ // Arrange - just ensure Tick doesn't throw
+
+ // Act
+ FileWatcher.Tick(0.016f); // ~60fps frame time
+
+ // Assert - no exceptions
+ Assert.True(true);
+ }
+
+ [Fact]
+ public void FileWatcher_DisposeAll_CleansUpAllWatchers()
+ {
+ // Arrange
+ var watcher1 = new FileWatcher();
+ var watcher2 = new FileWatcher();
+ watcher1.AddFile("/tmp/test1.scss");
+ watcher2.AddFile("/tmp/test2.scss");
+
+ // Act
+ FileWatcher.DisposeAll();
+
+ // Assert - no exceptions
+ Assert.True(true);
+ }
+#else
+ [Fact]
+ public void FileWatcher_InReleaseBuild_IsNoOp()
+ {
+ // In release builds, FileWatcher methods should be no-ops
+ var watcher = new FileWatcher();
+ watcher.AddFile("/tmp/test.scss");
+
+ // In release, Enabled should be false (no-op mode)
+ Assert.False(watcher.Enabled);
+
+ watcher.Dispose();
+ }
+#endif
+}
diff --git a/tests/Sandbox.UI.Tests/StyleSheetTests.cs b/tests/Sandbox.UI.Tests/StyleSheetTests.cs
index 904048a6..c905c23f 100644
--- a/tests/Sandbox.UI.Tests/StyleSheetTests.cs
+++ b/tests/Sandbox.UI.Tests/StyleSheetTests.cs
@@ -1,4 +1,5 @@
using Sandbox.UI;
+using Fazor.Controls;
using System.Reflection;
using Xunit;
From 1a20a340cbbf80622713abba1fb077ddcd8d666e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 11 Jan 2026 12:25:18 +0000
Subject: [PATCH 5/5] Replace Console.WriteLine with Log.Info for consistency
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
---
src/Fazor.UI/FazorApplication.cs | 2 +-
src/Sandbox.UI/Styles/StyleSheet.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Fazor.UI/FazorApplication.cs b/src/Fazor.UI/FazorApplication.cs
index f577c084..8e894c67 100644
--- a/src/Fazor.UI/FazorApplication.cs
+++ b/src/Fazor.UI/FazorApplication.cs
@@ -249,7 +249,7 @@ public static void Run(RootPanel rootPanel, int width = 1280, int height = 720,
// Set up hotloading: when stylesheets change, dirty all styles on the root panel
StyleSheet.OnStyleSheetsHotloaded += () =>
{
- Console.WriteLine("[Hotload] Stylesheets changed - refreshing all panel styles");
+ Sandbox.UI.Log.Info("[Hotload] Stylesheets changed - refreshing all panel styles");
rootPanel.DirtyAllStyles();
};
#endif
diff --git a/src/Sandbox.UI/Styles/StyleSheet.cs b/src/Sandbox.UI/Styles/StyleSheet.cs
index 37cac413..96232d74 100644
--- a/src/Sandbox.UI/Styles/StyleSheet.cs
+++ b/src/Sandbox.UI/Styles/StyleSheet.cs
@@ -264,7 +264,7 @@ private void AddWatcher( string name )
Watcher.OnChanges += ( watcher ) =>
{
- Console.WriteLine( $"[StyleSheet] Hotloading: {watchFileName}" );
+ Log.Info( $"[StyleSheet] Hotloading: {watchFileName}" );
UpdateFromFile( watchFileName, true );
// Notify the UI system that stylesheets have changed