From 3bf26e15a1366b60b2681d8d0c8d8e1cc1cfd6a8 Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Tue, 12 Mar 2024 08:01:29 -0700 Subject: [PATCH 1/5] Project ID's updated for newer build system and added editorconfig https://github.com/dotnet/project-system/blob/main/docs/opening-with-new-project-system.md --- .editorconfig | 9 +++++++++ WebUI.NET.sln | 43 +++++++++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c69385c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +# All files +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +[*.{cs}] +csharp_new_line_before_open_brace = all diff --git a/WebUI.NET.sln b/WebUI.NET.sln index bedf325..61b2536 100644 --- a/WebUI.NET.sln +++ b/WebUI.NET.sln @@ -1,43 +1,46 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A18A8D0B-6550-4C02-80D8-4D8DCC8A4C26}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUI.NET", "src\WebUI.NET\WebUI.NET.csproj", "{473FADD0-6CEF-4832-9ABC-8370A8539292}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebUI.NET", "src\WebUI.NET\WebUI.NET.csproj", "{473FADD0-6CEF-4832-9ABC-8370A8539292}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUI.NET.Natives", "src\WebUI.NET.Natives\WebUI.NET.Natives.csproj", "{BE09800A-7B7D-44F4-A294-372BBD5C9B9D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebUI.NET.Natives", "src\WebUI.NET.Natives\WebUI.NET.Natives.csproj", "{BE09800A-7B7D-44F4-A294-372BBD5C9B9D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUI.NET.Natives.Secure", "src\WebUI.NET.Natives.Secure\WebUI.NET.Natives.Secure.csproj", "{444F629E-4AFF-4454-A1B2-4F765810321C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebUI.NET.Natives.Secure", "src\WebUI.NET.Natives.Secure\WebUI.NET.Natives.Secure.csproj", "{444F629E-4AFF-4454-A1B2-4F765810321C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{646E5093-39B6-4C21-8EEE-1488488F2366}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "basic_window", "examples\basic_window\basic_window.csproj", "{9BA583A6-1986-402E-BC29-5E054F6224E8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "basic_window", "examples\basic_window\basic_window.csproj", "{9BA583A6-1986-402E-BC29-5E054F6224E8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "basic_window_net48", "examples\basic_window_net48\basic_window_net48.csproj", "{B04F7684-A967-4977-901A-17A336AF02EF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "basic_window_net48", "examples\basic_window_net48\basic_window_net48.csproj", "{B04F7684-A967-4977-901A-17A336AF02EF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "events", "examples\events\events.csproj", "{BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "events", "examples\events\events.csproj", "{BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dynamic_content", "examples\dynamic_content\dynamic_content.csproj", "{A1B64637-4976-41AA-8F56-1D7E58DED082}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dynamic_content", "examples\dynamic_content\dynamic_content.csproj", "{A1B64637-4976-41AA-8F56-1D7E58DED082}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "js_interop", "examples\js_interop\js_interop.csproj", "{6543E52E-FB68-4754-848A-24E9927CC258}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "js_interop", "examples\js_interop\js_interop.csproj", "{6543E52E-FB68-4754-848A-24E9927CC258}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "js_call", "examples\js_call\js_call.csproj", "{085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "js_call", "examples\js_call\js_call.csproj", "{085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "invoke_backend_function", "examples\invoke_backend_function\invoke_backend_function.csproj", "{BF7D75E8-B257-45AD-9DB6-439DA9AB927C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "invoke_backend_function", "examples\invoke_backend_function\invoke_backend_function.csproj", "{BF7D75E8-B257-45AD-9DB6-439DA9AB927C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "text_editor", "examples\text_editor\text_editor.csproj", "{047D6121-AF91-4428-8B92-C31A976E5642}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "text_editor", "examples\text_editor\text_editor.csproj", "{047D6121-AF91-4428-8B92-C31A976E5642}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "native_api", "examples\native_api\native_api.csproj", "{7662158E-8A4E-4904-9656-69B1E2064D06}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "native_api", "examples\native_api\native_api.csproj", "{7662158E-8A4E-4904-9656-69B1E2064D06}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{350DEBCA-D462-4937-A562-D3939B9CEC35}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {473FADD0-6CEF-4832-9ABC-8370A8539292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {473FADD0-6CEF-4832-9ABC-8370A8539292}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -87,6 +90,13 @@ Global {7662158E-8A4E-4904-9656-69B1E2064D06}.Debug|Any CPU.Build.0 = Debug|Any CPU {7662158E-8A4E-4904-9656-69B1E2064D06}.Release|Any CPU.ActiveCfg = Release|Any CPU {7662158E-8A4E-4904-9656-69B1E2064D06}.Release|Any CPU.Build.0 = Release|Any CPU + {6391CD02-5B36-48C0-968F-F9C95ACD5A54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6391CD02-5B36-48C0-968F-F9C95ACD5A54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6391CD02-5B36-48C0-968F-F9C95ACD5A54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6391CD02-5B36-48C0-968F-F9C95ACD5A54}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {473FADD0-6CEF-4832-9ABC-8370A8539292} = {A18A8D0B-6550-4C02-80D8-4D8DCC8A4C26} @@ -101,5 +111,6 @@ Global {BF7D75E8-B257-45AD-9DB6-439DA9AB927C} = {646E5093-39B6-4C21-8EEE-1488488F2366} {047D6121-AF91-4428-8B92-C31A976E5642} = {646E5093-39B6-4C21-8EEE-1488488F2366} {7662158E-8A4E-4904-9656-69B1E2064D06} = {646E5093-39B6-4C21-8EEE-1488488F2366} + {6391CD02-5B36-48C0-968F-F9C95ACD5A54} = {646E5093-39B6-4C21-8EEE-1488488F2366} EndGlobalSection EndGlobal From df4a0a48a530a4f30c74dfef0a2a000561ef9154 Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Tue, 12 Mar 2024 08:35:40 -0700 Subject: [PATCH 2/5] Large additions for supporting standard C# async/await, more standardized code flow paths, performance optimizations See PR #1 for more details --- src/WebUI.NET/Events/Event.cs | 4 +- src/WebUI.NET/Models/CEFSharpImports.cs | 136 ++++++++++++++ src/WebUI.NET/Models/ReflectionHelpers.cs | 70 ++++++++ src/WebUI.NET/WebUI.NET.csproj | 4 +- src/WebUI.NET/Window.cs | 206 ++++++++++++++++++++-- 5 files changed, 406 insertions(+), 14 deletions(-) create mode 100644 src/WebUI.NET/Models/CEFSharpImports.cs create mode 100644 src/WebUI.NET/Models/ReflectionHelpers.cs diff --git a/src/WebUI.NET/Events/Event.cs b/src/WebUI.NET/Events/Event.cs index c65d930..058a378 100644 --- a/src/WebUI.NET/Events/Event.cs +++ b/src/WebUI.NET/Events/Event.cs @@ -162,7 +162,7 @@ internal void ReturnValue(string value) } #if NET7_0_OR_GREATER - private static partial class Natives + internal static partial class Natives { [LibraryImport(Utils.LibraryName, StringMarshalling = StringMarshalling.Utf8, EntryPoint = "webui_interface_get_int_at")] [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] @@ -187,7 +187,7 @@ private static partial class Natives public static partial void WebUIReturn(IntPtr windowHandle, UIntPtr eventId, string content); } #else - private static class Natives + internal static class Natives { [DllImport(Utils.LibraryName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ThrowOnUnmappableChar = false, BestFitMapping = false, diff --git a/src/WebUI.NET/Models/CEFSharpImports.cs b/src/WebUI.NET/Models/CEFSharpImports.cs new file mode 100644 index 0000000..d36e099 --- /dev/null +++ b/src/WebUI.NET/Models/CEFSharpImports.cs @@ -0,0 +1,136 @@ +/* +This code is generally taken directly from or modified from the fantastic CefSharp project (https://github.com/cefsharp/CefSharp) please support: https://github.com/sponsors/amaitland the license: + +// Copyright © The CefSharp Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the name CefSharp nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace WebUI.Models +{ + public static class CEFSharpImports + { + /*https://raw.githubusercontent.com/cefsharp/CefSharp/master/CefSharp/WebBrowserExtensions.cs*/ + /// + /// Function used to encode the params passed to , + /// and + /// + /// Provide your own custom function to perform custom encoding. You can use your choice of JSON encoder here if you should so + /// choose. + /// + /// + /// A function delegate that yields a string. + /// + public static Func EncodeScriptParam { get; set; } = (str) => + { + return str.Replace("\\", "\\\\") + .Replace("'", "\\'") + .Replace("\t", "\\t") + .Replace("\r", "\\r") + .Replace("\n", "\\n"); + }; + /// + /// Checks if the given object is a numerical object. + /// + /// The object to check. + /// + /// True if numeric, otherwise false. + /// + private static bool IsNumeric(this object value) + { + return value is sbyte + || value is byte + || value is short + || value is ushort + || value is int + || value is uint + || value is long + || value is ulong + || value is float + || value is double + || value is decimal; + } + /// + /// Transforms the methodName and arguments into valid Javascript code. Will encapsulate params in single quotes (unless int, + /// uint, etc) + /// + /// The javascript method name to execute. + /// the arguments to be passed as params to the method. + /// + /// The Javascript code. + /// + public static string GetScriptForJavascriptMethodWithArgs(string methodName, object[] args) + { + var stringBuilder = new StringBuilder(); + stringBuilder.Append(methodName); + stringBuilder.Append("("); + + if (args.Length > 0) + { + for (int i = 0; i < args.Length; i++) + { + var obj = args[i]; + if (obj == null) + { + stringBuilder.Append("null"); + } + else if (obj.IsNumeric()) + { + stringBuilder.Append(Convert.ToString(args[i], CultureInfo.InvariantCulture)); + } + else if (obj is bool) + { + stringBuilder.Append(args[i].ToString().ToLowerInvariant()); + } + else + { + stringBuilder.Append("'"); + stringBuilder.Append(EncodeScriptParam(obj.ToString())); + stringBuilder.Append("'"); + } + + stringBuilder.Append(", "); + } + + //Remove the trailing comma + stringBuilder.Remove(stringBuilder.Length - 2, 2); + } + + stringBuilder.Append(")"); + + return stringBuilder.ToString(); + } + } +} diff --git a/src/WebUI.NET/Models/ReflectionHelpers.cs b/src/WebUI.NET/Models/ReflectionHelpers.cs new file mode 100644 index 0000000..6237385 --- /dev/null +++ b/src/WebUI.NET/Models/ReflectionHelpers.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace WebUI.Models +{ + public static class ReflectionHelpers + { + /// Given a lambda expression that calls a method, returns the method info + /// The lambda expression using the method + /// The method in the lambda expression + /// + public static MethodInfo GetMethodInfo(LambdaExpression expression, out object instance) + { + instance = null; + var outermostExpression = expression.Body as MethodCallExpression; + if (outermostExpression != null) + { + Expression> getCallerExpression = Expression>.Lambda>(outermostExpression.Object); + instance = getCallerExpression.Compile()();//need to verify this working + + } + if (outermostExpression is null) + { + if (expression.Body is UnaryExpression ue && ue.Operand is MethodCallExpression me && me.Object is System.Linq.Expressions.ConstantExpression ce && ce.Value is MethodInfo mi) + { + if (me.Arguments.Count > 1) + { + var inst = me.Arguments.ElementAt(1); + if (inst is ConstantExpression ce2) + instance = ce2.Value; + + } + return mi; + } + throw new ArgumentException("Invalid Expression. Expression should consist of a Method call only."); + } + + var method = outermostExpression.Method; + if (method is null) + throw new Exception($"Cannot find method for expression {expression}"); + + return method; + } + + public static (object[] defaultVals, Type[] argTypes) GetArgsForMethod(MethodInfo method) + { + var infoArgs = method.GetParameters(); + var defaultCallArgs = new object[infoArgs.Length]; + var callArgTypes = infoArgs.Select(a => a.ParameterType).ToArray(); + for (var x = 0; x < infoArgs.Length; x++) + { + var infoArg = infoArgs[x]; + if (infoArg.HasDefaultValue) + defaultCallArgs[x] = infoArg.DefaultValue; + else + { + if (!infoArg.ParameterType.IsValueType) + defaultCallArgs[x] = null; + else + defaultCallArgs[x] = Activator.CreateInstance(infoArg.ParameterType); + } + } + return (defaultCallArgs, callArgTypes); + } + } +} diff --git a/src/WebUI.NET/WebUI.NET.csproj b/src/WebUI.NET/WebUI.NET.csproj index 9cd6145..3fea9af 100644 --- a/src/WebUI.NET/WebUI.NET.csproj +++ b/src/WebUI.NET/WebUI.NET.csproj @@ -13,7 +13,7 @@ disable 9999 WebUI - + preview true @@ -59,7 +59,9 @@ + + diff --git a/src/WebUI.NET/Window.cs b/src/WebUI.NET/Window.cs index 925f325..dd13c87 100644 --- a/src/WebUI.NET/Window.cs +++ b/src/WebUI.NET/Window.cs @@ -1,4 +1,4 @@ -/* +/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. @@ -11,16 +11,25 @@ #endif using System; -using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + #if NET7_0_OR_GREATER using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; #endif using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using CommunityToolkit.HighPerformance.Buffers; using WebUI.Events; +using WebUI.Models; namespace WebUI { @@ -122,7 +131,12 @@ internal Window(IntPtr windowHandle, bool isMainInstance = true) { _handle = new WindowHandle(windowHandle, isMainInstance); } - + private class DefaultInvokeHelper : IInvokerHelper + { + public T InvokeWithReturnType(Func action) => action(); + public void InvokeWithVoid(Action action) => action(); + } + public static IInvokerHelper UseSpecificDispatcher; /// /// Creates a new Window instance /// @@ -430,6 +444,171 @@ public void InvokeJavaScript(string js) Natives.WebUIRun(_handle, js); } + + public class WindowConfig + { + public int ScriptEvaulationMaxReturnSize = 1024 * 1024 * 8; + public TimeSpan ScriptEvaluationDefaultTimeout = TimeSpan.FromSeconds(15); + + } + public class JavaScriptException : Exception + { + public JavaScriptException() { } + + public JavaScriptException(string? message) : base(message) { } + + public JavaScriptException(string? message, Exception? innerException) : base(message, innerException) { } + + } + public WindowConfig config = new(); + /// + /// Note for non-valuetype (string, numbers,etc) return types it expects the value to have been returned from javascript in json form (ie JSON.stringify({'my':'obj'}); if you want something to stringify for you see ScriptEvaluateMethod. + /// + /// + /// Note timeout has a resolution of 1 second + /// When true even strings will be run through the deserializer so they must be quoted when returned, when null(default) will try to auto detect and strip off first pair of quotes (if present) when false nothing will be done to the result just returned bare. + /// + public async Task ScriptEvaluate(string javascript, TimeSpan? timeout = null, bool? stringReturnsAreSerialized = null) + { + var time = timeout ?? config.ScriptEvaluationDefaultTimeout; + var result = await BackgroundExecuteScript(javascript, time); + if (stringReturnsAreSerialized != true && typeof(T) == typeof(string)) + { + if (stringReturnsAreSerialized != false && result is string s && s.Length > 2 && s.StartsWith("\"") && s.EndsWith("\"")) //try to autodetect when j + return (T)(object)s.Substring(1, s.Length - 2); + return (T)(object)result; + } + return Newtonsoft.Json.JsonConvert.DeserializeObject(result); + } + + /// + /// Automatically serializes the result so can handle more types by default + /// + /// + /// + /// + /// + public async Task ScriptEvaluateMethod(string javascriptMethod, params object[] args) + { + return await ScriptEvaluate($"return JSON.stringify({CEFSharpImports.GetScriptForJavascriptMethodWithArgs(javascriptMethod, args)});", stringReturnsAreSerialized: true); + + } + + public void RegisterBoundFunction(LambdaExpression methodExpression, String OverrideRegisteredName = null, object OverrideInstance = null) + { + var info = ReflectionHelpers.GetMethodInfo(methodExpression, out var instance); + OverrideInstance ??= instance; + OverrideRegisteredName ??= info.Name; + + if (!info.IsStatic && OverrideInstance == null) + throw new ArgumentException("Method is an instance method but are not able to automatically determine instance please use OverrideInstance arg"); + var id = Natives.WebUIBind(_handle, OverrideRegisteredName, OurFuncCallback); + boundMethods[id] = new MethodBoundInfo { toCall = info, JSName = OverrideRegisteredName, instance = OverrideInstance, ReturnType = info.ReturnType }; + } + /// + /// Takes a function that gets the name of the element clicked + /// + /// + public void RegisterOnClick(Action OnClick, String domId) + { + var id = Natives.WebUIBind(_handle, domId, OurFuncCallback); + boundMethods[id] = new ClickBoundInfo { OnClick = OnClick }; + } + + private static void OurFuncCallback(nint windowHandle, nuint eventType, string element, nuint eventId, nuint bindId) + { + Debug.WriteLine("OurFuncCallback called"); + if (boundMethods.TryGetValue(bindId, out var _boundItem)) + { + if (_boundItem is ClickBoundInfo cb){ + UseSpecificDispatcher.InvokeWithVoid(() => cb.OnClick(element)); + return; + } + var info = _boundItem as MethodBoundInfo; + if (info.defaultCallArgs == null) + (info.defaultCallArgs,info.callArgTypes) = ReflectionHelpers.GetArgsForMethod(info.toCall); + + var args = (object[])info.defaultCallArgs.Clone(); + for (var x = 0; x < info.defaultCallArgs.Length; x++) + { + var ptr = Event.Natives.WebUIGet(windowHandle, eventId, (nuint)x); //we can get everything as strings and do the conversions ourselves, if an arg doesn't exist it just returns an empty string + var str = Marshal.PtrToStringAnsi(ptr); + var typ = info.callArgTypes[x]; + if (typ == typeof(string)) + args[x] = str; + else if (!String.IsNullOrWhiteSpace(str)) + { + if (typ == typeof(int) && int.TryParse(str, out var pi)) + args[x] = pi; + else if (typ == typeof(double) && double.TryParse(str, out var pd)) + args[x] = pd; + else if (typ == typeof(float) && float.TryParse(str, out var pf)) + args[x] = pf; + else if (typ == typeof(decimal) && decimal.TryParse(str, out var pde)) + args[x] = pde; + else if (typ == typeof(char)) + args[x] = str.FirstOrDefault(); + + } + + } + if (info.ReturnType != typeof(void)) + { + var callMethod = UseSpecificDispatcher.GetType().GetMethod("InvokeWithReturnType").MakeGenericMethod(typeof(object));//info.ReturnType); + var result = callMethod.Invoke(UseSpecificDispatcher, new object[] { () => info.toCall.Invoke(info.instance, args) }); + if (result is Task T){ + T.Wait(); //ewww even though it returns a promise it doesnt have a deferral + result = result.GetType().GetProperty("Result").GetValue(result); + } + Event.Natives.WebUIReturn(windowHandle, eventId, result.ToString()); + } else + UseSpecificDispatcher.InvokeWithVoid(() => info.toCall.Invoke(info.instance, args)); + } + } + public interface IInvokerHelper + { + T InvokeWithReturnType(Func action); + void InvokeWithVoid(Action action); + } + private class MethodBoundInfo : IBoundInfo + { + public MethodInfo toCall; + public object instance; + public object[] defaultCallArgs; + public Type[] callArgTypes; + public string JSName; + public Type ReturnType; + } + private class ClickBoundInfo : IBoundInfo { + public Action OnClick; + } + private interface IBoundInfo{ } + private static ConcurrentDictionary boundMethods = new(); + + private unsafe Task BackgroundExecuteScript(string javascript, TimeSpan timeout) + { + var it = Task.Run(() => + { + using var buffer = MemoryOwner.Allocate(config.ScriptEvaulationMaxReturnSize); // this is a memory pool, I promise:) + var res = Script(javascript, (uint)Math.Round(timeout.TotalSeconds), buffer.Span); + fixed (byte* ptr = buffer.Span) + { + var str = Encoding.UTF8.GetString(ptr, buffer.Span.IndexOf((byte)0)); + if (!res) + throw new JavaScriptException(str); + return str; + } + } + ); + return it; + } + + public unsafe bool Script(string javaScript, uint timeout_secs, Span buffer) + { + fixed (byte* ptr = buffer) + return Natives.WebUIScript(_handle, javaScript, (UIntPtr)timeout_secs, ptr, (UIntPtr)buffer.Length); + + } /// /// Runs JavaScript and stores the result in /// @@ -791,12 +970,10 @@ public static void WebUISendRaw(WindowHandle windowHandle, string function, { WebUISendRaw(windowHandle, function, notNullPointer.AddrOfPinnedObject(), length); } - } - catch + } catch { // If an exception throws it will most likely be from Marshal.Copy and there will be nothing to handle - } - finally + } finally { pinnedDataPointer?.Free(); } @@ -861,6 +1038,11 @@ public static partial void WebUISetPublic(WindowHandle windowHandle, [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] public static partial void WebUIRun(WindowHandle windowHandle, string javaScript); + [LibraryImport(Utils.LibraryName, StringMarshalling = StringMarshalling.Utf8, EntryPoint = "webui_script")] + [return: MarshalAs(UnmanagedType.I1)] + public static unsafe partial bool WebUIScript(WindowHandle windowHandle, string javaScript, UIntPtr timeout, byte* data, UIntPtr length); + + [LibraryImport(Utils.LibraryName, StringMarshalling = StringMarshalling.Utf8, EntryPoint = "webui_script")] [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] [return: MarshalAs(UnmanagedType.I1)] @@ -879,12 +1061,10 @@ public static bool WebUIRun(WindowHandle windowHandle, string javaScript, UIntPt Marshal.Copy(buffer, dataPointer, 0, (int)length); return result; - } - catch + } catch { return false; - } - finally + } finally { Utils.Free(buffer); } @@ -1069,6 +1249,10 @@ public static extern void WebUISetPublic(WindowHandle windowHandle, public static extern bool WebUIRun(WindowHandle windowHandle, string javaScript, UIntPtr timeout, [MarshalAs(UnmanagedType.LPArray), Out] byte[] data, UIntPtr length); + [return: MarshalAs(UnmanagedType.I1)] + public unsafe static extern bool WebUIScript(WindowHandle windowHandle, string javaScript, UIntPtr timeout, + byte* data, UIntPtr length); + [DllImport(Utils.LibraryName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ThrowOnUnmappableChar = false, BestFitMapping = false, EntryPoint = "webui_set_runtime")] From a6505102d1fa32f4fb0ba19403d993c4e2218854 Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Tue, 12 Mar 2024 08:37:28 -0700 Subject: [PATCH 3/5] WPF Project showing off the new features --- WebUI.NET.sln | 180 +++++++++--------- examples/WpfControllerApp/App.xaml | 9 + examples/WpfControllerApp/App.xaml.cs | 12 ++ examples/WpfControllerApp/AssemblyInfo.cs | 10 + examples/WpfControllerApp/MainWindow.xaml | 20 ++ examples/WpfControllerApp/MainWindow.xaml.cs | 113 +++++++++++ examples/WpfControllerApp/NativeMethods.txt | 7 + .../WpfControllerApp/WpfControllerApp.csproj | 50 +++++ examples/WpfControllerApp/index.html | 39 ++++ 9 files changed, 351 insertions(+), 89 deletions(-) create mode 100644 examples/WpfControllerApp/App.xaml create mode 100644 examples/WpfControllerApp/App.xaml.cs create mode 100644 examples/WpfControllerApp/AssemblyInfo.cs create mode 100644 examples/WpfControllerApp/MainWindow.xaml create mode 100644 examples/WpfControllerApp/MainWindow.xaml.cs create mode 100644 examples/WpfControllerApp/NativeMethods.txt create mode 100644 examples/WpfControllerApp/WpfControllerApp.csproj create mode 100644 examples/WpfControllerApp/index.html diff --git a/WebUI.NET.sln b/WebUI.NET.sln index 61b2536..10883a3 100644 --- a/WebUI.NET.sln +++ b/WebUI.NET.sln @@ -1,95 +1,97 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A18A8D0B-6550-4C02-80D8-4D8DCC8A4C26}" -EndProject +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A18A8D0B-6550-4C02-80D8-4D8DCC8A4C26}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebUI.NET", "src\WebUI.NET\WebUI.NET.csproj", "{473FADD0-6CEF-4832-9ABC-8370A8539292}" -EndProject +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebUI.NET.Natives", "src\WebUI.NET.Natives\WebUI.NET.Natives.csproj", "{BE09800A-7B7D-44F4-A294-372BBD5C9B9D}" -EndProject +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebUI.NET.Natives.Secure", "src\WebUI.NET.Natives.Secure\WebUI.NET.Natives.Secure.csproj", "{444F629E-4AFF-4454-A1B2-4F765810321C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{646E5093-39B6-4C21-8EEE-1488488F2366}" -EndProject +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{646E5093-39B6-4C21-8EEE-1488488F2366}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "basic_window", "examples\basic_window\basic_window.csproj", "{9BA583A6-1986-402E-BC29-5E054F6224E8}" -EndProject +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "basic_window_net48", "examples\basic_window_net48\basic_window_net48.csproj", "{B04F7684-A967-4977-901A-17A336AF02EF}" -EndProject +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "events", "examples\events\events.csproj", "{BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}" -EndProject +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dynamic_content", "examples\dynamic_content\dynamic_content.csproj", "{A1B64637-4976-41AA-8F56-1D7E58DED082}" -EndProject +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "js_interop", "examples\js_interop\js_interop.csproj", "{6543E52E-FB68-4754-848A-24E9927CC258}" -EndProject +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "js_call", "examples\js_call\js_call.csproj", "{085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}" -EndProject +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "invoke_backend_function", "examples\invoke_backend_function\invoke_backend_function.csproj", "{BF7D75E8-B257-45AD-9DB6-439DA9AB927C}" -EndProject +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "text_editor", "examples\text_editor\text_editor.csproj", "{047D6121-AF91-4428-8B92-C31A976E5642}" -EndProject +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "native_api", "examples\native_api\native_api.csproj", "{7662158E-8A4E-4904-9656-69B1E2064D06}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{350DEBCA-D462-4937-A562-D3939B9CEC35}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {473FADD0-6CEF-4832-9ABC-8370A8539292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {473FADD0-6CEF-4832-9ABC-8370A8539292}.Debug|Any CPU.Build.0 = Debug|Any CPU - {473FADD0-6CEF-4832-9ABC-8370A8539292}.Release|Any CPU.ActiveCfg = Release|Any CPU - {473FADD0-6CEF-4832-9ABC-8370A8539292}.Release|Any CPU.Build.0 = Release|Any CPU - {BE09800A-7B7D-44F4-A294-372BBD5C9B9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE09800A-7B7D-44F4-A294-372BBD5C9B9D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE09800A-7B7D-44F4-A294-372BBD5C9B9D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE09800A-7B7D-44F4-A294-372BBD5C9B9D}.Release|Any CPU.Build.0 = Release|Any CPU - {444F629E-4AFF-4454-A1B2-4F765810321C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {444F629E-4AFF-4454-A1B2-4F765810321C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {444F629E-4AFF-4454-A1B2-4F765810321C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {444F629E-4AFF-4454-A1B2-4F765810321C}.Release|Any CPU.Build.0 = Release|Any CPU - {9BA583A6-1986-402E-BC29-5E054F6224E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BA583A6-1986-402E-BC29-5E054F6224E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BA583A6-1986-402E-BC29-5E054F6224E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BA583A6-1986-402E-BC29-5E054F6224E8}.Release|Any CPU.Build.0 = Release|Any CPU - {B04F7684-A967-4977-901A-17A336AF02EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B04F7684-A967-4977-901A-17A336AF02EF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B04F7684-A967-4977-901A-17A336AF02EF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B04F7684-A967-4977-901A-17A336AF02EF}.Release|Any CPU.Build.0 = Release|Any CPU - {BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}.Release|Any CPU.Build.0 = Release|Any CPU - {A1B64637-4976-41AA-8F56-1D7E58DED082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1B64637-4976-41AA-8F56-1D7E58DED082}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1B64637-4976-41AA-8F56-1D7E58DED082}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1B64637-4976-41AA-8F56-1D7E58DED082}.Release|Any CPU.Build.0 = Release|Any CPU - {6543E52E-FB68-4754-848A-24E9927CC258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6543E52E-FB68-4754-848A-24E9927CC258}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6543E52E-FB68-4754-848A-24E9927CC258}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6543E52E-FB68-4754-848A-24E9927CC258}.Release|Any CPU.Build.0 = Release|Any CPU - {085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}.Release|Any CPU.Build.0 = Release|Any CPU - {BF7D75E8-B257-45AD-9DB6-439DA9AB927C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BF7D75E8-B257-45AD-9DB6-439DA9AB927C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BF7D75E8-B257-45AD-9DB6-439DA9AB927C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BF7D75E8-B257-45AD-9DB6-439DA9AB927C}.Release|Any CPU.Build.0 = Release|Any CPU - {047D6121-AF91-4428-8B92-C31A976E5642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {047D6121-AF91-4428-8B92-C31A976E5642}.Debug|Any CPU.Build.0 = Debug|Any CPU - {047D6121-AF91-4428-8B92-C31A976E5642}.Release|Any CPU.ActiveCfg = Release|Any CPU - {047D6121-AF91-4428-8B92-C31A976E5642}.Release|Any CPU.Build.0 = Release|Any CPU - {7662158E-8A4E-4904-9656-69B1E2064D06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7662158E-8A4E-4904-9656-69B1E2064D06}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7662158E-8A4E-4904-9656-69B1E2064D06}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7662158E-8A4E-4904-9656-69B1E2064D06}.Release|Any CPU.Build.0 = Release|Any CPU +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfControllerApp", "examples\WpfControllerApp\WpfControllerApp.csproj", "{6391CD02-5B36-48C0-968F-F9C95ACD5A54}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {473FADD0-6CEF-4832-9ABC-8370A8539292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {473FADD0-6CEF-4832-9ABC-8370A8539292}.Debug|Any CPU.Build.0 = Debug|Any CPU + {473FADD0-6CEF-4832-9ABC-8370A8539292}.Release|Any CPU.ActiveCfg = Release|Any CPU + {473FADD0-6CEF-4832-9ABC-8370A8539292}.Release|Any CPU.Build.0 = Release|Any CPU + {BE09800A-7B7D-44F4-A294-372BBD5C9B9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE09800A-7B7D-44F4-A294-372BBD5C9B9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE09800A-7B7D-44F4-A294-372BBD5C9B9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE09800A-7B7D-44F4-A294-372BBD5C9B9D}.Release|Any CPU.Build.0 = Release|Any CPU + {444F629E-4AFF-4454-A1B2-4F765810321C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {444F629E-4AFF-4454-A1B2-4F765810321C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {444F629E-4AFF-4454-A1B2-4F765810321C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {444F629E-4AFF-4454-A1B2-4F765810321C}.Release|Any CPU.Build.0 = Release|Any CPU + {9BA583A6-1986-402E-BC29-5E054F6224E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BA583A6-1986-402E-BC29-5E054F6224E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BA583A6-1986-402E-BC29-5E054F6224E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BA583A6-1986-402E-BC29-5E054F6224E8}.Release|Any CPU.Build.0 = Release|Any CPU + {B04F7684-A967-4977-901A-17A336AF02EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B04F7684-A967-4977-901A-17A336AF02EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B04F7684-A967-4977-901A-17A336AF02EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B04F7684-A967-4977-901A-17A336AF02EF}.Release|Any CPU.Build.0 = Release|Any CPU + {BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}.Release|Any CPU.Build.0 = Release|Any CPU + {A1B64637-4976-41AA-8F56-1D7E58DED082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B64637-4976-41AA-8F56-1D7E58DED082}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B64637-4976-41AA-8F56-1D7E58DED082}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B64637-4976-41AA-8F56-1D7E58DED082}.Release|Any CPU.Build.0 = Release|Any CPU + {6543E52E-FB68-4754-848A-24E9927CC258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6543E52E-FB68-4754-848A-24E9927CC258}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6543E52E-FB68-4754-848A-24E9927CC258}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6543E52E-FB68-4754-848A-24E9927CC258}.Release|Any CPU.Build.0 = Release|Any CPU + {085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}.Release|Any CPU.Build.0 = Release|Any CPU + {BF7D75E8-B257-45AD-9DB6-439DA9AB927C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF7D75E8-B257-45AD-9DB6-439DA9AB927C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF7D75E8-B257-45AD-9DB6-439DA9AB927C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF7D75E8-B257-45AD-9DB6-439DA9AB927C}.Release|Any CPU.Build.0 = Release|Any CPU + {047D6121-AF91-4428-8B92-C31A976E5642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {047D6121-AF91-4428-8B92-C31A976E5642}.Debug|Any CPU.Build.0 = Debug|Any CPU + {047D6121-AF91-4428-8B92-C31A976E5642}.Release|Any CPU.ActiveCfg = Release|Any CPU + {047D6121-AF91-4428-8B92-C31A976E5642}.Release|Any CPU.Build.0 = Release|Any CPU + {7662158E-8A4E-4904-9656-69B1E2064D06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7662158E-8A4E-4904-9656-69B1E2064D06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7662158E-8A4E-4904-9656-69B1E2064D06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7662158E-8A4E-4904-9656-69B1E2064D06}.Release|Any CPU.Build.0 = Release|Any CPU {6391CD02-5B36-48C0-968F-F9C95ACD5A54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6391CD02-5B36-48C0-968F-F9C95ACD5A54}.Debug|Any CPU.Build.0 = Debug|Any CPU {6391CD02-5B36-48C0-968F-F9C95ACD5A54}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -97,20 +99,20 @@ Global EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {473FADD0-6CEF-4832-9ABC-8370A8539292} = {A18A8D0B-6550-4C02-80D8-4D8DCC8A4C26} - {BE09800A-7B7D-44F4-A294-372BBD5C9B9D} = {A18A8D0B-6550-4C02-80D8-4D8DCC8A4C26} - {444F629E-4AFF-4454-A1B2-4F765810321C} = {A18A8D0B-6550-4C02-80D8-4D8DCC8A4C26} - {9BA583A6-1986-402E-BC29-5E054F6224E8} = {646E5093-39B6-4C21-8EEE-1488488F2366} - {B04F7684-A967-4977-901A-17A336AF02EF} = {646E5093-39B6-4C21-8EEE-1488488F2366} - {BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5} = {646E5093-39B6-4C21-8EEE-1488488F2366} - {A1B64637-4976-41AA-8F56-1D7E58DED082} = {646E5093-39B6-4C21-8EEE-1488488F2366} - {6543E52E-FB68-4754-848A-24E9927CC258} = {646E5093-39B6-4C21-8EEE-1488488F2366} - {085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B} = {646E5093-39B6-4C21-8EEE-1488488F2366} - {BF7D75E8-B257-45AD-9DB6-439DA9AB927C} = {646E5093-39B6-4C21-8EEE-1488488F2366} - {047D6121-AF91-4428-8B92-C31A976E5642} = {646E5093-39B6-4C21-8EEE-1488488F2366} - {7662158E-8A4E-4904-9656-69B1E2064D06} = {646E5093-39B6-4C21-8EEE-1488488F2366} + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {473FADD0-6CEF-4832-9ABC-8370A8539292} = {A18A8D0B-6550-4C02-80D8-4D8DCC8A4C26} + {BE09800A-7B7D-44F4-A294-372BBD5C9B9D} = {A18A8D0B-6550-4C02-80D8-4D8DCC8A4C26} + {444F629E-4AFF-4454-A1B2-4F765810321C} = {A18A8D0B-6550-4C02-80D8-4D8DCC8A4C26} + {9BA583A6-1986-402E-BC29-5E054F6224E8} = {646E5093-39B6-4C21-8EEE-1488488F2366} + {B04F7684-A967-4977-901A-17A336AF02EF} = {646E5093-39B6-4C21-8EEE-1488488F2366} + {BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5} = {646E5093-39B6-4C21-8EEE-1488488F2366} + {A1B64637-4976-41AA-8F56-1D7E58DED082} = {646E5093-39B6-4C21-8EEE-1488488F2366} + {6543E52E-FB68-4754-848A-24E9927CC258} = {646E5093-39B6-4C21-8EEE-1488488F2366} + {085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B} = {646E5093-39B6-4C21-8EEE-1488488F2366} + {BF7D75E8-B257-45AD-9DB6-439DA9AB927C} = {646E5093-39B6-4C21-8EEE-1488488F2366} + {047D6121-AF91-4428-8B92-C31A976E5642} = {646E5093-39B6-4C21-8EEE-1488488F2366} + {7662158E-8A4E-4904-9656-69B1E2064D06} = {646E5093-39B6-4C21-8EEE-1488488F2366} {6391CD02-5B36-48C0-968F-F9C95ACD5A54} = {646E5093-39B6-4C21-8EEE-1488488F2366} - EndGlobalSection -EndGlobal + EndGlobalSection +EndGlobal diff --git a/examples/WpfControllerApp/App.xaml b/examples/WpfControllerApp/App.xaml new file mode 100644 index 0000000..f47ddce --- /dev/null +++ b/examples/WpfControllerApp/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/examples/WpfControllerApp/App.xaml.cs b/examples/WpfControllerApp/App.xaml.cs new file mode 100644 index 0000000..eba3ae6 --- /dev/null +++ b/examples/WpfControllerApp/App.xaml.cs @@ -0,0 +1,12 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace WpfControllerApp { + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application { + } + +} diff --git a/examples/WpfControllerApp/AssemblyInfo.cs b/examples/WpfControllerApp/AssemblyInfo.cs new file mode 100644 index 0000000..d9a1bde --- /dev/null +++ b/examples/WpfControllerApp/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/examples/WpfControllerApp/MainWindow.xaml b/examples/WpfControllerApp/MainWindow.xaml new file mode 100644 index 0000000..07986c2 --- /dev/null +++ b/examples/WpfControllerApp/MainWindow.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + From b0d0ead02b216d81be929b73e8caca5ea6e06dc9 Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Wed, 20 Mar 2024 13:29:40 -0700 Subject: [PATCH 4/5] Updated editor config to fix new line items --- .editorconfig | 131 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index c69385c..24442bb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,135 @@ root = true [*] indent_style = space indent_size = 4 -end_of_line = lf +end_of_line = lf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion [*.{cs}] csharp_new_line_before_open_brace = all + +[*.cs] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:suggestion +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_indent_labels = no_change +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_else = true + +[*.vb] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case From 85ef6eb3b483813f63462a0ed7190a010a9f21e7 Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Wed, 20 Mar 2024 13:32:38 -0700 Subject: [PATCH 5/5] Switch to a new internal function/event registration system --- examples/WpfControllerApp/MainWindow.xaml.cs | 54 ++++- examples/WpfControllerApp/index.html | 5 +- src/WebUI.NET/MilisecondEpochConverter.cs | 64 ++++++ src/WebUI.NET/WebUI.NET.csproj | 6 + src/WebUI.NET/WebUINetUI.cs | 117 ++++++++++ src/WebUI.NET/Window.cs | 213 +++++++++++-------- src/WebUI.NET/webui_net.js | 106 +++++++++ 7 files changed, 462 insertions(+), 103 deletions(-) create mode 100644 src/WebUI.NET/MilisecondEpochConverter.cs create mode 100644 src/WebUI.NET/WebUINetUI.cs create mode 100644 src/WebUI.NET/webui_net.js diff --git a/examples/WpfControllerApp/MainWindow.xaml.cs b/examples/WpfControllerApp/MainWindow.xaml.cs index a378833..5808dda 100644 --- a/examples/WpfControllerApp/MainWindow.xaml.cs +++ b/examples/WpfControllerApp/MainWindow.xaml.cs @@ -6,6 +6,7 @@ using WebUI; using Windows.Win32; using Windows.Win32.UI.Input.KeyboardAndMouse; +using kvp=System.Collections.Generic.KeyValuePair; namespace WpfControllerApp { @@ -38,22 +39,57 @@ public WinDispatcher(Dispatcher dispatcher) public void InvokeWithVoid(Action action) => dispatcher.Invoke(action); } - private void MainWindow_Loaded(object sender, RoutedEventArgs e) + private async void MainWindow_Loaded(object sender, RoutedEventArgs e) { WebUI.Window.UseSpecificDispatcher = new WinDispatcher(Dispatcher); window = new(new WebUI.WindowProperties { Width = 640, Height = 480, X = 800, Y = 50 }); var evts = window.RegisterDefaultEventHandler(); - evts.OnDisconnect += (_) => + + //evts.OnDisconnect += (_) => + //{ + // window = null; + // Dispatcher.InvokeAsync(() => Close()); + //}; + + window.InitOurBridge(); + //window.Show("Hi Bob"); + //await Task.Delay(2000); + //btnDevTools_Click(null, null); + // await Task.Delay(2000); + + evts.OnConnect += (window) => { - window = null; - Dispatcher.InvokeAsync(() => Close()); + //this.window = window; + NewConnect(); }; + + + window.Show("index.html"); - window.RegisterOnClick(JSClickedButton, "TestButton"); - window.RegisterBoundFunction(() => MyTest_function); + //window.RegisterBoundFunction(() => MyTest_function); } + private async void NewConnect() + { + window.SetDebug(); + window.AddEventListener(JSClickedButton, CommonEventTypes.click, "TestButton"); + window.RegisterBoundFunction("MyTest_function", RawMyTestCalled); + window.AddEventListener(JSDblClickAny,CommonEventTypes.dblclick,"window",null,new kvp[]{new("button","button"), new("X Pos","clientX"), new("Y Pos","clientY") }); + + } + + private void JSDblClickAny(DomEvent evt) + { + LogItem($"DoubleClick event: " + Newtonsoft.Json.JsonConvert.SerializeObject(evt, Newtonsoft.Json.Formatting.Indented).Replace("\n","\t\n")); + } + + private Task RawMyTestCalled(string serialized) + { + var args = Newtonsoft.Json.JsonConvert.DeserializeObject(serialized); + return MyTest_function(args[0], args[1], double.Parse(args[2])); + } + private async Task MyTest_function(String arg1, String arg2, double arg3) { var str = $"I am called: {arg1}({arg1.GetType()}) and {arg2}({arg2.GetType()}) {arg3}({arg3.GetType()})"; @@ -61,9 +97,9 @@ private async Task MyTest_function(String arg1, String arg2, double arg3 await Task.Delay(500); return "Nice Call " + arg1; } - private void JSClickedButton(String elem) + private void JSClickedButton(DomEvent evt) { - LogItem($"Got a click from JS for: {elem}"); + LogItem($"Got a click from JS for: {evt.originalTargetId}"); } private void LogItem(object val, [CallerArgumentExpression(nameof(val))] string expression = "unknown") @@ -74,7 +110,7 @@ private void LogItem(object val, [CallerArgumentExpression(nameof(val))] string double or decimal or float or char or bool or int or string => val.ToString(), _ => "Serialized as: " + Newtonsoft.Json.JsonConvert.SerializeObject(val, Newtonsoft.Json.Formatting.None) }; - txtLog.Text += $"{expression} => {valStr} ({val.GetType()})\n"; + txtLog.Text += $"{expression} => {valStr.Replace("\r","")} ({val.GetType()})\n"; } private async void btnTest1_Click(object sender, RoutedEventArgs e) { diff --git a/examples/WpfControllerApp/index.html b/examples/WpfControllerApp/index.html index 27aabdc..b3eae49 100644 --- a/examples/WpfControllerApp/index.html +++ b/examples/WpfControllerApp/index.html @@ -3,7 +3,8 @@ - + + JS Interop