diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..24442bb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,138 @@ +root = true + +# All files +[*] +indent_style = space +indent_size = 4 +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 diff --git a/WebUI.NET.sln b/WebUI.NET.sln index bedf325..10883a3 100644 --- a/WebUI.NET.sln +++ b/WebUI.NET.sln @@ -1,105 +1,118 @@ -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}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}" -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}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}" -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 - {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 - 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 -EndGlobal + +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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebUI.NET", "src\WebUI.NET\WebUI.NET.csproj", "{473FADD0-6CEF-4832-9ABC-8370A8539292}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebUI.NET.Natives", "src\WebUI.NET.Natives\WebUI.NET.Natives.csproj", "{BE09800A-7B7D-44F4-A294-372BBD5C9B9D}" +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 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "basic_window", "examples\basic_window\basic_window.csproj", "{9BA583A6-1986-402E-BC29-5E054F6224E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "basic_window_net48", "examples\basic_window_net48\basic_window_net48.csproj", "{B04F7684-A967-4977-901A-17A336AF02EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "events", "examples\events\events.csproj", "{BE2C15B8-EEB9-46E3-BA90-06432CD1C9D5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dynamic_content", "examples\dynamic_content\dynamic_content.csproj", "{A1B64637-4976-41AA-8F56-1D7E58DED082}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "js_interop", "examples\js_interop\js_interop.csproj", "{6543E52E-FB68-4754-848A-24E9927CC258}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "js_call", "examples\js_call\js_call.csproj", "{085C4587-AB5B-4ECC-9CD8-E8C3D9ED0F2B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "invoke_backend_function", "examples\invoke_backend_function\invoke_backend_function.csproj", "{BF7D75E8-B257-45AD-9DB6-439DA9AB927C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "text_editor", "examples\text_editor\text_editor.csproj", "{047D6121-AF91-4428-8B92-C31A976E5642}" +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 +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 + {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} + {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 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 @@ + + + + + + + + + + + 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/MilisecondEpochConverter.cs b/src/WebUI.NET/MilisecondEpochConverter.cs new file mode 100644 index 0000000..8b0ea5e --- /dev/null +++ b/src/WebUI.NET/MilisecondEpochConverter.cs @@ -0,0 +1,64 @@ +using System; +using Newtonsoft.Json; + +namespace WebUI +{ + public class MilisecondEpochConverter : RandomNumberTimeConverter + { + private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public MilisecondEpochConverter() : base(long.Parse) { } + + public static DateTime ParseLong(long val) => _epoch.AddMilliseconds(val); + + protected override DateTime ParseValue(long val) => ParseLong(val); + + protected override string WriteValue(DateTime when) + { + return ((long)(when - _epoch).TotalMilliseconds).ToString(); + } + public override bool CanConvert(Type objectType) + { + if (objectType == typeof(DateTime) || objectType == typeof(DateTime?)) + { + return true; + } + + if (objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?)) + { + return true; + } + + return false; + } + } + public abstract class RandomNumberTimeConverter : JsonConverter + { + protected RandomNumberTimeConverter(Func ParseNotNullStringHandler) + { + this.ParseNotNullStringHandler = ParseNotNullStringHandler; + } + Func ParseNotNullStringHandler; + + protected abstract SOURCE_TYPE ParseValue(T val); + protected abstract String WriteValue(SOURCE_TYPE when); + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteRawValue(WriteValue((SOURCE_TYPE)value)); + + + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.Value == null) { return null; } + T val; + if (reader.TokenType == JsonToken.String) + val = ParseNotNullStringHandler((string)reader.Value); + else + val = (T)Convert.ChangeType(reader.Value, typeof(T)); + return ParseValue(val); + } + } +} 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..0d96d9b 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,15 @@ + + + + + + + PreserveNewest + diff --git a/src/WebUI.NET/WebUINetUI.cs b/src/WebUI.NET/WebUINetUI.cs new file mode 100644 index 0000000..5171de6 --- /dev/null +++ b/src/WebUI.NET/WebUINetUI.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace WebUI +{ + + public class DomEvent + { + + public string currentTargetId { get; set; } + public string originalTargetId { get; set; } + [JsonConverter(typeof(MilisecondEpochConverter))] + public DateTime timestamp { get; set; } + public string type { get; set; } + public Dictionary additionalProps; //map of additional property values requested + + } + public enum CommonEventTypes { load, unload, error, resize, scroll, focus, blur, click, dblclick, mousedown, mouseup, mouseover, mouseout, mousemove, input, keydown, keypress, keyup, submit, change, DOMNodeInserted, DOMNodeRemoved, DOMSubtreeModified, DOMNodeInsertedIntoDocument, DOMNodeRemovedFromDocument, DOMContentLoaded, hashchange, beforeunload } + public static class WebUINetUI + { + + public class EventOpts + { + public bool capture { get; set; } + public bool once { get; set; } + public string abortKey { get; set; } + + } + private const int startId = 5432; + private static volatile int curFuncId = startId; + // CommonEventTypes + public static void AddEventListener(this Window window, Action OnFired, CommonEventTypes eventType, String domId, EventOpts additionalOpts = null, params KeyValuePair[] additionalEventCaptureProps) => window.AddEventListener(OnFired, eventType.ToString(), domId, additionalOpts, additionalEventCaptureProps); + + public static void AddEventListener(this Window window, Action OnFired, String eventType, String domId, EventOpts additionalOpts = null, params KeyValuePair[] additionalEventCaptureProps) + { + + //addCSEventListener(type, elementID, csFuncID, additionalProps, options, abortKey) + var addlDict = additionalEventCaptureProps?.Length > 0 ? + Newtonsoft.Json.JsonConvert.SerializeObject( additionalEventCaptureProps.ToDictionary(x => x.Key, x => x.Value)) : null; + var abortKey = additionalOpts?.abortKey; + if (abortKey != null) + additionalOpts.abortKey = null; + var curId = window._RegisterBoundFunction(OnCalled: (str) => OnFired(Newtonsoft.Json.JsonConvert.DeserializeObject(str)[0]), registerFunc: false); + //window.InvokeJavascriptMethod($"{ourJSClass}.addCSEventListener", eventType, domId, curId, addlDict, additionalOpts, abortKey); + window.ScriptEvaluateMethod($"{ourJSClass}.addCSEventListener", eventType, domId, curId, addlDict, additionalOpts, abortKey);//not awiating but at least will be in background task exception + } + private static async void JavascriptFuncCallback(this Window window, int csFuncId, int jsCallId, string jsonOfArgs) + { + if (!csFuncIdToFired.TryGetValue(csFuncId, out var info)) + throw new Exception($"The JS func callback handler was passed an invalid function id {csFuncId}"); ; + if (info.normFunc != null) + { + Window.UseSpecificDispatcher.InvokeWithVoid(() => info.normFunc(jsonOfArgs)); + return; + } + var res = await Window.UseSpecificDispatcher.InvokeWithReturnType(() => info.taskFunc(jsonOfArgs)); + + await window.ScriptEvaluateMethod($"{ourJSClass}.setCSFunctionResult", jsCallId, res);//we don't use invoke so we can get the error + //window.InvokeJavascriptMethod("setCSFunctionResult", jsCallId, res); + + } + private const string ourJSClass = $"window.WebUINet"; + public static void SetDebug(this Window window, bool debugging = true) + { + var jsDebug = debugging ? "true" : "false"; + window.InvokeJavaScript($@"{ourJSClass}.DEBUG_MODE={jsDebug}; +window.webui.setLogging({jsDebug}); +"); + } + public static void RegisterBoundFunction(this Window window, String RegisteredName, Func> OnCalled) => window._RegisterBoundFunction(RegisteredName, OnCalledTask: OnCalled); + public static void RegisterBoundFunction(this Window window, String RegisteredName, Action OnCalled) => window._RegisterBoundFunction(RegisteredName, OnCalled); + + private static int _RegisterBoundFunction(this Window window, String RegisteredName = null, Action OnCalled = null, Func> OnCalledTask = null, bool registerFunc = true) + { + var curId = Interlocked.Increment(ref curFuncId); + csFuncIdToFired[curId] = new RegisteredFunc { taskFunc = OnCalledTask, normFunc = OnCalled }; + if (registerFunc) + { + //we don't use invoke so we can get the error + window.ScriptEvaluateMethod($"{ourJSClass}.addCSFunction", curId, RegisteredName, OnCalledTask != null); //not awaiting but exception will at least show in logs just no ST + //window.InvokeJavascriptMethod("WebUINet.addCSFunction", curId, RegisteredName, OnCalledTask != null); + } + if (curId == startId + 1) + { + window.RegisterEventHandler(RawFuncCallback, "webuiNet_Callback"); + } + return curId; + } + public static void InitOurBridge(this Window window) + { + OurJSBytes = Encoding.UTF8.GetBytes(File.ReadAllText("webui_net.js")); + window.SetFileHandler((path) => path == "/webui_net.js" ? OurJSBytes : null); + } + private static byte[] OurJSBytes; + private static object RawFuncCallback(WebUI.Events.Event evt, string element, ulong handlerId) + { + evt.Window.JavascriptFuncCallback((int)evt.GetNumber(0), (int)evt.GetNumber(1), evt.GetString(2)); + return null; + } + + private class RegisteredFunc + { + public Func> taskFunc; + public Action normFunc; + } + private static ConcurrentDictionary csFuncIdToFired = new(); + } +} diff --git a/src/WebUI.NET/Window.cs b/src/WebUI.NET/Window.cs index 925f325..b30b4b2 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,196 @@ 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 originalCall, string? message) : base(message + " when running: " + originalCall) { } + + public JavaScriptException(string originalCall, string? message, Exception? innerException) : base(message + " when running: " + originalCall, 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 (result == "undefined") + return default; + 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);//not allowed to return null or undefined to webui + + } + + public void InvokeJavascriptMethod(string javascriptMethod, params object[] args) => InvokeJavaScript(CEFSharpImports.GetScriptForJavascriptMethodWithArgs(javascriptMethod, args)); + + + + //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 }; + //} + + //public void RegisterRawFunc(RawBoundCallback callback, String domId){ + // var id = Natives.WebUIBind(_handle, domId, OurFuncCallback); + // boundMethods[id] = new RawBoundInfo { cb=callback }; + //} + //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; + // } + // if (_boundItem is RawBoundInfo rb) + // { + // UseSpecificDispatcher.InvokeWithVoid(() => rb.cb(element, eventId)); + // 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; + //} + // (nint windowHandle, nuint eventType, string element, nuint eventId, nuint bindId) + //public delegate void RawBoundCallback(string element, nuint eventId); + //private class RawBoundInfo : IBoundInfo + //{ + // public RawBoundCallback cb; + //} + 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(javascript, 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 /// @@ -861,6 +1065,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)] @@ -1069,6 +1278,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")] diff --git a/src/WebUI.NET/webui_net.js b/src/WebUI.NET/webui_net.js new file mode 100644 index 0000000..b2b1cc7 --- /dev/null +++ b/src/WebUI.NET/webui_net.js @@ -0,0 +1,106 @@ + +{ + class WebUINet { + static DEBUG_MODE = false; + // calls a CS function but does not return any result + static fireCSFunction = (csFuncId, ...args) => this.#_callFireCSFunction(csFuncId, false, args); + // calls a CS function and returns a promise that will resolve with the result + static callCSFunction = (csFuncId, ...args) => this.#_callFireCSFunction(csFuncId, true, args); + static #curCallNumber = 1234; + static #jsCallIdToPromise = new Map(); + static #completedPromise = Promise.resolve(); + static #_callFireCSFunction(csFuncId, needsReturn, ...args) { + const callId = this.#curCallNumber++; + if (this.DEBUG_MODE) + console.log(`executing js call: ${callId} a cs function: ${csFuncId} with args`, args); + webui.call("webuiNet_Callback", csFuncId, callId, JSON.stringify(...args));//webui has a 16 arg max limit so we will just stringify args and deserialize in CS + if (!needsReturn) + return this.#completedPromise; //not sure if we should do this or return undefined + let promise = new Promise((resolv) => this.#jsCallIdToPromise.set(callId, resolv)); + + return promise; + } + + /// Options are standard options like capture, once, passive, abortKey is for a mantained internal db of AbortHandlers + static addCSEventListener(type, elementID, csFuncId, additionalProps, options, abortKey) { + if (this.DEBUG_MODE) + console.log(`addCSEventListener on element: ${elementID} for type: ${type}`); + let abortController = undefined; + if (abortKey) { + abortController = this.#abortKeyToHandlerMap.get(abortKey); + if (abortController == undefined) { + abortController = new AbortController(); + this.#abortKeyToHandlerMap.set(abortKey, abortController); + } + } + let elem = document.getElementById(elementID); + if (!elem) { + if (elementID == "window") + elem = window; + else if (elementID == "document") + elem = document; + else { + if (this.DEBUG_MODE) + console.warn(`cannot find the element to add listener to of id: ${elementID}`); + return; + } + } + let opts = { passive: true, signal: abortController }; + Object.assign(opts, options); + if (additionalProps) + additionalProps = JSON.parse(additionalProps); + elem.addEventListener(type, new InternalEventRecord(csFuncId, additionalProps), opts); + } + static #abortKeyToHandlerMap = new Map(); + static setCSFunctionResult(jsCallId, result) { + if (this.DEBUG_MODE) + console.log(`Setting cs result on jsCall: ${jsCallId} to: ${result}`); + const promise = this.#jsCallIdToPromise.get(jsCallId); + if (!promise) + return; + promise(result); + } + static addCSFunction(csFuncId, registeredName, hasResult) { + window[registeredName] = hasResult ? this.callCSFunction.bind(null, csFuncId) : this.fireCSFunction.bind(null, csFuncId); + } + } + class SerializableMap extends Map { + toJSON = () => Object.fromEntries(this); + } + class InternalEventRecord { + constructor(csFuncId, captureProps) { + this.csFuncId = csFuncId; + if (captureProps && typeof captureProps[Symbol.iterator] !== 'function')//this is a similar call to what map does + captureProps = Object.entries(captureProps); + this.captureProps = captureProps ? new Map(captureProps) : undefined; + } + csFuncId; + captureProps; //map to the additional properties to capture + static #getEventVal = (obj, path) => path.split('.').reduce((a, v) => (a ? a[v] : undefined), obj); + + /** @param {Event} evt */ + handleEvent(evt) { + if (WebUINet.DEBUG_MODE) + console.log("Event listener callback got event: ", evt); + + const eventInfo = new EventInfo(); + eventInfo.currentTargetId = evt.currentTarget?.id; + eventInfo.originalTargetId = evt.target?.id; + eventInfo.timestamp = performance.timing.navigationStart + evt.timeStamp; + eventInfo.type = evt.type; + if (this.captureProps) { + eventInfo.additionalProps = new SerializableMap(); + this.captureProps.forEach((val, key) => eventInfo.additionalProps.set(key, InternalEventRecord.#getEventVal(evt, val))); + } + WebUINet.fireCSFunction(this.csFuncId, eventInfo); + } + } + class EventInfo { + currentTargetId; + originalTargetId; + timestamp; + type; + additionalProps; //map of additional property values requested + } + window.WebUINet = WebUINet; +} \ No newline at end of file