diff --git a/InfiniFrame.slnx b/InfiniFrame.slnx index 57b31d637..f682c1114 100644 --- a/InfiniFrame.slnx +++ b/InfiniFrame.slnx @@ -101,6 +101,7 @@ + diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor new file mode 100644 index 000000000..dab47188f --- /dev/null +++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Layouts/MainLayout.razor b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Layouts/MainLayout.razor new file mode 100644 index 000000000..d715941b4 --- /dev/null +++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Layouts/MainLayout.razor @@ -0,0 +1,30 @@ +@using global::MudBlazor +@inherits LayoutComponentBase + + + + + InfiniFrame File Dialog Demo + + + + + + + + @Body + + + + + +@code { + + private bool _drawerOpen = true; + + private void ToggleDrawer() + { + _drawerOpen = !_drawerOpen; + } +} diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Layouts/NavMenu.razor b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Layouts/NavMenu.razor new file mode 100644 index 000000000..9a058f193 --- /dev/null +++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Layouts/NavMenu.razor @@ -0,0 +1,6 @@ +@using global::MudBlazor + + + File Dialogs + + diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor new file mode 100644 index 000000000..053720acd --- /dev/null +++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor @@ -0,0 +1,211 @@ +@page "/" +@using global::MudBlazor +@inject IInfiniFrameWindow Window + +File Dialogs + + + File Dialog Demo + + Test the native file/folder picker dialogs. Results are displayed below each button. + + + + Save File (Async) + + + @if (_saveFileLoading) { + + } + Choose Save Location + + + @if (_saveFileResult is not null) { + + } + + + + Open File (Async) + + + @if (_openFileLoading) { + + } + Choose File(s) + + + @if (_openFileResult is not null) { + + } + + + + Open Folder (Async) + + + @if (_openFolderLoading) { + + } + Choose Folder + + + @if (_openFolderResult is not null) { + + } + + + + + Event Log + + @if (_eventLog.Count == 0) { + No events yet. Click a button above. + } + @foreach (var entry in _eventLog) { + + + @entry + + } + + + +@code { + private bool _saveFileLoading; + private bool _openFileLoading; + private bool _openFolderLoading; + + private string? _saveFileResult; + private string? _openFileResult; + private string? _openFolderResult; + + private readonly List _eventLog = []; + + private async Task OnSaveFileAsync() { + _saveFileLoading = true; + StateHasChanged(); + + try { + LogEvent("Opening Save File dialog..."); + string? result = await Window.ShowSaveFileAsync( + title: "Save File As", + filters: [ + ("Excel Files", ["xlsx", "xls"]), + ("All Files", ["*"]) + ], + defaultFileName: "document.xlsx" + ); + + _saveFileResult = result ?? "(canceled)"; + LogEvent($"Save File result: {result ?? "(canceled)"}"); + } + catch (Exception ex) { + _saveFileResult = $"Error: {ex.Message}"; + LogEvent($"Save File error: {ex.Message}"); + } + finally { + _saveFileLoading = false; + StateHasChanged(); + } + } + + private async Task OnOpenFileAsync() { + _openFileLoading = true; + StateHasChanged(); + + try { + LogEvent("Opening Open File dialog..."); + string?[] results = await Window.ShowOpenFileAsync( + title: "Open File", + multiSelect: true, + filters: [ + ("Image Files", ["png", "jpg", "jpeg", "gif", "bmp"]), + ("Text Files", ["txt", "md"]), + ("All Files", ["*"]) + ] + ); + + _openFileResult = results.Length > 0 + ? string.Join(Environment.NewLine, results) + : "(canceled)"; + LogEvent($"Open File result: {results.Length} file(s) selected"); + } + catch (Exception ex) { + _openFileResult = $"Error: {ex.Message}"; + LogEvent($"Open File error: {ex.Message}"); + } + finally { + _openFileLoading = false; + StateHasChanged(); + } + } + + private async Task OnOpenFolderAsync() { + _openFolderLoading = true; + StateHasChanged(); + + try { + LogEvent("Opening Open Folder dialog..."); + string?[] results = await Window.ShowOpenFolderAsync( + title: "Select Folder", + multiSelect: false + ); + + _openFolderResult = results.Length > 0 + ? results[0] + : "(canceled)"; + LogEvent($"Open Folder result: {_openFolderResult}"); + } + catch (Exception ex) { + _openFolderResult = $"Error: {ex.Message}"; + LogEvent($"Open Folder error: {ex.Message}"); + } + finally { + _openFolderLoading = false; + StateHasChanged(); + } + } + + private void LogEvent(string message) { + _eventLog.Add($"[{DateTime.Now:HH:mm:ss}] {message}"); + if (_eventLog.Count > 50) + _eventLog.RemoveAt(0); + } +} diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/PageNotFound.razor b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/PageNotFound.razor new file mode 100644 index 000000000..4fccfc1b5 --- /dev/null +++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/PageNotFound.razor @@ -0,0 +1,16 @@ +@page "/PageNotFound" +@using global::MudBlazor +@layout MainLayout + +Page Not Found + + + + 404 + + Sorry, there's nothing at this address. + + + Go Home + + diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/_Imports.razor b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/_Imports.razor new file mode 100644 index 000000000..a1ae9f606 --- /dev/null +++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/_Imports.razor @@ -0,0 +1,15 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using Microsoft.Extensions.Logging +@using MudBlazor +@using InfiniFrame +@using InfiniFrame.Blazor +@using InfiniFrameExample.BlazorWebView.MudBlazor.Components +@using InfiniFrameExample.BlazorWebView.MudBlazor.Components.Layouts +@using InfiniFrameExample.BlazorWebView.MudBlazor.Components.Pages diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/InfiniFrameExample.BlazorWebView.MudBlazor.csproj b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/InfiniFrameExample.BlazorWebView.MudBlazor.csproj new file mode 100644 index 000000000..586058394 --- /dev/null +++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/InfiniFrameExample.BlazorWebView.MudBlazor.csproj @@ -0,0 +1,44 @@ + + + net10.0 + WinExe + 14.0 + enable + enable + true + false + true + ../../assets/favicon.ico + + + + + + + + + + + + + + + + + + + wwwroot/favicon.ico + Always + + + + + + + + builds/$(Configuration)/$(TargetFramework)/%(RecursiveDir)%(Filename)%(Extension) + + + diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs new file mode 100644 index 000000000..5b4217353 --- /dev/null +++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs @@ -0,0 +1,63 @@ +// --------------------------------------------------------------------------------------------------------------------- +// Imports +// --------------------------------------------------------------------------------------------------------------------- +using InfiniFrame; +using InfiniFrame.BlazorWebView; +using InfiniFrameExample.BlazorWebView.MudBlazor.Components; +using MudBlazor.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using System.Drawing; + +namespace InfiniFrameExample.BlazorWebView.MudBlazor; +// --------------------------------------------------------------------------------------------------------------------- +// Code +// --------------------------------------------------------------------------------------------------------------------- +public static class Program { + [STAThread] + private static void Main(string[] args) { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Async(static c => c.Console()) + .CreateLogger(); + + try { + Log.Information("Starting InfiniFrame BlazorWebView MudBlazor example..."); + + var appBuilder = InfiniFrameBlazorAppBuilder.CreateDefault(args); + + appBuilder.Services + .AddLogging(config => { + config.ClearProviders(); + config.AddSerilog(); + }) + .AddSerilog(config => { + config.WriteTo.Async(static c => c.Console()) + .MinimumLevel.Debug(); + }) + .AddMudServices(); + + appBuilder.RootComponents.Add("app"); + + appBuilder.WithInfiniFrameWindowBuilder(builder => { + builder + .SetIconFile("wwwroot/favicon.ico") + .SetLocation(new Point(100, 100)) + .SetSize(new Size(800, 600)); + }); + + Log.Information("Building InfiniFrame application..."); + InfiniFrameBlazorApp app = appBuilder.Build(); + + Log.Information("Running application..."); + app.Run(); + } + catch (Exception ex) { + Log.Fatal(ex, "Application terminated unexpectedly"); + } + finally { + Log.CloseAndFlush(); + } + } +} diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/wwwroot/index.html b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/wwwroot/index.html new file mode 100644 index 000000000..dca9b3d8d --- /dev/null +++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/wwwroot/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + +Loading... + +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + diff --git a/src/InfiniFrame/Window/Events/InfiniFrameEvents.cs b/src/InfiniFrame/Window/Events/InfiniFrameEvents.cs index e15722185..106a13772 100644 --- a/src/InfiniFrame/Window/Events/InfiniFrameEvents.cs +++ b/src/InfiniFrame/Window/Events/InfiniFrameEvents.cs @@ -256,7 +256,7 @@ public void OnFileDropped(IntPtr pathsPtr, int count, int x, int y) { var files = new List(count); for (int i = 0; i < count; i++) { IntPtr pathPtr = Marshal.ReadIntPtr(pathsPtr, i * IntPtr.Size); - files.Add(Marshal.PtrToStringAuto(pathPtr) ?? string.Empty); + files.Add(Marshal.PtrToStringUTF8(pathPtr) ?? string.Empty); } var args = new FileDroppedEventArgs(files, new Point(x, y)); diff --git a/src/InfiniFrame/Window/Features/FilePickerDialogs/InfiniFileDialogOperation.cs b/src/InfiniFrame/Window/Features/FilePickerDialogs/InfiniFileDialogOperation.cs index fc20ec5d3..0c0422946 100644 --- a/src/InfiniFrame/Window/Features/FilePickerDialogs/InfiniFileDialogOperation.cs +++ b/src/InfiniFrame/Window/Features/FilePickerDialogs/InfiniFileDialogOperation.cs @@ -150,9 +150,7 @@ private static void Complete(IntPtr context, ulong operationId, int result, int IntPtr[] pointers = new IntPtr[valueCount]; Marshal.Copy(values, pointers, 0, valueCount); resultValues = [ - .. pointers.Select(pointer => OperatingSystem.IsWindows() - ? Marshal.PtrToStringUni(pointer) - : Marshal.PtrToStringUTF8(pointer)) + .. pointers.Select(pointer => Marshal.PtrToStringUTF8(pointer)) ]; }