Add stylesheet hotloading for DEBUG builds - #79
Conversation
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
|
@copilot how do i use this in a project? |
The stylesheet hotloading works automatically in DEBUG builds - no code changes needed in your project! How it works:
Requirements:
Example project setup: <!-- In your .csproj, ensure stylesheets are copied to output -->
<ItemGroup>
<Content Include="Styles\**\*.scss">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>The feature is completely disabled in Release builds for zero overhead. |
There was a problem hiding this comment.
Pull request overview
This PR implements stylesheet hot-reloading for DEBUG builds, allowing developers to modify .scss files and see changes immediately without restarting the application. The implementation is based on S&box's hot-reloading system.
Changes:
- Implemented a FileWatcher utility with debouncing and thread-safe file change detection
- Added hot-reload support to StyleSheet with automatic watching of main files and
@importincludes - Integrated hot-reload events into the UI refresh pipeline via RootPanel and FazorApplication
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/Sandbox.UI/Utility/FileWatcher.cs |
New file watcher utility wrapping FileSystemWatcher with 200ms debouncing, exact path matching, and DEBUG/Release conditional compilation |
src/Sandbox.UI/Styles/StyleSheet.cs |
Added file watching setup, hotload event, and automatic reloading of stylesheets and their includes |
src/Sandbox.UI/Panel/RootPanel.cs |
Added DirtyAllStyles() to invalidate all panel styles when stylesheets change, plus unused TriggerHotload() for future Razor support |
src/Fazor.UI/FazorApplication.cs |
Subscribed to stylesheet hotload events to trigger UI refresh |
src/Fazor.UI/NativeWindow.cs |
Added FileWatcher.Tick() call in render loop to process file change notifications |
tests/Sandbox.UI.Tests/FileWatcherTests.cs |
Basic smoke tests verifying FileWatcher API works in DEBUG and is no-op in Release |
tests/Sandbox.UI.Tests/StyleSheetTests.cs |
Added unused import (likely accidental) |
| @@ -1,4 +1,5 @@ | |||
| using Sandbox.UI; | |||
| using Fazor.Controls; | |||
There was a problem hiding this comment.
This using directive for Fazor.Controls is unused. The file doesn't reference any types from this namespace.
| using Fazor.Controls; |
| StyleSheet.OnStyleSheetsHotloaded += () => | ||
| { | ||
| Sandbox.UI.Log.Info("[Hotload] Stylesheets changed - refreshing all panel styles"); | ||
| rootPanel.DirtyAllStyles(); | ||
| }; |
There was a problem hiding this comment.
This static event subscription creates a potential memory leak. Each time RunPanel is called, a new lambda is subscribed to the static event, but there's no corresponding unsubscription. The lambda captures rootPanel, which prevents the RootPanel from being garbage collected even after the window closes.
Consider either:
- Unsubscribing when the window is disposed/closed
- Using weak event patterns
- Making this subscription at a different lifecycle point where it can be properly cleaned up
| StyleSheet.OnStyleSheetsHotloaded += () => | |
| { | |
| Sandbox.UI.Log.Info("[Hotload] Stylesheets changed - refreshing all panel styles"); | |
| rootPanel.DirtyAllStyles(); | |
| }; | |
| // Use a weak reference so the RootPanel can be collected, and unsubscribe when it's gone | |
| var rootPanelRef = new WeakReference<RootPanel>(rootPanel); | |
| void OnHotload() | |
| { | |
| if (rootPanelRef.TryGetTarget(out var rp)) | |
| { | |
| Sandbox.UI.Log.Info("[Hotload] Stylesheets changed - refreshing all panel styles"); | |
| rp.DirtyAllStyles(); | |
| } | |
| else | |
| { | |
| // Root panel has been collected; remove this handler to avoid leaking it | |
| StyleSheet.OnStyleSheetsHotloaded -= OnHotload; | |
| } | |
| } | |
| StyleSheet.OnStyleSheetsHotloaded += OnHotload; |
| internal void TriggerHotload() | ||
| { | ||
| OnHotloaded(); | ||
| } |
There was a problem hiding this comment.
The TriggerHotload method is defined but never called anywhere in the codebase. If this is intended for future Razor hotloading support, consider adding a comment explaining this. If it should be used for stylesheet hotloading, it should be called alongside or instead of DirtyAllStyles().
Implements hot-reloading for stylesheets, similar to S&box's implementation. When running DEBUG builds, modifying
.scssfiles automatically refreshes the UI without restarting the application.Changes
FileWatcher (
src/Sandbox.UI/Utility/FileWatcher.cs)System.IO.FileSystemWatcherwith 200ms debouncingStyleSheet hotloading (
src/Sandbox.UI/Styles/StyleSheet.cs)OnStyleSheetsHotloadedevent and file watcher integration@importincludesUI refresh (
src/Sandbox.UI/Panel/RootPanel.cs,src/Fazor.UI/FazorApplication.cs)DirtyAllStyles()invalidates all panel styles on file changeFazorApplication.RunPanel()Render loop (
src/Fazor.UI/NativeWindow.cs)FileWatcher.Tick()called each frame to process queued changesUsage
All hotloading code is
#if DEBUGguarded for zero release overhead.Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.