From 518ffecef67fe352d3f6d1b4dd42c0d7cec64dca Mon Sep 17 00:00:00 2001 From: Infarh Date: Thu, 17 Apr 2025 19:59:07 +0300 Subject: [PATCH 01/16] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=B8=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE=20=D1=81=D1=82=D0=B8=D0=BB?= =?UTF-8?q?=D1=8E=20=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=B8=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D1=8F=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В файл copilot-instructions.md были добавлены новые инструкции, касающиеся написания кода и комментариев. Основные изменения включают требования к языку комментариев, стилю именования переменных и методов, а также рекомендации по минимизации кода и использованию современных синтаксических конструкций. Указано, что комментарии должны быть на русском языке, а имена переменных и методов — на английском. Также добавлены советы по использованию ключевого слова var при объявлении переменных и выражений инициализации для коллекций. --- .github/copilot-instructions.md | 15 +++++++++++++++ MathCore.WPF.sln | 3 +++ 2 files changed, 18 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..4786551a --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,15 @@ +Всегда отвечай мне используя русский язык. +Всегда пиши комментарии в коде на русском языке. +Комментарии к классам, структурам делегатам и перечислениям, а также к их членам всегда пиши в системном виде. +При написании комментариев (ели они короткие) в коде предпочитай размещение комментария в конце той же строке, что и сам комментируемый код. +Старайся избегать тривиальных комментариев. +При герерации кода старайся минимизировать количество фигурных скобок. +При генерации кода используй самые современные виды синтаксических конструкций языка. +Всегда старайся минимизировтаь размер кода если не запрошено иное. +Используй стиль именования локальных переменных snake_case. +Используй стиль именования входных переменных методов PascalCase. +Используй стиль именования полей классов _PascalCase для нестатических переменных и __PascalCase для статических переменных. +Ппредпочитай английский язык при именовании переменных, методов, классов и прочих сущностей. +При инициализации массивов, списков и словарей используй выражения инициализации массивов. +При объявлении переменных предпочитай использовать ключевое слово var. +При написании системных комментариев старайся писать их компактно в одну строку, если длина текста небольшая. \ No newline at end of file diff --git a/MathCore.WPF.sln b/MathCore.WPF.sln index 7a99efa8..ffc59b3c 100644 --- a/MathCore.WPF.sln +++ b/MathCore.WPF.sln @@ -19,6 +19,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Service", ".Service", "{F8 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{40F619FF-49AE-493A-846A-FE4D455B9BCB}" + ProjectSection(SolutionItems) = preProject + .github\copilot-instructions.md = .github\copilot-instructions.md + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C83B7C5C-5536-4BDA-9447-7B9A6393F30E}" ProjectSection(SolutionItems) = preProject From 8660e498b3dd8ccd4778a51d7aebe5a831e4dc3c Mon Sep 17 00:00:00 2001 From: Infarh Date: Mon, 23 Jun 2025 00:10:56 +0300 Subject: [PATCH 02/16] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/DataAnnotations/ColumnWidthAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MathCore.WPF/DataAnnotations/ColumnWidthAttribute.cs b/MathCore.WPF/DataAnnotations/ColumnWidthAttribute.cs index 70d01217..66ebf5d0 100644 --- a/MathCore.WPF/DataAnnotations/ColumnWidthAttribute.cs +++ b/MathCore.WPF/DataAnnotations/ColumnWidthAttribute.cs @@ -1,7 +1,7 @@ namespace MathCore.WPF.DataAnnotations; [AttributeUsage(AttributeTargets.Property)] -internal sealed class ColumnWidthAttribute : Attribute +public sealed class ColumnWidthAttribute : Attribute { public bool Auto { get; set; } From 2524a809403b0d2d6b23a414a0e0a3f305497eab Mon Sep 17 00:00:00 2001 From: Infarh Date: Mon, 23 Jun 2025 00:11:14 +0300 Subject: [PATCH 03/16] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B8?= =?UTF-8?q?=D1=81=D1=85=D0=BE=D0=B4=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2=20=D1=88?= =?UTF-8?q?=D0=B5=D0=B9=D0=B4=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/AttachedProperties/DataGridEx.cs | 35 +++---- MathCore.WPF/ItemsCollection.cs | 52 +++++++++++ MathCore.WPF/Shaders/Download DirectX MS.url | 5 + MathCore.WPF/Shaders/compile.bat | 2 +- MathCore.WPF/Shaders/new/0-255 to 16-235.psh | 13 +++ .../Shaders/new/16-235 to 0-255 [SD].psh | 22 +++++ .../Shaders/new/16-235 to 0-255 [SD][HD].psh | 13 +++ .../Shaders/new/3D OAU GreenMagenta.psh | 24 +++++ MathCore.WPF/Shaders/new/3D OAU RedCyan.psh | 24 +++++ MathCore.WPF/Shaders/new/3D OAU to 2D.psh | 21 +++++ .../Shaders/new/3D SBS GreenMagenta.psh | 24 +++++ MathCore.WPF/Shaders/new/3D SBS RedCyan.psh | 24 +++++ MathCore.WPF/Shaders/new/3D SBS to 2D.psh | 21 +++++ MathCore.WPF/Shaders/new/BT.601 to BT.709.psh | 19 ++++ .../Shaders/new/LCD angle correction.psh | 49 ++++++++++ MathCore.WPF/Shaders/new/Levels.psh | 15 +++ MathCore.WPF/Shaders/new/Levels2.psh | 24 +++++ MathCore.WPF/Shaders/new/Sharpen_3x3.psh | 41 +++++++++ MathCore.WPF/Shaders/new/Sharpen_5x5.psh | 64 +++++++++++++ .../Shaders/new/YV12 Chroma Upsampling.psh | 76 +++++++++++++++ MathCore.WPF/Shaders/new/contour.psh | 27 ++++++ .../Shaders/new/deinterlace (blend).psh | 18 ++++ MathCore.WPF/Shaders/new/denoise.psh | 36 ++++++++ MathCore.WPF/Shaders/new/edge sharpen.psh | 54 +++++++++++ MathCore.WPF/Shaders/new/emboss.psh | 24 +++++ MathCore.WPF/Shaders/new/grayscale.psh | 9 ++ MathCore.WPF/Shaders/new/invert.psh | 9 ++ MathCore.WPF/Shaders/new/letterbox.psh | 20 ++++ .../Shaders/new/sharpen complex 2.psh | 92 +++++++++++++++++++ MathCore.WPF/Shaders/new/sharpen complex.psh | 71 ++++++++++++++ MathCore.WPF/Shaders/new/sharpen flou.psh | 59 ++++++++++++ MathCore.WPF/Shaders/new/sharpen.psh | 31 +++++++ 32 files changed, 994 insertions(+), 24 deletions(-) create mode 100644 MathCore.WPF/ItemsCollection.cs create mode 100644 MathCore.WPF/Shaders/Download DirectX MS.url create mode 100644 MathCore.WPF/Shaders/new/0-255 to 16-235.psh create mode 100644 MathCore.WPF/Shaders/new/16-235 to 0-255 [SD].psh create mode 100644 MathCore.WPF/Shaders/new/16-235 to 0-255 [SD][HD].psh create mode 100644 MathCore.WPF/Shaders/new/3D OAU GreenMagenta.psh create mode 100644 MathCore.WPF/Shaders/new/3D OAU RedCyan.psh create mode 100644 MathCore.WPF/Shaders/new/3D OAU to 2D.psh create mode 100644 MathCore.WPF/Shaders/new/3D SBS GreenMagenta.psh create mode 100644 MathCore.WPF/Shaders/new/3D SBS RedCyan.psh create mode 100644 MathCore.WPF/Shaders/new/3D SBS to 2D.psh create mode 100644 MathCore.WPF/Shaders/new/BT.601 to BT.709.psh create mode 100644 MathCore.WPF/Shaders/new/LCD angle correction.psh create mode 100644 MathCore.WPF/Shaders/new/Levels.psh create mode 100644 MathCore.WPF/Shaders/new/Levels2.psh create mode 100644 MathCore.WPF/Shaders/new/Sharpen_3x3.psh create mode 100644 MathCore.WPF/Shaders/new/Sharpen_5x5.psh create mode 100644 MathCore.WPF/Shaders/new/YV12 Chroma Upsampling.psh create mode 100644 MathCore.WPF/Shaders/new/contour.psh create mode 100644 MathCore.WPF/Shaders/new/deinterlace (blend).psh create mode 100644 MathCore.WPF/Shaders/new/denoise.psh create mode 100644 MathCore.WPF/Shaders/new/edge sharpen.psh create mode 100644 MathCore.WPF/Shaders/new/emboss.psh create mode 100644 MathCore.WPF/Shaders/new/grayscale.psh create mode 100644 MathCore.WPF/Shaders/new/invert.psh create mode 100644 MathCore.WPF/Shaders/new/letterbox.psh create mode 100644 MathCore.WPF/Shaders/new/sharpen complex 2.psh create mode 100644 MathCore.WPF/Shaders/new/sharpen complex.psh create mode 100644 MathCore.WPF/Shaders/new/sharpen flou.psh create mode 100644 MathCore.WPF/Shaders/new/sharpen.psh diff --git a/MathCore.WPF/AttachedProperties/DataGridEx.cs b/MathCore.WPF/AttachedProperties/DataGridEx.cs index 7f15d7a7..94eb47e5 100644 --- a/MathCore.WPF/AttachedProperties/DataGridEx.cs +++ b/MathCore.WPF/AttachedProperties/DataGridEx.cs @@ -59,26 +59,15 @@ private static void OnDataGridGeneratingColumn(object? Sender, DataGridAutoGener var column = E.Column; - //if (property.PropertyType == typeof(DateTime)) - //{ - // E.Column = new DataGridTemplateColumn - // { - // HeaderTemplate = column.HeaderTemplate, - // Header = column.Header, - // CellTemplate = new DataTemplate(item_type) { } - // }; - // column = E.Column; - //} - var display_attribute = property.GetCustomAttribute(); - if(display_attribute?.GetAutoGenerateField() == false) + if (display_attribute?.GetAutoGenerateField() == false) { E.Cancel = true; return; } - if((display_attribute?.Name ?? property.GetCustomAttribute()?.DisplayName) is { } name) + if ((display_attribute?.Name ?? property.GetCustomAttribute()?.DisplayName) is { } name) column.Header = name; if (display_attribute?.Name is { } description) @@ -94,12 +83,12 @@ private static void OnDataGridGeneratingColumn(object? Sender, DataGridAutoGener if (property.GetCustomAttribute() is { } format_attribute) { - var text_column = column as DataGridTextColumn; + var text_column = column as DataGridTextColumn; var value_format = format_attribute.DataFormatString; if (value_format != null && text_column != null) { var binding = (Binding)text_column.Binding; - binding.StringFormat = value_format; + binding.StringFormat = value_format; binding.ConverterCulture = Thread.CurrentThread.CurrentUICulture; } @@ -112,17 +101,17 @@ private static void OnDataGridGeneratingColumn(object? Sender, DataGridAutoGener column.IsReadOnly = column_readonly; if (property.GetCustomAttribute() is - { - Width : var col_width, - Auto : var col_auto, - Adaptive: var col_adaptive - }) + { + Width: var col_width, + Auto: var col_auto, + Adaptive: var col_adaptive + }) column.Width = (col_width, col_auto, col_adaptive) switch { (not double.NaN and var width, false, false) => new DataGridLength(width), - (var width, false, true) => new DataGridLength(width, DataGridLengthUnitType.Star), - (var width, true, _) => new DataGridLength(width, DataGridLengthUnitType.Auto), - _ => new DataGridLength() + (var width, false, true) => new DataGridLength(width, DataGridLengthUnitType.Star), + (var width, true, _) => new DataGridLength(width, DataGridLengthUnitType.Auto), + _ => new DataGridLength() }; } diff --git a/MathCore.WPF/ItemsCollection.cs b/MathCore.WPF/ItemsCollection.cs new file mode 100644 index 00000000..b6b69e08 --- /dev/null +++ b/MathCore.WPF/ItemsCollection.cs @@ -0,0 +1,52 @@ +#nullable enable +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Input; + +using MathCore.WPF.Commands; + +namespace MathCore.WPF; + +public class ItemsCollection( + Func> CreatorAsync, + PropertyChangedEventHandler? ItemPropertyChanged = null, + Action? Editor = null) + : SelectableCollection +{ + [field: MaybeNull] + public ICommand AddCommand => field ??= Command.New(OnCreateCommandExecuted); + private async Task OnCreateCommandExecuted() + { + var item = await CreatorAsync(); + Add(item); + SelectedItem = item; + } + + [field: MaybeNull] + public ICommand RemoveCommand => field ??= Command.New(t => Remove(t), Contains); + + [field: MaybeNull] + public ICommand EditCommand => field ??= Command.New(t => Editor?.Invoke(t!), Contains); + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (ItemPropertyChanged is not null) + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (e.NewItems is not null) + foreach (var item in e.NewItems.OfType()) + item.PropertyChanged += ItemPropertyChanged; + break; + + case NotifyCollectionChangedAction.Remove: + if (e.OldItems is not null) + foreach (var item in e.OldItems.OfType()) + item.PropertyChanged -= ItemPropertyChanged; + break; + } + + base.OnCollectionChanged(e); + } +} diff --git a/MathCore.WPF/Shaders/Download DirectX MS.url b/MathCore.WPF/Shaders/Download DirectX MS.url new file mode 100644 index 00000000..b35a49a7 --- /dev/null +++ b/MathCore.WPF/Shaders/Download DirectX MS.url @@ -0,0 +1,5 @@ +[{000214A0-0000-0000-C000-000000000046}] +Prop3=19,11 +[InternetShortcut] +IDList= +URL=https://www.microsoft.com/ru-ru/download/details.aspx?id=35 diff --git a/MathCore.WPF/Shaders/compile.bat b/MathCore.WPF/Shaders/compile.bat index 951a7342..25dd6e54 100644 --- a/MathCore.WPF/Shaders/compile.bat +++ b/MathCore.WPF/Shaders/compile.bat @@ -1 +1 @@ -fxc /T ps_3_0 /E main /Fo "%1.ps" "%1" +fxc /T ps_3_0 /E main /Fo %1.ps %1 diff --git a/MathCore.WPF/Shaders/new/0-255 to 16-235.psh b/MathCore.WPF/Shaders/new/0-255 to 16-235.psh new file mode 100644 index 00000000..c2cb3799 --- /dev/null +++ b/MathCore.WPF/Shaders/new/0-255 to 16-235.psh @@ -0,0 +1,13 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); + +#define const_1 ( 16.0 / 255.0) +#define const_2 (219.0 / 255.0) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + // original pixel + float4 c0 = tex2D(s0, tex); + + return (c0 * const_2) + const_1; +} diff --git a/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD].psh b/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD].psh new file mode 100644 index 00000000..c6a50fa6 --- /dev/null +++ b/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD].psh @@ -0,0 +1,22 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +#define const_1 ( 16.0 / 255.0) +#define const_2 (255.0 / 219.0) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + // original pixel + float4 c0 = tex2D(s0, tex); + + // ATI driver only looks at the height + if (height >= 720) { + return c0; + } else { + return ((c0 - const_1) * const_2); + } +} diff --git a/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD][HD].psh b/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD][HD].psh new file mode 100644 index 00000000..2f5fc675 --- /dev/null +++ b/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD][HD].psh @@ -0,0 +1,13 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); + +#define const_1 ( 16.0 / 255.0) +#define const_2 (255.0 / 219.0) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + // original pixel + float4 c0 = tex2D(s0, tex); + + return ((c0 - const_1) * const_2); +} diff --git a/MathCore.WPF/Shaders/new/3D OAU GreenMagenta.psh b/MathCore.WPF/Shaders/new/3D OAU GreenMagenta.psh new file mode 100644 index 00000000..c344afdc --- /dev/null +++ b/MathCore.WPF/Shaders/new/3D OAU GreenMagenta.psh @@ -0,0 +1,24 @@ +// 3D OverandUnder Green/Magenta + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR +{ + +tex.y=tex.y/2; + +float4 l = tex2D(s0, tex); +tex.y=tex.y + 0.5; + +float4 r = tex2D(s0, tex); + +float red =r.r*0.3+r.g*0.4+r.b*0.1; +float green =l.g*0.7; +float blue = l.b*0.7; + +return float4(red, green, blue, 1); +} \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/3D OAU RedCyan.psh b/MathCore.WPF/Shaders/new/3D OAU RedCyan.psh new file mode 100644 index 00000000..536b421f --- /dev/null +++ b/MathCore.WPF/Shaders/new/3D OAU RedCyan.psh @@ -0,0 +1,24 @@ +// 3D OverandUnder Red/Cyan + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR +{ + tex.y = tex.y / 2; + + float4 l = tex2D(s0, tex); + + tex.y = tex.y + 0.5; + + float4 r = tex2D(s0, tex); + + float red = l.r; + float green = r.g * 0.8; + float blue = r.b; + + return float4(red, green, blue, 9); +} \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/3D OAU to 2D.psh b/MathCore.WPF/Shaders/new/3D OAU to 2D.psh new file mode 100644 index 00000000..ffd31ec6 --- /dev/null +++ b/MathCore.WPF/Shaders/new/3D OAU to 2D.psh @@ -0,0 +1,21 @@ +// 3D OverandUnder to 2D + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR +{ + tex.y = tex.y / 2; + + float4 l = tex2D(s0, tex); + float4 r = tex2D(s0, tex); + + float red = l.r; + float green = r.g; + float blue = r.b; + + return float4(red, green, blue, 1); +} \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/3D SBS GreenMagenta.psh b/MathCore.WPF/Shaders/new/3D SBS GreenMagenta.psh new file mode 100644 index 00000000..029bd7f4 --- /dev/null +++ b/MathCore.WPF/Shaders/new/3D SBS GreenMagenta.psh @@ -0,0 +1,24 @@ +// 3D SidebySide Green/Magenta + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR +{ + +tex.x=tex.x/2; + +float4 l = tex2D(s0, tex); +tex.x=tex.x + 0.5; + +float4 r = tex2D(s0, tex); + +float red =r.r*0.3+r.g*0.4+r.b*0.1; +float green =l.g*0.7; +float blue = l.b*0.7; + +return float4(red, green, blue, 1); +} \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/3D SBS RedCyan.psh b/MathCore.WPF/Shaders/new/3D SBS RedCyan.psh new file mode 100644 index 00000000..74e5aec0 --- /dev/null +++ b/MathCore.WPF/Shaders/new/3D SBS RedCyan.psh @@ -0,0 +1,24 @@ +// 3D SidebySide Red/Cyan + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR +{ + tex.x = tex.x / 2; + + float4 l = tex2D(s0, tex); + + tex.x = tex.x + 0.5; + + float4 r = tex2D(s0, tex); + + float red = l.r; + float green = r.g * 0.8; + float blue = r.b; + + return float4(red, green, blue, 9); +} \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/3D SBS to 2D.psh b/MathCore.WPF/Shaders/new/3D SBS to 2D.psh new file mode 100644 index 00000000..8f0b8f27 --- /dev/null +++ b/MathCore.WPF/Shaders/new/3D SBS to 2D.psh @@ -0,0 +1,21 @@ +// 3D SidebySide to 2D + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR +{ + tex.x = tex.x / 2; + + float4 l = tex2D(s0, tex); + float4 r = tex2D(s0, tex); + + float red = l.r; + float green = r.g; + float blue = r.b; + + return float4(red, green, blue, 1); +} \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/BT.601 to BT.709.psh b/MathCore.WPF/Shaders/new/BT.601 to BT.709.psh new file mode 100644 index 00000000..f520ebab --- /dev/null +++ b/MathCore.WPF/Shaders/new/BT.601 to BT.709.psh @@ -0,0 +1,19 @@ +// $MinimumShaderProfile: ps_2_0 + +// (C) 2011 Jan-Willem Krans (janwillem32 hotmail.com) released under GPL v2; see COPYING.txt + +// Correct video colorspace BT.601 [SD] to BT.709 [HD] for HD video input +// Use this shader only if BT.709 [HD] encoded video is incorrectly matrixed to full range RGB with the BT.601 [SD] colorspace. + +sampler s0; +float2 c0; + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float4 si = tex2D(s0, tex); // original pixel + if (c0.x < 1120 && c0.y < 630) { + return si; // this shader does not alter SD video + } + float3 s1 = si.rgb; + s1 = s1.rrr * float3(0.299, -0.1495 / 0.886, 0.5) + s1.ggg * float3(0.587, -0.2935 / 0.886, -0.2935 / 0.701) + s1.bbb * float3(0.114, 0.5, -0.057 / 0.701); // RGB to Y'CbCr, BT.601 [SD] colorspace + return (s1.rrr + float3(0, -0.1674679 / 0.894, 1.8556) * s1.ggg + float3(1.5748, -0.4185031 / 0.894, 0) * s1.bbb).rgbb; // Y'CbCr to RGB output, BT.709 [HD] colorspace +} diff --git a/MathCore.WPF/Shaders/new/LCD angle correction.psh b/MathCore.WPF/Shaders/new/LCD angle correction.psh new file mode 100644 index 00000000..f88f0d02 --- /dev/null +++ b/MathCore.WPF/Shaders/new/LCD angle correction.psh @@ -0,0 +1,49 @@ +// $MinimumShaderProfile: ps_2_0 + +// (C) 2011 Jan-Willem Krans (janwillem32 hotmail.com) released under GPL v2; see COPYING.txt + +// Brightness, contrast and gamma controls for RGB, linearly scaled from top to bottom. +// This shader can be run as a screen space pixel shader. It requires compiling with ps_2_0, +// but higher is better see http://en.wikipedia.org/wiki/Pixel_shader to look up what PS version +// your video card supports. +// This shader is meant to work with linear RGB input and output. Regular R'G'B' with +// a video gamma encoding will have to be converted with the linear gamma shaders to work properly. + +// Fractions, either decimal or not, are allowed +// RedBrightness, GreenBrightness and BlueBrightness, interval [-10, 10], default 0 +#define RedBrightnessTop 0 +#define GreenBrightnessTop 0 +#define BlueBrightnessTop 0 +#define RedBrightnessBottom 0 +#define GreenBrightnessBottom 0 +#define BlueBrightnessBottom 0 + +// RedContrast, GreenContrast and BlueContrast, interval [0, 10], default 1 +#define RedContrastTop 1 +#define GreenContrastTop 1 +#define BlueContrastTop 1 +#define RedContrastBottom 1 +#define GreenContrastBottom 1 +#define BlueContrastBottom 1 + +// RedGamma, GreenGamma and BlueGamma, interval (0, 10], default 1 +#define RedGammaTop 0.8 +#define GreenGammaTop 0.8 +#define BlueGammaTop 0.8 +#define RedGammaBottom 1 +#define GreenGammaBottom 1 +#define BlueGammaBottom 1 + +sampler s0; + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float3 s1 = tex2D(s0, tex).rgb; + // original pixel + float texyi = 1.0 - tex.y; + s1 = s1 * (texyi * float3(RedContrastTop, GreenContrastTop, BlueContrastTop) + tex.y * float3(RedContrastBottom, GreenContrastBottom, BlueContrastBottom)) + texyi * float3(RedBrightnessTop, GreenBrightnessTop, BlueBrightnessTop) + tex.y * float3(RedBrightnessBottom, GreenBrightnessBottom, BlueBrightnessBottom); + // process contrast and brightness on the original pixel + // preserve the sign bits of RGB values + float3 sb = sign(s1); + return (sb*pow(abs(s1), texyi * float3(RedGammaTop, GreenGammaTop, BlueGammaTop) + tex.y * float3(RedGammaBottom, GreenGammaBottom, BlueGammaBottom))).rgbb; + // process gamma correction and output +} diff --git a/MathCore.WPF/Shaders/new/Levels.psh b/MathCore.WPF/Shaders/new/Levels.psh new file mode 100644 index 00000000..96fbc553 --- /dev/null +++ b/MathCore.WPF/Shaders/new/Levels.psh @@ -0,0 +1,15 @@ +// Levels=ps_2_0 +// Code from MPC + +sampler s0 : register(s0); + +#define const_1 (16.0/255.0) +#define const_2 (255.0/219.0) + +float4 main(float2 tex : TEXCOORD0) : COLOR +{ + // original pixel + float4 c0 = tex2D(s0,tex); + + return((c0 - const_1) * const_2); +} diff --git a/MathCore.WPF/Shaders/new/Levels2.psh b/MathCore.WPF/Shaders/new/Levels2.psh new file mode 100644 index 00000000..ff4fdbba --- /dev/null +++ b/MathCore.WPF/Shaders/new/Levels2.psh @@ -0,0 +1,24 @@ +// Levels=ps_2_0 +// Code from MPC + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define height (p0[1]) +//#define width (p0[0]) + +#define const_1 (16.0/255.0) +#define const_2 (255.0/219.0) + +float4 main(float2 tex : TEXCOORD0) : COLOR +{ + // original pixel + float4 c0 = tex2D(s0,tex); + + if(height > 719 ) { + //if(width > 1279) { + return c0; + } else { + return((c0 - const_1) * const_2); + } +} diff --git a/MathCore.WPF/Shaders/new/Sharpen_3x3.psh b/MathCore.WPF/Shaders/new/Sharpen_3x3.psh new file mode 100644 index 00000000..00d30f80 --- /dev/null +++ b/MathCore.WPF/Shaders/new/Sharpen_3x3.psh @@ -0,0 +1,41 @@ +// Sharpen_3x3=ps_2_0 +// http://www.homecinema-fr.com/forum/viewtopic.php?t=29814317 + +sampler s0 : register(s0); +float4 p0 : register(c0); +float4 p1 : register(c1); + +#define width (p0[0]) +#define height (p0[1]) +#define counter (p0[2]) +#define clock (p0[3]) +#define one_over_width (p1[0]) +#define one_over_height (p1[1]) + +#define PI acos(-1) + +#define x 0.8 + +float4 main(float2 tex : TEXCOORD0) : COLOR +{ + + float dx = one_over_width*x; + float dy = one_over_height*x; + + float4 c9 = tex2D(s0, tex) * 2.0; + + float4 c1 = tex2D( s0, tex + float2( -dx , -dy ) ) * 0.1; + c1 += tex2D( s0, tex + float2( 0 , -dy ) ) * 0.15; + c1 += tex2D( s0, tex + float2( dx , -dy ) ) * 0.1; + + c1 += tex2D( s0, tex + float2( -dx , 0 ) ) * 0.15; + c1 += tex2D( s0, tex + float2( dx , 0 ) ) * 0.15; + + c1 += tex2D( s0, tex + float2( -dx , dy ) ) * 0.1; + c1 += tex2D( s0, tex + float2( 0 , dy ) ) * 0.15; + c1 += tex2D( s0, tex + float2( dx , dy ) ) * 0.1; + + float4 c0 = c9 -c1 ; + + return c0; +} \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/Sharpen_5x5.psh b/MathCore.WPF/Shaders/new/Sharpen_5x5.psh new file mode 100644 index 00000000..6807b612 --- /dev/null +++ b/MathCore.WPF/Shaders/new/Sharpen_5x5.psh @@ -0,0 +1,64 @@ +// Sharpen_5x5=ps_3_0 +// http://www.homecinema-fr.com/forum/viewtopic.php?t=29814317 + +sampler s0 : register(s0); + +float4 p0 : register(c0); +float4 p1 : register(c1); + +#define width (p0[0]) +#define height (p0[1]) +#define counter (p0[2]) +#define clock (p0[3]) +#define one_over_width (p1[0]) +#define one_over_height (p1[1]) + +#define PI acos(-1) + +#define x 0.8 + +float4 main(float2 tex : TEXCOORD0) : COLOR +{ + float dx = one_over_width * x; + float dy = one_over_height * x; + + float dx2 = one_over_width * 2; + float dy2 = one_over_height * 2; + + float4 coef = float4( 0.025, 0.05, 0.1, 2.0 ); + + float4 c0 = tex2D( s0, tex ) * coef[3]; + + float4 c1 = tex2D( s0, tex + float2( -dx2 , -dy2 ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( -dx , -dy2 ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( 0 , -dy2 ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( dx , -dy2 ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( dx2 , -dy2 ) ) * coef[0]; + + c1 += tex2D( s0, tex + float2( -dx2 , -dy ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( -dx , -dy ) ) * coef[1] ; + c1 += tex2D( s0, tex + float2( 0 , -dy ) ) * coef[2] ; + c1 += tex2D( s0, tex + float2( dx , -dy ) ) * coef[1] ; + c1 += tex2D( s0, tex + float2( dx2 , -dy ) ) * coef[0]; + + c1 += tex2D( s0, tex + float2( -dx2 , 0 ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( -dx , 0 ) ) * coef[2] ; + c1 += tex2D( s0, tex + float2( dx , 0 ) ) * coef[2] ; + c1 += tex2D( s0, tex + float2( dx2 , 0 ) ) * coef[0]; + + c1 += tex2D( s0, tex + float2( -dx2 , dy ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( -dx , dy ) ) * coef[1] ; + c1 += tex2D( s0, tex + float2( 0 , dy ) ) * coef[2] ; + c1 += tex2D( s0, tex + float2( dx , dy ) ) * coef[1] ; + c1 += tex2D( s0, tex + float2( dx2 , dy ) ) * coef[0]; + + c1 += tex2D( s0, tex + float2( -dx2 , dy2 ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( -dx , dy2 ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( 0 , dy2 ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( dx , dy2 ) ) * coef[0]; + c1 += tex2D( s0, tex + float2( dx2 , dy2 ) ) * coef[0]; + + c0 -= c1; + + return c0; +} \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/YV12 Chroma Upsampling.psh b/MathCore.WPF/Shaders/new/YV12 Chroma Upsampling.psh new file mode 100644 index 00000000..9314303c --- /dev/null +++ b/MathCore.WPF/Shaders/new/YV12 Chroma Upsampling.psh @@ -0,0 +1,76 @@ +// $MinimumShaderProfile: ps_2_0 + +/* +YV12 chroma upsampling fixer +by Kurt Bernhard 'Leak' Pruenner + +Use with YV12 output if the half-resolution chroma +gets upsampled in hardware by doubling the values +instead of interpolating between them. + +(i.e. if you're getting blocky red edges on dark +backgrounds...) +*/ + +sampler s0 : register(s0); +float4 p0 : register(c0); +float4 p1 : register(c1); + +#define width (p0[0]) +#define height (p0[1]) + +float4 getPixel(float2 tex, float dx, float dy) +{ + tex.x += dx; + tex.y += dy; + + return tex2D(s0, tex); +} + +float4 rgb2yuv(float4 rgb) +{ + float4x4 coeffs = { + 0.299, 0.587, 0.114, 0.000, + -0.147, -0.289, 0.436, 0.000, + 0.615, -0.515, -0.100, 0.000, + 0.000, 0.000, 0.000, 0.000 + }; + + return mul(coeffs, rgb); +} + +float4 yuv2rgb(float4 yuv) +{ + float4x4 coeffs = { + 1.000, 0.000, 1.140, 0.000, + 1.000, -0.395, -0.581, 0.000, + 1.000, 2.032, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000 + }; + + return mul(coeffs, yuv); +} + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float dx = 1 / width; + float dy = 1 / height; + + float4 yuv00 = rgb2yuv(getPixel(tex, -dx, -dy)); + float4 yuv01 = rgb2yuv(getPixel(tex, -dx, 0)); + float4 yuv02 = rgb2yuv(getPixel(tex, -dx, dy)); + float4 yuv10 = rgb2yuv(getPixel(tex, 0, -dy)); + float4 yuv11 = rgb2yuv(getPixel(tex, 0, 0)); + float4 yuv12 = rgb2yuv(getPixel(tex, 0, dy)); + float4 yuv20 = rgb2yuv(getPixel(tex, dx, -dy)); + float4 yuv21 = rgb2yuv(getPixel(tex, dx, 0)); + float4 yuv22 = rgb2yuv(getPixel(tex, dx, dy)); + + float4 yuv = + (yuv00 * 1 + yuv01 * 2 + yuv02 * 1 + + yuv10 * 2 + yuv11 * 4 + yuv12 * 2 + + yuv20 * 1 + yuv21 * 2 + yuv22 * 1) / 16; + + yuv.r = yuv11.r; + + return yuv2rgb(yuv); +} diff --git a/MathCore.WPF/Shaders/new/contour.psh b/MathCore.WPF/Shaders/new/contour.psh new file mode 100644 index 00000000..f26449d2 --- /dev/null +++ b/MathCore.WPF/Shaders/new/contour.psh @@ -0,0 +1,27 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float dx = 4 / width; + float dy = 4 / height; + + float4 c2 = tex2D(s0, tex + float2( 0, -dy)); + float4 c4 = tex2D(s0, tex + float2(-dx, 0)); + float4 c5 = tex2D(s0, tex + float2( 0, 0)); + float4 c6 = tex2D(s0, tex + float2( dx, 0)); + float4 c8 = tex2D(s0, tex + float2( 0, dy)); + + float4 c0 = (-c2 - c4 + c5 * 4 - c6 - c8); + if (length(c0) < 1.0) { + c0 = float4(0, 0, 0, 0); + } else { + c0 = float4(1, 1, 1, 0); + } + + return c0; +} diff --git a/MathCore.WPF/Shaders/new/deinterlace (blend).psh b/MathCore.WPF/Shaders/new/deinterlace (blend).psh new file mode 100644 index 00000000..338fb129 --- /dev/null +++ b/MathCore.WPF/Shaders/new/deinterlace (blend).psh @@ -0,0 +1,18 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float4 c0 = tex2D(s0, tex); + + float2 h = float2(0, 1 / height); + float4 c1 = tex2D(s0, tex - h); + float4 c2 = tex2D(s0, tex + h); + c0 = (c0 * 2 + c1 + c2) / 4; + + return c0; +} diff --git a/MathCore.WPF/Shaders/new/denoise.psh b/MathCore.WPF/Shaders/new/denoise.psh new file mode 100644 index 00000000..537b7cf9 --- /dev/null +++ b/MathCore.WPF/Shaders/new/denoise.psh @@ -0,0 +1,36 @@ +// $MinimumShaderProfile: ps_3_0 + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) +#define val0 (1.0) +#define val1 (0.125) +#define effect_width (0.1) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float dx = 0.0f; + float dy = 0.0f; + float fTap = effect_width; + + float4 cAccum = tex2D(s0, tex) * val0; + + for (int iDx = 0; iDx < 16; ++iDx) { + dx = fTap / width; + dy = fTap / height; + + cAccum += tex2D(s0, tex + float2(-dx, -dy)) * val1; + cAccum += tex2D(s0, tex + float2( 0, -dy)) * val1; + cAccum += tex2D(s0, tex + float2(-dx, 0)) * val1; + cAccum += tex2D(s0, tex + float2( dx, 0)) * val1; + cAccum += tex2D(s0, tex + float2( 0, dy)) * val1; + cAccum += tex2D(s0, tex + float2( dx, dy)) * val1; + cAccum += tex2D(s0, tex + float2(-dx, +dy)) * val1; + cAccum += tex2D(s0, tex + float2(+dx, -dy)) * val1; + + fTap += 0.1f; + } + + return (cAccum / 16.0f); +} diff --git a/MathCore.WPF/Shaders/new/edge sharpen.psh b/MathCore.WPF/Shaders/new/edge sharpen.psh new file mode 100644 index 00000000..ee464786 --- /dev/null +++ b/MathCore.WPF/Shaders/new/edge sharpen.psh @@ -0,0 +1,54 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +#define NbPixel 1 +#define Edge_threshold 0.2 +#define Sharpen_val0 2.0 +#define Sharpen_val1 0.125 + +float4 main(float2 tex : TEXCOORD0) : COLOR { + // size of NbPixel pixels + float dx = NbPixel / width; + float dy = NbPixel / height; + float4 Res = 0; + + // Edge detection using Prewitt operator + // Get neighbor points + // [ 1, 2, 3 ] + // [ 4, 0, 5 ] + // [ 6, 7, 8 ] + float4 c0 = tex2D(s0, tex); + float4 c1 = tex2D(s0, tex + float2(-dx, -dy)); + float4 c2 = tex2D(s0, tex + float2( 0, -dy)); + float4 c3 = tex2D(s0, tex + float2( dx, -dy)); + float4 c4 = tex2D(s0, tex + float2(-dx, 0)); + float4 c5 = tex2D(s0, tex + float2( dx, 0)); + float4 c6 = tex2D(s0, tex + float2(-dx, dy)); + float4 c7 = tex2D(s0, tex + float2( 0, dy)); + float4 c8 = tex2D(s0, tex + float2( dx, dy)); + + // Computation of the 3 derived vectors (hor, vert, diag1, diag2) + float4 delta1 = (c6 + c4 + c1 - c3 - c5 - c8); + float4 delta2 = (c4 + c1 + c2 - c5 - c8 - c7); + float4 delta3 = (c1 + c2 + c3 - c8 - c7 - c6); + float4 delta4 = (c2 + c3 + c5 - c7 - c6 - c4); + + // Computation of the Prewitt operator + float value = length(abs(delta1) + abs(delta2) + abs(delta3) + abs(delta4)) / 6; + + // If we have an edge (vector length > Edge_threshold) => apply sharpen filter + if (value > Edge_threshold) { + Res = c0 * Sharpen_val0 - (c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8) * Sharpen_val1; + // Display edges in red... + //Res = float4( 1.0, 0.0, 0.0, 0.0 ); + + return Res; + } else { + return c0; + } +} diff --git a/MathCore.WPF/Shaders/new/emboss.psh b/MathCore.WPF/Shaders/new/emboss.psh new file mode 100644 index 00000000..59b36853 --- /dev/null +++ b/MathCore.WPF/Shaders/new/emboss.psh @@ -0,0 +1,24 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float dx = 1 / width; + float dy = 1 / height; + + float4 c1 = tex2D(s0, tex + float2(-dx, -dy)); + float4 c2 = tex2D(s0, tex + float2( 0, -dy)); + float4 c4 = tex2D(s0, tex + float2(-dx, 0)); + float4 c6 = tex2D(s0, tex + float2( dx, 0)); + float4 c8 = tex2D(s0, tex + float2( 0, dy)); + float4 c9 = tex2D(s0, tex + float2( dx, dy)); + + float4 c0 = (-c1 - c2 - c4 + c6 + c8 + c9); + c0 = (c0.r + c0.g + c0.b) / 3 + 0.5; + + return c0; +} diff --git a/MathCore.WPF/Shaders/new/grayscale.psh b/MathCore.WPF/Shaders/new/grayscale.psh new file mode 100644 index 00000000..617eecbf --- /dev/null +++ b/MathCore.WPF/Shaders/new/grayscale.psh @@ -0,0 +1,9 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float c0 = dot(tex2D(s0, tex), float4(0.299, 0.587, 0.114, 0)); + + return c0; +} diff --git a/MathCore.WPF/Shaders/new/invert.psh b/MathCore.WPF/Shaders/new/invert.psh new file mode 100644 index 00000000..b3d607e0 --- /dev/null +++ b/MathCore.WPF/Shaders/new/invert.psh @@ -0,0 +1,9 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float4 c0 = float4(1, 1, 1, 1) - tex2D(s0, tex); + + return c0; +} diff --git a/MathCore.WPF/Shaders/new/letterbox.psh b/MathCore.WPF/Shaders/new/letterbox.psh new file mode 100644 index 00000000..b643f817 --- /dev/null +++ b/MathCore.WPF/Shaders/new/letterbox.psh @@ -0,0 +1,20 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); +float4 p0 : register(c0); + +#define width (p0[0]) +#define height (p0[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float4 c0 = 0; + + float2 ar = float2(16, 9); + float h = (1 - width / height * ar.y / ar.x) / 2; + + if (tex.y >= h && tex.y <= 1 - h) { + c0 = tex2D(s0, tex); + } + + return c0; +} diff --git a/MathCore.WPF/Shaders/new/sharpen complex 2.psh b/MathCore.WPF/Shaders/new/sharpen complex 2.psh new file mode 100644 index 00000000..82c2e425 --- /dev/null +++ b/MathCore.WPF/Shaders/new/sharpen complex 2.psh @@ -0,0 +1,92 @@ +// $MinimumShaderProfile: ps_2_a + +/* Sharpen complex v2 (requires ps >= 2a) */ + +sampler s0 : register(s0); +float4 p0 : register(c0); +float4 p1 : register(c1); + +#define width (p0[0]) +#define height (p0[1]) + +// pixel "width" +#define px (p1[0]) +#define py (p1[1]) + +/* Parameters */ + +// for the blur filter +#define mean 0.6 +#define dx (mean * px) +#define dy (mean * py) + +#define CoefBlur 2 +#define CoefOrig (1 + CoefBlur) + +// for the sharpen filter +#define SharpenEdge 0.2 +#define Sharpen_val0 2 +#define Sharpen_val1 ((Sharpen_val0 - 1) / 8.0) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + // get original pixel + float4 orig = tex2D(s0, tex); + + // compute blurred image (gaussian filter) + float4 c1 = tex2D(s0, tex + float2(-dx, -dy)); + float4 c2 = tex2D(s0, tex + float2( 0, -dy)); + float4 c3 = tex2D(s0, tex + float2( dx, -dy)); + float4 c4 = tex2D(s0, tex + float2(-dx, 0)); + float4 c5 = tex2D(s0, tex + float2( dx, 0)); + float4 c6 = tex2D(s0, tex + float2(-dx, dy)); + float4 c7 = tex2D(s0, tex + float2( 0, dy)); + float4 c8 = tex2D(s0, tex + float2( dx, dy)); + + // gaussian filter + // [ 1, 2, 1 ] + // [ 2, 4, 2 ] + // [ 1, 2, 1 ] + // to normalize the values, we need to divide by the coeff sum + // 1 / (1+2+1+2+4+2+1+2+1) = 1 / 16 = 0.0625 + float4 flou = (c1 + c3 + c6 + c8 + 2 * (c2 + c4 + c5 + c7) + 4 * orig) * 0.0625; + + // substract blurred image from original image + float4 corrected = CoefOrig * orig - CoefBlur * flou; + + // edge detection + // Get neighbor points + // [ c1, c2, c3 ] + // [ c4, orig, c5 ] + // [ c6, c7, c8 ] + c1 = tex2D(s0, tex + float2(-px, -py)); + c2 = tex2D(s0, tex + float2( 0, -py)); + c3 = tex2D(s0, tex + float2( px, -py)); + c4 = tex2D(s0, tex + float2(-px, 0)); + c5 = tex2D(s0, tex + float2( px, 0)); + c6 = tex2D(s0, tex + float2(-px, py)); + c7 = tex2D(s0, tex + float2( 0, py)); + c8 = tex2D(s0, tex + float2( px, py)); + + // using Sobel filter + // horizontal gradient + // [ -1, 0, 1 ] + // [ -2, 0, 2 ] + // [ -1, 0, 1 ] + float delta1 = (c3 + 2 * c5 + c8) - (c1 + 2 * c4 + c6); + + // vertical gradient + // [ -1, - 2, -1 ] + // [ 0, 0, 0 ] + // [ 1, 2, 1 ] + float delta2 = (c6 + 2 * c7 + c8) - (c1 + 2 * c2 + c3); + + // computation + if (sqrt(mul(delta1, delta1) + mul(delta2, delta2)) > SharpenEdge) { + // if we have an edge, use sharpen + //return float4(1,0,0,0); + return orig * Sharpen_val0 - (c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8) * Sharpen_val1; + } else { + // else return corrected image + return corrected; + } +} diff --git a/MathCore.WPF/Shaders/new/sharpen complex.psh b/MathCore.WPF/Shaders/new/sharpen complex.psh new file mode 100644 index 00000000..82a79478 --- /dev/null +++ b/MathCore.WPF/Shaders/new/sharpen complex.psh @@ -0,0 +1,71 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); +float4 p1 : register(c1); + +#define dx (p1[0]) +#define dy (p1[1]) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + // Pixels definition: original, blurred, corrected, final + float4 orig; + float4 blurred; + float4 corrected; + float4 final; + + // Get neighbor points + // [ 1, 2, 3 ] + // [ 4, orig, 5 ] + // [ 6, 7, 8 ] + + orig = tex2D(s0, tex); + float4 c1 = tex2D(s0, tex + float2(-dx, -dy)); + float4 c2 = tex2D(s0, tex + float2( 0, -dy)); + float4 c3 = tex2D(s0, tex + float2( dx, -dy)); + float4 c4 = tex2D(s0, tex + float2(-dx, 0)); + float4 c5 = tex2D(s0, tex + float2( dx, 0)); + float4 c6 = tex2D(s0, tex + float2(-dx, dy)); + float4 c7 = tex2D(s0, tex + float2( 0, dy)); + float4 c8 = tex2D(s0, tex + float2( dx, dy)); + + // Computation of the blurred image (gaussian filter) + // to normalize the values, we need to divide by the coeff sum + // 1/(1+2+1+2+4+2+1+2+1) = 1/16 = 0.0625 + blurred = (c1 + c3 + c6 + c8 + 2 * (c2 + c4 + c5 + c7) + 4 * orig) * 0.0625; + + // substract blurred image from original image + corrected = 2 * orig - blurred; + + // edge detection + float delta1; + float delta2; + float value; + + // using Sobel filter + // horizontal gradient + // [ -1, 0, 1 ] + // [ -2, 0, 2 ] + // [ -1, 0, 1 ] + delta1 = (c3 + 2 * c5 + c8) - (c1 + 2 * c4 + c6); + + // vertical gradient + // [ -1, -2, -1 ] + // [ 0, 0, 0 ] + // [ 1, 2, 1 ] + delta2 = (c6 + 2 * c7 + c8) - (c1 + 2 * c2 + c3); + + // computation + value = sqrt(mul(delta1, delta1) + mul(delta2, delta2)); + + if (value > 0.3) { + // if we have an edge, use sharpen + #define Sharpen_val0 2.0 + #define Sharpen_val1 0.125 + final = orig * 2 - (c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8) * 0.125; + //final = float4(1, 0, 0, 0); + return final; + } + + // else return corrected image + return corrected; +} diff --git a/MathCore.WPF/Shaders/new/sharpen flou.psh b/MathCore.WPF/Shaders/new/sharpen flou.psh new file mode 100644 index 00000000..0c96bfae --- /dev/null +++ b/MathCore.WPF/Shaders/new/sharpen flou.psh @@ -0,0 +1,59 @@ +// $MinimumShaderProfile: ps_2_0 +sampler s0 : register(s0); +float4 p0 : register(c0); +float4 p1 : register(c1); + +#define width (p0[0]) +#define height (p0[1]) +#define counter (p0[2]) +#define clock (p0[3]) +#define one_over_width (p1[0]) +#define one_over_height (p1[1]) + +#define PI acos(-1) + +float4 main( float2 tex : TEXCOORD0 ) : COLOR +{ +float dx = one_over_width; +float dy = one_over_height; + +// recupperation de la matrice de 9 points +// [ 1, 2 , 3 ] +// [ 4,ori, 5 ] +// [ 6, 7 , 8 ] + + float4 ori = tex2D(s0, tex); + float4 c1 = tex2D(s0, tex + float2(-dx,-dy)); + float4 c2 = tex2D(s0, tex + float2(0,-dy)); + float4 c3 = tex2D(s0, tex + float2(dx,-dy)); + float4 c4 = tex2D(s0, tex + float2(-dx,0)); + float4 c5 = tex2D(s0, tex + float2(dx,0)); + float4 c6 = tex2D(s0, tex + float2(-dx,dy)); + float4 c7 = tex2D(s0, tex + float2(0,dy)); + float4 c8 = tex2D(s0, tex + float2(dx,dy)); + +// calcul image floue (filtre gaussien) +float multipliers[9]= + {1,2,1, + 2,4,2, + 1,2,1}; + +float4 total=0; + total += c1 * multipliers[0]; + total += c2 * multipliers[1]; + total += c3 * multipliers[2]; + total += c4 * multipliers[3]; + total += ori * multipliers[4]; + total += c5 * multipliers[5]; + total += c6 * multipliers[6]; + total += c7 * multipliers[7]; + total += c8 * multipliers[8]; + + // 1/(1+2+1+2+4+2+1+2+1) = 1/ 16 = .0625 + total *= 0.0625f; + +// soustraction de l'image flou a l'image originale + total = 2*ori - total; +//return ori; + return total; +} \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/sharpen.psh b/MathCore.WPF/Shaders/new/sharpen.psh new file mode 100644 index 00000000..73fe8ddb --- /dev/null +++ b/MathCore.WPF/Shaders/new/sharpen.psh @@ -0,0 +1,31 @@ +// $MinimumShaderProfile: ps_2_0 + +sampler s0 : register(s0); +float4 p0 : register(c0); +float4 p1 : register(c1); + +#define width (p0[0]) +#define height (p0[1]) + +#define val0 (2.0) +#define val1 (-0.125) +#define effect_width (1.6) + +float4 main(float2 tex : TEXCOORD0) : COLOR { + float dx = effect_width / width; + float dy = effect_width / height; + + float4 c1 = tex2D(s0, tex + float2(-dx, -dy)) * val1; + float4 c2 = tex2D(s0, tex + float2( 0, -dy)) * val1; + float4 c3 = tex2D(s0, tex + float2(-dx, 0)) * val1; + float4 c4 = tex2D(s0, tex + float2( dx, 0)) * val1; + float4 c5 = tex2D(s0, tex + float2( 0, dy)) * val1; + float4 c6 = tex2D(s0, tex + float2( dx, dy)) * val1; + float4 c7 = tex2D(s0, tex + float2(-dx, +dy)) * val1; + float4 c8 = tex2D(s0, tex + float2(+dx, -dy)) * val1; + float4 c9 = tex2D(s0, tex) * val0; + + float4 c0 = (c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8 + c9); + + return c0; +} From 1fc802cd5c51c4ad64a94e446b06188946427532 Mon Sep 17 00:00:00 2001 From: Infarh Date: Sat, 5 Jul 2025 15:00:30 +0300 Subject: [PATCH 04/16] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=D0=BE=D0=B2=20ThreadSafeInvoke=20=D0=B4=D0=BB=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=B1=D0=B5=D0=B7=D0=BE=D0=BF?= =?UTF-8?q?=D0=B0=D1=81=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B2=D1=8B=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=87?= =?UTF-8?q?=D0=B8=D0=BA=D0=BE=D0=B2=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8?= =?UTF-8?q?=D0=B9=20EventHandler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Выполнена унификация кода Добавлен сбор возникших исключений для нескольких подписчиков события с последующей генерацией AggregateException --- .../Extensions/EventHandlerExtensions.cs | 81 ++++++++++--------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/MathCore.WPF/Extensions/EventHandlerExtensions.cs b/MathCore.WPF/Extensions/EventHandlerExtensions.cs index 6d9befd4..e858e404 100644 --- a/MathCore.WPF/Extensions/EventHandlerExtensions.cs +++ b/MathCore.WPF/Extensions/EventHandlerExtensions.cs @@ -4,51 +4,60 @@ // ReSharper disable once CheckNamespace namespace System; +/// Статический класс-расширение для потокобезопасного вызова событий public static class EventHandlerExtensions { + /// Потокобезопасный вызов события EventHandler + /// Событие + /// Источник события + /// Аргументы события public static void ThreadSafeInvoke(this EventHandler? Event, object? Sender, EventArgs? E) - { - if (Event is null) return; - - var e = E ?? EventArgs.Empty; - object[]? args = null; - foreach (var d in Event.GetInvocationList()) - switch (d.Target) - { - case ISynchronizeInvoke { InvokeRequired: true } synchronize_invoke: - synchronize_invoke.Invoke(d, args ??= [Sender, e]); - break; - - case DispatcherObject dispatcher_obj when !dispatcher_obj.CheckAccess(): - dispatcher_obj.Dispatcher.Invoke(d, Sender, e); - break; - - default: - d.DynamicInvoke(Sender, e); - break; - } - } + => ThreadSafeInvokeInternal(Event, Sender, E ?? EventArgs.Empty); + /// Потокобезопасный вызов события EventHandler с аргументом типа TArg + /// Тип аргумента события + /// Событие + /// Источник события + /// Аргументы события public static void ThreadSafeInvoke(this EventHandler? Event, object? Sender, TArg E) where TArg : EventArgs - { - if (Event is null) return; + => ThreadSafeInvokeInternal(Event, Sender, E); - object[]? args = null; - foreach (var d in Event.GetInvocationList()) - switch (d.Target) + /// Универсальный потокобезопасный вызов делегатов событий + /// Делегат события + /// Источник события + /// Аргументы события + private static void ThreadSafeInvokeInternal(Delegate? EventDelegate, object? Sender, object E) + { + if (EventDelegate.GetInvocationList() is not { Length: > 0 } delegates) return; + var args = new[] { Sender, E }; + List<(Delegate d, Exception error)>? exceptions = null; + foreach (var d in delegates) + try + { + switch (d.Target) + { + case ISynchronizeInvoke { InvokeRequired: true } sync: + sync.Invoke(d, args); // Потокобезопасный вызов через ISynchronizeInvoke + break; + case DispatcherObject dispatcher_obj when !dispatcher_obj.CheckAccess(): + dispatcher_obj.Dispatcher?.Invoke(() => + d.DynamicInvoke(args)); // Потокобезопасный вызов через Dispatcher + break; + default: + d.DynamicInvoke(args); // Обычный вызов + break; + } + } + catch (Exception error) when (delegates.Length > 1) { - case ISynchronizeInvoke { InvokeRequired: true } synchronize_invoke: - synchronize_invoke.Invoke(d, args ??= [Sender, E]); - break; + (exceptions ??= []).Add((d, error)); + } - case DispatcherObject dispatcher_obj when !dispatcher_obj.CheckAccess(): - dispatcher_obj.Dispatcher.Invoke(d, Sender, E); - break; + if (exceptions is not { Count: > 0 }) return; - default: - d.DynamicInvoke(Sender, E); - break; - } + throw new AggregateException( + $"Ошибка при выполнении обработчика события\r\n{exceptions.Select(d => $" {d.d.Target}.{d.d.Method.Name} : {d.error.Message}").ToSeparatedStr("\r\n")}", + [.. exceptions.Select(d => d.error)]); } } From ec0a975e8fd4fbe3b73adef3ac819a9b10ec059e Mon Sep 17 00:00:00 2001 From: Infarh Date: Sat, 5 Jul 2025 15:01:22 +0300 Subject: [PATCH 05/16] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81?= =?UTF-8?q?=D0=B0=20=D0=BC=D0=B5=D1=82=D0=B4=D0=BE=D0=B2-=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B8=D0=BD?= =?UTF-8?q?=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81=D0=B0=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=B0=D0=BD=D0=B4=D1=8B=20ICommand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/Extensions/CommandEx.cs | 37 ++++++++++++++++++++ MathCore.WPF/Extensions/CommandExtensions.cs | 26 -------------- 2 files changed, 37 insertions(+), 26 deletions(-) create mode 100644 MathCore.WPF/Extensions/CommandEx.cs delete mode 100644 MathCore.WPF/Extensions/CommandExtensions.cs diff --git a/MathCore.WPF/Extensions/CommandEx.cs b/MathCore.WPF/Extensions/CommandEx.cs new file mode 100644 index 00000000..2e6936b7 --- /dev/null +++ b/MathCore.WPF/Extensions/CommandEx.cs @@ -0,0 +1,37 @@ +using System.Windows.Input; + +using MathCore.WPF.Commands; + +namespace MathCore.WPF.Extensions; + +/// Статический класс-расширение для работы с командами WPF +public static class CommandEx +{ + /// Устанавливает имя и описание для команды + /// Тип команды + /// Команда + /// Имя команды + /// Описание команды + /// Команда с установленными именем и описанием + public static TCommand WithName(this TCommand Command, string Name, string? Description = null) + where TCommand : Command + { + Command.Name = Name; + if (Description is not null) + Command.Description = Description; + return Command; + } + + /// Пытается выполнить команду с указанным параметром + /// Команда + /// Параметр команды + /// Истина, если команда была успешно выполнена + public static bool TryExecute(this ICommand? Command, object Parameter) + { + if (Command is null || !Command.CanExecute(Parameter)) return false; + + Command.Execute(Parameter); + + return true; + } +} diff --git a/MathCore.WPF/Extensions/CommandExtensions.cs b/MathCore.WPF/Extensions/CommandExtensions.cs deleted file mode 100644 index 116fe1a9..00000000 --- a/MathCore.WPF/Extensions/CommandExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Windows.Input; - -using MathCore.WPF.Commands; - -namespace MathCore.WPF.Extensions; - -public static class CommandExtensions -{ - public static TCommand WithName(this TCommand Command, string Name, string? Description = null) - where TCommand : Command - { - Command.Name = Name; - if (Description is not null) - Command.Description = Description; - return Command; - } - - public static bool TryExecute(this ICommand? Command, object Parameter) - { - if (Command is null || !Command.CanExecute(Parameter)) return false; - - Command.Execute(Parameter); - - return true; - } -} From 718caebd1bb237e0a2f7f1980baeb49343f5e728 Mon Sep 17 00:00:00 2001 From: Infarh Date: Sat, 5 Jul 2025 15:02:43 +0300 Subject: [PATCH 06/16] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF.sln.DotSettings | 4 +- MathCore.WPF/AttachedProperties/DataGridEx.cs | 6 +-- MathCore.WPF/AttachedProperties/UI.cs | 6 +-- MathCore.WPF/Attributes/MayBeNullAttribute.cs | 22 ++++++++++ MathCore.WPF/Commands/Base/Command.cs | 41 ++++++++----------- MathCore.WPF/Commands/LambdaCommand.cs | 4 +- MathCore.WPF/Commands/LambdaCommandAsync.cs | 13 +++--- MathCore.WPF/ItemsCollection.cs | 16 ++++---- MathCore.WPF/LongPress.cs | 14 ++++--- 9 files changed, 74 insertions(+), 52 deletions(-) create mode 100644 MathCore.WPF/Attributes/MayBeNullAttribute.cs diff --git a/MathCore.WPF.sln.DotSettings b/MathCore.WPF.sln.DotSettings index bf06b76d..220e77f7 100644 --- a/MathCore.WPF.sln.DotSettings +++ b/MathCore.WPF.sln.DotSettings @@ -31,5 +31,7 @@ True True True + True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/MathCore.WPF/AttachedProperties/DataGridEx.cs b/MathCore.WPF/AttachedProperties/DataGridEx.cs index 94eb47e5..4445374e 100644 --- a/MathCore.WPF/AttachedProperties/DataGridEx.cs +++ b/MathCore.WPF/AttachedProperties/DataGridEx.cs @@ -13,7 +13,7 @@ public static class DataGridEx { #region Attached property DataGrid.UseDataAnnotations : bool - Использовать аннотации данных из пространства имён System.ComponentModel.DataAnnotation - /// Использовать аннотации данных из пространства имён System.DataAnnotation + /// Использовать аннотации данных из пространства имён public static readonly DependencyProperty UseDataAnnotationsProperty = DependencyProperty.RegisterAttached( "UseDataAnnotations", @@ -21,11 +21,11 @@ public static class DataGridEx typeof(DataGridEx), new(OnUseDataAnnotationsPropertyChanged)); - /// Использовать аннотации данных из пространства имён System.DataAnnotation + /// Использовать аннотации данных из пространства имён [AttachedPropertyBrowsableForType(typeof(DataGrid))] public static void SetUseDataAnnotations(DependencyObject D, bool value) => D.SetValue(UseDataAnnotationsProperty, value); - /// Использовать аннотации данных из пространства имён System.DataAnnotation + /// Использовать аннотации данных из пространства имён public static bool GetUseDataAnnotations(DependencyObject D) => (bool)D.GetValue(UseDataAnnotationsProperty); private static void OnUseDataAnnotationsPropertyChanged(DependencyObject D, DependencyPropertyChangedEventArgs E) diff --git a/MathCore.WPF/AttachedProperties/UI.cs b/MathCore.WPF/AttachedProperties/UI.cs index 0eadc404..7c1a331e 100644 --- a/MathCore.WPF/AttachedProperties/UI.cs +++ b/MathCore.WPF/AttachedProperties/UI.cs @@ -12,7 +12,7 @@ public static class UI "InputBinding", typeof(InputBinding), typeof(UI), - new(default(InputBinding), OnInputBindingChanged)); + new(null, OnInputBindingChanged)); /// Обработчик события изменения значения свойства /// Элемент, с которым ассоциирована коллекция горячих клавиш @@ -43,14 +43,14 @@ public static class UI /// Глобальные горячие клавиши public static GlobalHotKeysCollection GetHotKeys(DependencyObject element) { - if (element.GetValue(HotKeysProperty) is GlobalHotKeysCollection collection) + if (element.GetValue(HotKeysProperty) is GlobalHotKeysCollection collection) return collection; collection = []; if (element is FrameworkElement framework_element) framework_element.Unloaded += (e, _) => { - if(((DependencyObject)e).GetValue(HotKeysProperty) is GlobalHotKeysCollection keys) + if (((DependencyObject)e).GetValue(HotKeysProperty) is GlobalHotKeysCollection keys) keys.Dispose(); }; SetHotKeys(element, collection); diff --git a/MathCore.WPF/Attributes/MayBeNullAttribute.cs b/MathCore.WPF/Attributes/MayBeNullAttribute.cs new file mode 100644 index 00000000..58cbac4b --- /dev/null +++ b/MathCore.WPF/Attributes/MayBeNullAttribute.cs @@ -0,0 +1,22 @@ +#if !NET5_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace System.Diagnostics.CodeAnalysis; + +/// Specifies that is allowed as an input even if the corresponding type disallows it. +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)] +public sealed class AllowNullAttribute : Attribute +{ +} + +/// Specifies that an output may be even if the corresponding type disallows it. +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)] +public sealed class MaybeNullAttribute : Attribute +{ +} + +#endif \ No newline at end of file diff --git a/MathCore.WPF/Commands/Base/Command.cs b/MathCore.WPF/Commands/Base/Command.cs index 2e8623b3..0ed9fc18 100644 --- a/MathCore.WPF/Commands/Base/Command.cs +++ b/MathCore.WPF/Commands/Base/Command.cs @@ -189,7 +189,7 @@ public Command OnError(ExceptionEventHandler Handler) protected virtual void OnExecuted(object? p) { - if(Executed is not { } handler) return; + if (Executed is not { } handler) return; handler.ThreadSafeInvoke(this, p); } @@ -231,7 +231,7 @@ public virtual bool IsCanExecute { if (_IsCanExecute == value) return; _IsCanExecute = value; - OnPropertyChanged(nameof(IsCanExecute)); + OnPropertyChanged(); CommandManager.InvalidateRequerySuggested(); } } @@ -239,20 +239,14 @@ public virtual bool IsCanExecute #region Name : string? - Название /// Название - private string? _Name; - - /// Название - public string? Name { get => _Name; set => Set(ref _Name, value); } + public string? Name { get; set => Set(ref field, value); } #endregion #region Description : string? - Описание /// Описание - private string? _Description; - - /// Описание - public string? Description { get => _Description; set => Set(ref _Description, value); } + public string? Description { get; set => Set(ref field, value); } #endregion @@ -263,9 +257,7 @@ public virtual bool IsCanExecute /// public override object ProvideValue(IServiceProvider sp) { - //var target_value_provider = (IProvideValueTarget)sp.GetService(typeof(IProvideValueTarget)); - var target_value_provider = sp.GetValueTargetProvider(); - if (target_value_provider != null) + if (sp.GetValueTargetProvider() is { } target_value_provider) { var target = target_value_provider.TargetObject; _TargetObjectReference = target is null ? null : new WeakReference(target); @@ -273,13 +265,9 @@ public override object ProvideValue(IServiceProvider sp) _TargetPropertyReference = target_property is null ? null : new WeakReference(target_property); } - //var root_object_provider = (IRootObjectProvider)sp.GetService(typeof(IRootObjectProvider)); - var root_object_provider = sp.GetRootObjectProvider(); - if (root_object_provider != null) - { - var root = root_object_provider.RootObject; - _RootObjectReference = root is null ? null : new WeakReference(root); - } + if (sp.GetRootObjectProvider() is not { } root_object_provider) return this; + + _RootObjectReference = root_object_provider.RootObject is { } root ? new(root) : null; return this; } @@ -310,7 +298,7 @@ void ICommand.Execute(object? parameter) { _Observable?.OnError(error); - if(!OnError(error)) + if (!OnError(error)) throw; } } @@ -319,21 +307,26 @@ void ICommand.Execute(object? parameter) #region IDisposable + public void Dispose() { + if (IsDisposed) return; + Dispose(true); GC.SuppressFinalize(this); + + IsDisposed = true; } - private bool _Disposed; + protected bool IsDisposed { get; private set; } + protected virtual void Dispose(bool disposing) { - if (!disposing || _Disposed) return; + if (!disposing || IsDisposed) return; _Observable?.OnCompleted(); _Observable?.Dispose(); _Observable = null; - _Disposed = true; } #endregion diff --git a/MathCore.WPF/Commands/LambdaCommand.cs b/MathCore.WPF/Commands/LambdaCommand.cs index d0ca4658..f18115ab 100644 --- a/MathCore.WPF/Commands/LambdaCommand.cs +++ b/MathCore.WPF/Commands/LambdaCommand.cs @@ -140,7 +140,7 @@ public override bool CanExecute(object? parameter) => } /// -/// Типизированная лямбда-команда +/// Типизированная лямбда-команда
/// Позволяет быстро указывать методы для выполнения основного тела команды и определения возможности выполнения ///
public class LambdaCommand : Command, IObservableEx @@ -214,7 +214,7 @@ public static T ConvertParameter(object? parameter) { switch (parameter) { - case null: return default!; + case null: return default!; case T result: return result; } diff --git a/MathCore.WPF/Commands/LambdaCommandAsync.cs b/MathCore.WPF/Commands/LambdaCommandAsync.cs index 185b2010..9dd893c8 100644 --- a/MathCore.WPF/Commands/LambdaCommandAsync.cs +++ b/MathCore.WPF/Commands/LambdaCommandAsync.cs @@ -2,6 +2,9 @@ namespace MathCore.WPF.Commands; +/// Асинхронная команда +/// Асинхронный метод, выполняемый командой +/// Метод, определяющий возможность выполнения команды public class LambdaCommandAsync(Func ExecuteAsync, Func? CanExecuteAsync = null) : Command { @@ -30,7 +33,7 @@ public override async void Execute(object? parameter) { var background = Background; - var can_execute = background + var can_execute = background ? await Task.Run(() => CanExecute(parameter)) : CanExecute(parameter); if (!can_execute) return; @@ -64,7 +67,7 @@ public class LambdaCommandAsync(Func ExecuteAsync, Func? public bool Background { get; set; } public LambdaCommandAsync(Func ExecuteAsync, Func? CanExecuteAsync = null) - :this( + : this( ExecuteAsync is null ? throw new ArgumentNullException(nameof(ExecuteAsync)) : new Func(_ => ExecuteAsync()), CanExecuteAsync is null ? null : new Func(_ => CanExecuteAsync())) { } @@ -83,8 +86,8 @@ public override async void Execute(object? parameter) if (parameter is not T value) value = parameter is null ? default! - : background - ? await Task.Run(() => LambdaCommand.ConvertParameter(parameter)) + : background + ? await Task.Run(() => LambdaCommand.ConvertParameter(parameter)) : LambdaCommand.ConvertParameter(parameter); var can_execute = background @@ -94,7 +97,7 @@ public override async void Execute(object? parameter) if (!can_execute) return; var execute_async = background - ? Task.Run(() => _ExecuteAsync(value!)) + ? Task.Run(() => _ExecuteAsync(value!)) : _ExecuteAsync(value!); _ = Interlocked.Exchange(ref _ExecutingTask, execute_async); diff --git a/MathCore.WPF/ItemsCollection.cs b/MathCore.WPF/ItemsCollection.cs index b6b69e08..0bc7c6e6 100644 --- a/MathCore.WPF/ItemsCollection.cs +++ b/MathCore.WPF/ItemsCollection.cs @@ -14,7 +14,7 @@ public class ItemsCollection( Action? Editor = null) : SelectableCollection { - [field: MaybeNull] + [field: MaybeNull, AllowNull] public ICommand AddCommand => field ??= Command.New(OnCreateCommandExecuted); private async Task OnCreateCommandExecuted() { @@ -34,16 +34,14 @@ protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) if (ItemPropertyChanged is not null) switch (e.Action) { - case NotifyCollectionChangedAction.Add: - if (e.NewItems is not null) - foreach (var item in e.NewItems.OfType()) - item.PropertyChanged += ItemPropertyChanged; + case NotifyCollectionChangedAction.Add when e.NewItems?.OfType() is { } new_items: + foreach (var item in new_items) + item.PropertyChanged += ItemPropertyChanged; break; - case NotifyCollectionChangedAction.Remove: - if (e.OldItems is not null) - foreach (var item in e.OldItems.OfType()) - item.PropertyChanged -= ItemPropertyChanged; + case NotifyCollectionChangedAction.Remove when e.OldItems?.OfType() is { } old_items: + foreach (var item in old_items) + item.PropertyChanged -= ItemPropertyChanged; break; } diff --git a/MathCore.WPF/LongPress.cs b/MathCore.WPF/LongPress.cs index bf617292..4a8a5b18 100644 --- a/MathCore.WPF/LongPress.cs +++ b/MathCore.WPF/LongPress.cs @@ -36,7 +36,7 @@ public static class LongPress "Command", typeof(ICommand), typeof(LongPress), - new(default(ICommand), OnCommandPropertyChanged)); + new(null, OnCommandPropertyChanged)); private static void OnCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -46,7 +46,7 @@ private static void OnCommandPropertyChanged(DependencyObject d, DependencyPrope UnregisterHandlers((Control)attached_d); } - if(e.NewValue is null) + if (e.NewValue is null) { UnregisterHandlers((Control)d); d.SetValue(__AttachedControls, null); @@ -69,7 +69,11 @@ private static void UnregisterHandlers(Control control) control.MouseLeftButtonUp -= OnMouseUp; } - private static DependencyProperty __LastClickTime = DependencyProperty.RegisterAttached(nameof(__LastClickTime), typeof(DateTime), typeof(LongPress)); + private static readonly DependencyProperty __LastClickTime = DependencyProperty + .RegisterAttached( + nameof(__LastClickTime), + typeof(DateTime), + typeof(LongPress)); private static async void OnMouseDown(object sender, MouseButtonEventArgs e) { @@ -82,7 +86,7 @@ private static async void OnMouseDown(object sender, MouseButtonEventArgs e) await Task.Delay(timeout); if (control.GetValue(CommandProperty) is not ICommand command) return; - if(!Equals(down_time, control.GetValue(__LastClickTime))) return; + if (!Equals(down_time, control.GetValue(__LastClickTime))) return; var parameter = control.GetValue(CommandParameterProperty); command.TryExecute(parameter); @@ -111,7 +115,7 @@ private static async void OnMouseDown(object sender, MouseButtonEventArgs e) public static void SetCommandParameter(DependencyObject D, object value) => D.SetValue(CommandParameterProperty, value); /// Параметр команды - public static object GetCommandParameter(DependencyObject D) => (object)D.GetValue(CommandParameterProperty); + public static object GetCommandParameter(DependencyObject D) => D.GetValue(CommandParameterProperty); #endregion From 59555d5397195fc7c6c98c6956f60d8f297f1979 Mon Sep 17 00:00:00 2001 From: Infarh Date: Sat, 5 Jul 2025 21:21:49 +0300 Subject: [PATCH 07/16] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D0=B0=D1=8F=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/Commands/Base/CommandAsync.cs | 158 +++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 MathCore.WPF/Commands/Base/CommandAsync.cs diff --git a/MathCore.WPF/Commands/Base/CommandAsync.cs b/MathCore.WPF/Commands/Base/CommandAsync.cs new file mode 100644 index 00000000..cef8f518 --- /dev/null +++ b/MathCore.WPF/Commands/Base/CommandAsync.cs @@ -0,0 +1,158 @@ +using System.Windows.Input; + +using MathCore.Annotations; + +namespace MathCore.WPF.Commands; + +/// Абстрактная асинхронная команда с поддержкой отмены и параллельного выполнения +[PublicAPI] +internal abstract class CommandAsync : Command +{ + /// Список всех активных CancellationTokenSource для отмены и освобождения + private readonly List _ActiveCancellations = []; + +#if NET9_0_OR_GREATER + /// Объект блокировки для синхронизации доступа к спискам + private readonly Lock _ActiveCancellationsLock = new(); +#else + /// Объект блокировки для синхронизации доступа к спискам + private readonly object _ActiveCancellationsLock = new(); +#endif + /// Список всех выполняющихся задач + private readonly List _ActiveTasks = []; + + /// Выполнять команду в UI-контексте + public bool ExecuteInUIContext { get; set => Set(ref field, value); } = true; + + /// Дождаться завершения всех выполняющихся задач перед запуском новой + public bool WaitOne { get; set => Set(ref field, value); } + + /// Отменить все выполняющиеся задачи перед запуском новой + public bool CancelOtherExecution { get; set => Set(ref field, value); } + + /// Признак длительного выполнения команды + public bool LongRunning { get; set => Set(ref field, value); } + + /// Команда отмены всех выполняющихся задач + private CancelExecutionCommand? _CancelCommand; + + /// Команда отмены всех выполняющихся задач + public ICommand CancelCommand => _CancelCommand ??= new(this); + + /// Вспомогательная команда для отмены всех задач + private sealed class CancelExecutionCommand(CommandAsync command) : ICommand + { + /// + public event EventHandler? CanExecuteChanged; + + /// Вызвать событие изменения возможности выполнения + public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); + + /// + public bool CanExecute(object? p) + { + lock (command._ActiveCancellationsLock) + return command._ActiveCancellations.Any(c => !c.IsCancellationRequested); + } + + /// + public void Execute(object? p) + { + CancellationTokenSource[] snapshot; + lock (command._ActiveCancellationsLock) + snapshot = command._ActiveCancellations.ToArray(); + foreach (var c in snapshot) c.Cancel(); + } + } + + /// + public override bool CanExecute(object? parameter) + { + if (!base.CanExecute(parameter)) return false; + lock (_ActiveCancellationsLock) + return !WaitOne || _ActiveTasks.All(t => t.IsCompleted); + } + + /// + public override void Execute(object? parameter) => _ = ExecuteInternalAsync(parameter); + + /// Асинхронный запуск выполнения команды + /// Параметр команды + private async Task ExecuteInternalAsync(object? parameter) + { + if (!ExecuteInUIContext || LongRunning) + await Task.Yield().ConfigureAwait(false, LongRunning); + + // Отмена всех предыдущих задач, если требуется + if (CancelOtherExecution) + { + CancellationTokenSource[] snapshot; + lock (_ActiveCancellationsLock) + snapshot = _ActiveCancellations.ToArray(); + foreach (var c in snapshot) +#if NET8_0_OR_GREATER + await c.CancelAsync(); +#else + c.Cancel(); +#endif + } + else if (WaitOne) + { + Task[] need_to_wait; + lock (_ActiveCancellationsLock) + need_to_wait = _ActiveTasks.Where(t => !t.IsCompleted).ToArray(); + if (need_to_wait.Length > 0) + try { await Task.WhenAll(need_to_wait).ConfigureAwait(false); } catch { /* */ } + } + + // Создаём новый CTS для текущей задачи + var cts = new CancellationTokenSource(); + lock (_ActiveCancellationsLock) + _ActiveCancellations.Add(cts); + var cancel = cts.Token; + + Task main_task = null; + try + { + OnBeforeExecuted(parameter); + main_task = ExecuteAsync(parameter, cancel); + lock (_ActiveCancellationsLock) + _ActiveTasks.Add(main_task); + await main_task.ConfigureAwait(false); + OnExecuted(parameter); + } + catch (OperationCanceledException) { /* */ } + catch (Exception ex) { if (OnError(ex)) return; throw; } + finally + { + lock (_ActiveCancellationsLock) + { + _ActiveCancellations.Remove(cts); + _ActiveTasks.Remove(main_task); + } + DisposeCts(cts); + _CancelCommand?.RaiseCanExecuteChanged(); + } + return; + + static void DisposeCts(CancellationTokenSource cts) + { + try { cts.Dispose(); } catch { /* */ } + } + } + + /// Отменить все выполняющиеся задачи + private void CancelInternal() + { + CancellationTokenSource[] snapshot; + lock (_ActiveCancellationsLock) + snapshot = _ActiveCancellations.ToArray(); + foreach (var c in snapshot) c.Cancel(); + } + + /// Асинхронное выполнение команды + /// Параметр команды + /// Токен отмены + /// Задача выполнения команды + public abstract Task ExecuteAsync(object? parameter, CancellationToken Cancel); +} From 154a70cf637c2868bfb36b49ce227e905a74f7c2 Mon Sep 17 00:00:00 2001 From: Infarh Date: Wed, 30 Jul 2025 21:38:05 +0300 Subject: [PATCH 08/16] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=BA=D0=BE=D0=BD=20=D0=B8=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=BF=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D1=81=D1=82=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В файле `TestWindow7.xaml` заменён элемент `Ellipse` с изменёнными атрибутами. Добавлен новый класс окна `TestWindows8` в `TestWindows8.xaml` с необходимыми пространствами имён и ресурсами. В `TestWindows8.xaml.cs` реализована инициализация компонентов для нового окна. Создан класс `TestWindow8ViewModel` в `TestWindow8ViewModel.cs`, наследующий от `TitledViewModel`, с установленным заголовком окна. --- Tests/MathCore.WPF.WindowTest/TestWindow7.xaml | 7 ++++--- Tests/MathCore.WPF.WindowTest/TestWindows8.xaml | 17 +++++++++++++++++ .../TestWindows8.xaml.cs | 6 ++++++ .../ViewModels/TestWindow8ViewModel.cs | 11 +++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 Tests/MathCore.WPF.WindowTest/TestWindows8.xaml create mode 100644 Tests/MathCore.WPF.WindowTest/TestWindows8.xaml.cs create mode 100644 Tests/MathCore.WPF.WindowTest/ViewModels/TestWindow8ViewModel.cs diff --git a/Tests/MathCore.WPF.WindowTest/TestWindow7.xaml b/Tests/MathCore.WPF.WindowTest/TestWindow7.xaml index 70667179..5dca1b8f 100644 --- a/Tests/MathCore.WPF.WindowTest/TestWindow7.xaml +++ b/Tests/MathCore.WPF.WindowTest/TestWindow7.xaml @@ -25,9 +25,6 @@ - @@ -35,6 +32,10 @@ + diff --git a/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml new file mode 100644 index 00000000..993a6368 --- /dev/null +++ b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml.cs b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml.cs new file mode 100644 index 00000000..9df2bd3c --- /dev/null +++ b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml.cs @@ -0,0 +1,6 @@ +namespace MathCore.WPF.WindowTest; + +public partial class TestWindows8 +{ + public TestWindows8() => InitializeComponent(); +} diff --git a/Tests/MathCore.WPF.WindowTest/ViewModels/TestWindow8ViewModel.cs b/Tests/MathCore.WPF.WindowTest/ViewModels/TestWindow8ViewModel.cs new file mode 100644 index 00000000..387c54c2 --- /dev/null +++ b/Tests/MathCore.WPF.WindowTest/ViewModels/TestWindow8ViewModel.cs @@ -0,0 +1,11 @@ +using System.Windows.Markup; + +using MathCore.WPF.ViewModels; + +namespace MathCore.WPF.WindowTest.ViewModels; + +[MarkupExtensionReturnType(typeof(TestWindow8ViewModel))] +internal class TestWindow8ViewModel() : TitledViewModel("Тестовое окно 8") +{ + +} From 36afe9fa641ca8c9fc417bbc6721b5f9a5ee5746 Mon Sep 17 00:00:00 2001 From: Infarh Date: Wed, 30 Jul 2025 22:29:29 +0300 Subject: [PATCH 09/16] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B4=D0=BE=D0=BB=D0=B3=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B6=D0=B0=D1=82=D0=B8=D1=8F=20=D0=B2=20LongPress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В классе `LongPress` были внесены изменения, касающиеся обработки долгого нажатия и управления задачами с использованием `CancellationTokenSource`. Основные изменения включают: - Добавление обработки события ухода мыши с элемента (`MouseLeave`), что позволяет отменять текущую задачу при уходе мыши. - Улучшение управления задачами: теперь при нажатии мыши (`OnMouseDown`) и отпускании (`OnMouseUp`) происходит отмена предыдущих задач, если они существуют. - Упрощение кода, связанного с очисткой значений свойств, таких как `__AttachedControls` и `__CancellationTokenSource`, с использованием методов `ClearValue`. - Удаление неиспользуемого кода, связанного с свойством `PropName`, что делает код более чистым и понятным. --- MathCore.WPF/LongPress.cs | 125 +++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 41 deletions(-) diff --git a/MathCore.WPF/LongPress.cs b/MathCore.WPF/LongPress.cs index 4a8a5b18..8771abbd 100644 --- a/MathCore.WPF/LongPress.cs +++ b/MathCore.WPF/LongPress.cs @@ -40,20 +40,20 @@ public static class LongPress private static void OnCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d.GetValue(__AttachedControls) is { } attached_d) - { - if (ReferenceEquals(d, attached_d)) return; - UnregisterHandlers((Control)attached_d); - } + // Отписываемся от старых обработчиков если они есть + if (d.GetValue(__AttachedControls) is Control old_control) + UnregisterHandlers(old_control); + // Если новое значение null - очищаем всё и выходим if (e.NewValue is null) { - UnregisterHandlers((Control)d); - d.SetValue(__AttachedControls, null); + d.ClearValue(__AttachedControls); + d.ClearValue(__CancellationTokenSource); + return; } + // Устанавливаем новую связь и регистрируем обработчики d.SetValue(__AttachedControls, d); - RegisterHandlers((Control)d); } @@ -61,12 +61,22 @@ private static void RegisterHandlers(Control control) { control.MouseLeftButtonDown += OnMouseDown; control.MouseLeftButtonUp += OnMouseUp; + control.MouseLeave += OnMouseLeave; // Добавляем обработку ухода мыши с элемента } private static void UnregisterHandlers(Control control) { control.MouseLeftButtonDown -= OnMouseDown; control.MouseLeftButtonUp -= OnMouseUp; + control.MouseLeave -= OnMouseLeave; + + // Отменяем текущую задачу если она есть + if (control.GetValue(__CancellationTokenSource) is CancellationTokenSource cts) + { + cts.Cancel(); + cts.Dispose(); + control.ClearValue(__CancellationTokenSource); + } } private static readonly DependencyProperty __LastClickTime = DependencyProperty @@ -75,26 +85,85 @@ private static void UnregisterHandlers(Control control) typeof(DateTime), typeof(LongPress)); + private static readonly DependencyProperty __CancellationTokenSource = DependencyProperty + .RegisterAttached( + nameof(__CancellationTokenSource), + typeof(CancellationTokenSource), + typeof(LongPress)); + private static async void OnMouseDown(object sender, MouseButtonEventArgs e) { if (sender is not Control control) return; + // Отменяем предыдущую задачу если она есть + if (control.GetValue(__CancellationTokenSource) is CancellationTokenSource old_cts) + { + old_cts.Cancel(); + old_cts.Dispose(); + } + var down_time = DateTime.Now; control.SetValue(__LastClickTime, down_time); + + var cts = new CancellationTokenSource(); + control.SetValue(__CancellationTokenSource, cts); + var timeout = Math.Max(100, GetTimeout(control)); - await Task.Delay(timeout); + try + { + await Task.Delay(timeout, cts.Token); - if (control.GetValue(CommandProperty) is not ICommand command) return; - if (!Equals(down_time, control.GetValue(__LastClickTime))) return; + // Проверяем что задача не была отменена и время совпадает + if (cts.Token.IsCancellationRequested) return; + if (control.GetValue(CommandProperty) is not ICommand command) return; + if (!Equals(down_time, control.GetValue(__LastClickTime))) return; - var parameter = control.GetValue(CommandParameterProperty); - command.TryExecute(parameter); + var parameter = control.GetValue(CommandParameterProperty); + command.TryExecute(parameter); - control.RaiseEvent(new(ClickEvent, control)); + control.RaiseEvent(new(ClickEvent, control)); + } + catch (OperationCanceledException) + { + // Задача была отменена - это нормально + } + finally + { + // Очищаем CancellationTokenSource если он всё ещё наш + if (ReferenceEquals(control.GetValue(__CancellationTokenSource), cts)) + { + control.ClearValue(__CancellationTokenSource); + cts.Dispose(); + } + } } - private static void OnMouseUp(object sender, MouseButtonEventArgs e) => ((DependencyObject)sender).ClearValue(__LastClickTime); + private static void OnMouseUp(object sender, MouseButtonEventArgs e) + { + var control = (DependencyObject)sender; + control.ClearValue(__LastClickTime); + + // Отменяем текущую задачу + if (control.GetValue(__CancellationTokenSource) is CancellationTokenSource cts) + { + cts.Cancel(); + control.ClearValue(__CancellationTokenSource); + } + } + + private static void OnMouseLeave(object sender, MouseEventArgs e) + { + var control = (DependencyObject)sender; + control.ClearValue(__LastClickTime); + + // Отменяем текущую задачу при уходе мыши с элемента + if (control.GetValue(__CancellationTokenSource) is CancellationTokenSource cts) + { + cts.Cancel(); + control.ClearValue(__CancellationTokenSource); + } + } /// Команда долгого нажатия [AttachedPropertyBrowsableForType(typeof(Control))] @@ -159,30 +228,4 @@ private static async void OnMouseDown(object sender, MouseButtonEventArgs e) #endregion - - #region Attached property LongPress.PropName : string - Свойство - - /// Свойство - private static readonly DependencyPropertyKey __PropNameProperty = - DependencyProperty.RegisterAttachedReadOnly( - "PropName", - typeof(string), - typeof(LongPress), - new(default(string), OnPropNamePropertyChanged)); - - public static readonly DependencyProperty PropNameProperty = __PropNameProperty.DependencyProperty; - - private static void OnPropNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - - } - - /// Свойство - [AttachedPropertyBrowsableForType(typeof(Control))] - public static void SetPropName(DependencyObject D, string value) => D.SetValue(__PropNameProperty, value); - - /// Свойство - public static string GetPropName(DependencyObject D) => (string)D.GetValue(PropNameProperty); - - #endregion } From f4680b1e8d800750121f4443581d52016bee7602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB?= Date: Tue, 18 Nov 2025 10:24:07 +0300 Subject: [PATCH 10/16] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/Converters/AdditionMulti.cs | 5 ++- MathCore.WPF/Converters/Average.cs | 5 ++- MathCore.WPF/Converters/AverageMulti.cs | 38 +++++++++++++++++++ .../Converters/Base/DoubleValueConverter.cs | 21 ++++++++-- .../Base/MultiDoubleValueValueConverter.cs | 4 +- MathCore.WPF/Converters/CombineMulti.cs | 10 ++--- MathCore.WPF/Converters/DivideMulti.cs | 11 +++--- .../Converters/JoinStringConverter.cs | 2 +- MathCore.WPF/Converters/MultiplyMany.cs | 8 +++- MathCore.WPF/Converters/SubtractionMulti.cs | 8 +++- 10 files changed, 90 insertions(+), 22 deletions(-) create mode 100644 MathCore.WPF/Converters/AverageMulti.cs diff --git a/MathCore.WPF/Converters/AdditionMulti.cs b/MathCore.WPF/Converters/AdditionMulti.cs index 9f10b74e..6810e0cf 100644 --- a/MathCore.WPF/Converters/AdditionMulti.cs +++ b/MathCore.WPF/Converters/AdditionMulti.cs @@ -25,7 +25,10 @@ public class AdditionMulti : MultiValueValueConverter for (var i = 1; i < vv.Length; i++) { if (vv[i] is null) return double.NaN; - v += vv[i] is double dv ? dv : System.Convert.ToDouble(vv[i]); + if (!DoubleValueConverter.TryConvertToDouble(vv[i], c, out var value)) + return double.NaN; + + v += value; } return v; diff --git a/MathCore.WPF/Converters/Average.cs b/MathCore.WPF/Converters/Average.cs index 63244dd3..11dfad5f 100644 --- a/MathCore.WPF/Converters/Average.cs +++ b/MathCore.WPF/Converters/Average.cs @@ -8,6 +8,8 @@ namespace MathCore.WPF.Converters; +/// Последовательное скользящее усреднение значений с фиксированным окном +/// Размер окна [MarkupExtensionReturnType(typeof(Average))] public class Average(int Length) : SimpleDoubleValueConverter { @@ -15,6 +17,7 @@ public Average() : this(0) { } private readonly AverageValue _Value = new(Length); + /// Размер окна public int Length { get => _Value.Length; @@ -28,4 +31,4 @@ protected override double To(double v, double p) _Value.Reset(); return v; } -} \ No newline at end of file +} diff --git a/MathCore.WPF/Converters/AverageMulti.cs b/MathCore.WPF/Converters/AverageMulti.cs new file mode 100644 index 00000000..0ac9b759 --- /dev/null +++ b/MathCore.WPF/Converters/AverageMulti.cs @@ -0,0 +1,38 @@ +using System.Globalization; +using System.Windows.Markup; + +using MathCore.WPF.Converters.Base; + +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global + +namespace MathCore.WPF.Converters; + +[MarkupExtensionReturnType(typeof(AverageMulti))] +public class AverageMulti() : MultiValueValueConverter +{ + protected override object? Convert(object?[]? vv, Type? t, object? p, CultureInfo? c) + { + switch (vv) + { + case null: + return null; + case [null]: + return double.NaN; + } + + var v = vv[0] is double d ? d : System.Convert.ToDouble(vv[0]); + + for (var i = 1; i < vv.Length; i++) + { + if (vv[i] is null) return double.NaN; + + if (!DoubleValueConverter.TryConvertToDouble(vv[i], c, out var value)) + return double.NaN; + + v += value; + } + + return v / vv.Length; + } +} \ No newline at end of file diff --git a/MathCore.WPF/Converters/Base/DoubleValueConverter.cs b/MathCore.WPF/Converters/Base/DoubleValueConverter.cs index 424870e5..83848739 100644 --- a/MathCore.WPF/Converters/Base/DoubleValueConverter.cs +++ b/MathCore.WPF/Converters/Base/DoubleValueConverter.cs @@ -61,7 +61,22 @@ public static double ConvertToDouble(object? obj, IFormatProvider format) /// Результат успешности преобразования public static bool TryConvertToDouble(object? obj, IFormatProvider format, out double value) { - if (Equals(obj, Binding.DoNothing) || Equals(obj, DependencyProperty.UnsetValue)) + // Проверка специальных/невалидных значений + if (obj is null || Equals(obj, Binding.DoNothing) || Equals(obj, DependencyProperty.UnsetValue) || obj == DBNull.Value) + { + value = double.NaN; + return false; + } + + // SQL-тип с IsNull + if (obj is System.Data.SqlTypes.INullable sql_null && sql_null.IsNull) + { + value = double.NaN; + return false; + } + + // Пустая или состоящая из пробелов строка — считать невалидной + if (obj is string s && string.IsNullOrWhiteSpace(s)) { value = double.NaN; return false; @@ -69,8 +84,8 @@ public static bool TryConvertToDouble(object? obj, IFormatProvider format, out d switch (obj) { - case string str when double.TryParse(str, NumberStyles.Any, format, out value) || - double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out value) || + case string str when double.TryParse(str, NumberStyles.Any, format, out value) || + double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out value) || double.TryParse(str, NumberStyles.Any, __NotInvarianceFormat, out value): return true; diff --git a/MathCore.WPF/Converters/Base/MultiDoubleValueValueConverter.cs b/MathCore.WPF/Converters/Base/MultiDoubleValueValueConverter.cs index 90820730..75158315 100644 --- a/MathCore.WPF/Converters/Base/MultiDoubleValueValueConverter.cs +++ b/MathCore.WPF/Converters/Base/MultiDoubleValueValueConverter.cs @@ -11,9 +11,9 @@ public abstract class MultiDoubleValueValueConverter : MultiValueValueConverter /// protected override object Convert(object[]? vv, Type? t, object? p, CultureInfo? c) { - double[] value = Array.Empty(); + var value = Array.Empty(); - if(vv is { Length: > 0 }) + if (vv is { Length: > 0 }) { value = new double[vv.Length]; for (var i = 0; i < vv.Length; i++) diff --git a/MathCore.WPF/Converters/CombineMulti.cs b/MathCore.WPF/Converters/CombineMulti.cs index d21ded49..59ab8596 100644 --- a/MathCore.WPF/Converters/CombineMulti.cs +++ b/MathCore.WPF/Converters/CombineMulti.cs @@ -16,23 +16,23 @@ public CombineMulti() : this(null, null) { } public CombineMulti(IMultiValueConverter First) : this(First, null) { } - [ConstructorArgument("First")] + [ConstructorArgument(nameof(First))] public IMultiValueConverter? First { get; set; } = First; - [ConstructorArgument("Then")] + [ConstructorArgument(nameof(Then))] public IValueConverter? Then { get; set; } = Then; protected override object? Convert(object[]? vv, Type? t, object? p, CultureInfo? c) { var result = (First ?? throw new InvalidOperationException("Не задан первичный конвертер значений")).Convert(vv, t, p, c); - return Then is { } then - ? then.Convert(result, t, p, c) + return Then is { } then + ? then.Convert(result, t, p, c) : result; } protected override object[]? ConvertBack(object? v, Type[]? tt, object? p, CultureInfo? c) { - if (Then is { } then) + if (Then is { } then) v = then.ConvertBack(v, v != null ? v.GetType() : typeof(object), p, c); return (First ?? throw new InvalidOperationException("Не задан первичный конвертер значений")).ConvertBack(v, tt, p, c); } diff --git a/MathCore.WPF/Converters/DivideMulti.cs b/MathCore.WPF/Converters/DivideMulti.cs index ba491f71..035f4497 100644 --- a/MathCore.WPF/Converters/DivideMulti.cs +++ b/MathCore.WPF/Converters/DivideMulti.cs @@ -23,12 +23,13 @@ public class DivideMulti : MultiValueValueConverter for (var i = 1; i < vv.Length; i++) { if (vv[i] is null or double.NaN) return double.NaN; - var div = vv[i] is double dv ? dv : System.Convert.ToDouble(vv[i]); + if (!DoubleValueConverter.TryConvertToDouble(vv[i], c, out var div)) + return double.NaN; if (div == 0) - return v == 0 - ? double.NaN - : v > 0 - ? double.PositiveInfinity + return v == 0 + ? double.NaN + : v > 0 + ? double.PositiveInfinity : double.NegativeInfinity; v /= div; } diff --git a/MathCore.WPF/Converters/JoinStringConverter.cs b/MathCore.WPF/Converters/JoinStringConverter.cs index 47cff0c5..296095e8 100644 --- a/MathCore.WPF/Converters/JoinStringConverter.cs +++ b/MathCore.WPF/Converters/JoinStringConverter.cs @@ -17,6 +17,6 @@ public object Convert(object[]? values, Type TargetType, object? parameter, Cult var separator = parameter as string ?? " "; - return str.Split([separator], StringSplitOptions.None).Cast().ToArray(); + return [.. str.Split([separator], StringSplitOptions.None).Cast()]; } } \ No newline at end of file diff --git a/MathCore.WPF/Converters/MultiplyMany.cs b/MathCore.WPF/Converters/MultiplyMany.cs index df7b59b5..9ed32c35 100644 --- a/MathCore.WPF/Converters/MultiplyMany.cs +++ b/MathCore.WPF/Converters/MultiplyMany.cs @@ -18,12 +18,16 @@ public class MultiplyMany : MultiValueValueConverter return double.NaN; } - var v = vv[0] is double d ? d : System.Convert.ToDouble(vv[0]); + if (!DoubleValueConverter.TryConvertToDouble(vv[0], c, out var value)) + return double.NaN; + var v = value; for (var i = 1; i < vv.Length; i++) { if (vv[i] is null) return double.NaN; - v *= vv[i] is double dv ? dv : System.Convert.ToDouble(vv[i]); + if (!DoubleValueConverter.TryConvertToDouble(vv[i], c, out value)) + return double.NaN; + v *= value; } return v; diff --git a/MathCore.WPF/Converters/SubtractionMulti.cs b/MathCore.WPF/Converters/SubtractionMulti.cs index 9f7e88b3..bcb30365 100644 --- a/MathCore.WPF/Converters/SubtractionMulti.cs +++ b/MathCore.WPF/Converters/SubtractionMulti.cs @@ -20,12 +20,16 @@ public class SubtractionMulti : MultiValueValueConverter return double.NaN; } - var v = vv[0] is double d ? d : System.Convert.ToDouble(vv[0]); + if (!DoubleValueConverter.TryConvertToDouble(vv[0], c, out var value)) + return double.NaN; + var v = value; for (var i = 1; i < vv.Length; i++) { if (vv[i] is null) return double.NaN; - v -= vv[i] is double dv ? dv : System.Convert.ToDouble(vv[i]); + if (!DoubleValueConverter.TryConvertToDouble(vv[i], c, out value)) + return double.NaN; + v -= value; } return v; From 89e8057d744cff4e482b161ece7ccc8839ca6b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB?= Date: Tue, 18 Nov 2025 10:27:06 +0300 Subject: [PATCH 11/16] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4-=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BB=D0=BA=D0=B0=D1=81=D1=81=D0=B0=20Command,=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B7=D0=B2=D0=BE=D0=BB=D1=8F=D1=8E=D1=89=D0=B8=D0=B9=20=D0=B2?= =?UTF-8?q?=20fluent-=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9?= =?UTF-8?q?=D1=81=D0=B5=20=D0=BC=D0=B5=D0=BD=D1=8F=D1=82=D1=8C=20=D0=BE?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=B0=D0=BD=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/Commands/CommandEx.cs | 8 ++++---- MathCore.WPF/Extensions/CommandEx.cs | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/MathCore.WPF/Commands/CommandEx.cs b/MathCore.WPF/Commands/CommandEx.cs index b5af5a06..e396f0d7 100644 --- a/MathCore.WPF/Commands/CommandEx.cs +++ b/MathCore.WPF/Commands/CommandEx.cs @@ -2,7 +2,7 @@ public static class CommandEx { - public static TCommand CatchException(this TCommand Command, Action Handler) + public static TCommand CatchException(this TCommand Command, Action Handler) where TCommand : Command { void Catch(object sender, ExceptionEventHandlerArgs e) => Handler(e.Argument); @@ -11,7 +11,7 @@ public static TCommand CatchException(this TCommand Command, Action(this TCommand Command, Action Handler) + public static TCommand CatchException(this TCommand Command, Action Handler) where TCommand : Command where TException : Exception { @@ -25,7 +25,7 @@ void Catch(object sender, ExceptionEventHandlerArgs e) return Command; } - public static TCommand CatchException(this TCommand Command, Func Handler) + public static TCommand CatchException(this TCommand Command, Func Handler) where TCommand : Command { void Catch(object sender, ExceptionEventHandlerArgs e) @@ -38,7 +38,7 @@ void Catch(object sender, ExceptionEventHandlerArgs e) return Command; } - public static TCommand CatchException(this TCommand Command, Func Handler) + public static TCommand CatchException(this TCommand Command, Func Handler) where TCommand : Command where TException : Exception { diff --git a/MathCore.WPF/Extensions/CommandEx.cs b/MathCore.WPF/Extensions/CommandEx.cs index 2e6936b7..09a9cb0e 100644 --- a/MathCore.WPF/Extensions/CommandEx.cs +++ b/MathCore.WPF/Extensions/CommandEx.cs @@ -22,6 +22,18 @@ public static TCommand WithName(this TCommand Command, string Name, st return Command; } + /// Устанавливает описание для команды + /// Тип команды + /// Команда + /// Описание команды + /// Команда с установленными описанием + public static TCommand WithDescription(this TCommand Command, string? Description) + where TCommand : Command + { + Command.Description = Description; + return Command; + } + /// Пытается выполнить команду с указанным параметром /// Команда /// Параметр команды From 9084c1c9de6a13dfdf687526dc72ff588cb07b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB?= Date: Tue, 18 Nov 2025 11:37:43 +0300 Subject: [PATCH 12/16] =?UTF-8?q?=D0=9C=D0=B8=D0=B3=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BD=D0=B0=20.NET10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/Commands/Base/Command.cs | 4 +- MathCore.WPF/Manager.cs | 46 +++++++++---------- MathCore.WPF/MathCore.WPF.csproj | 12 ++--- .../MathCore.WPF.ConsoleTest.csproj | 2 +- .../MathCore.WPF.Tests.csproj | 10 ++-- .../MathCore.WPF.WindowTest.csproj | 6 +-- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/MathCore.WPF/Commands/Base/Command.cs b/MathCore.WPF/Commands/Base/Command.cs index 0ed9fc18..25fef2b8 100644 --- a/MathCore.WPF/Commands/Base/Command.cs +++ b/MathCore.WPF/Commands/Base/Command.cs @@ -191,7 +191,7 @@ protected virtual void OnExecuted(object? p) { if (Executed is not { } handler) return; - handler.ThreadSafeInvoke(this, p); + handler.ThreadSafeInvoke(this, new EventArgs(p)); } public event EventHandler>? BeforeExecuted; @@ -200,7 +200,7 @@ protected virtual void OnBeforeExecuted(object? p) { if (BeforeExecuted is not { } handler) return; - handler.ThreadSafeInvoke(this, p); + handler.ThreadSafeInvoke(this, new EventArgs(p)); } #endregion diff --git a/MathCore.WPF/Manager.cs b/MathCore.WPF/Manager.cs index 7d74c4da..0a656704 100644 --- a/MathCore.WPF/Manager.cs +++ b/MathCore.WPF/Manager.cs @@ -19,7 +19,7 @@ public static class ElementManager public static ElementControllersCollection GetBehaviors(DependencyObject obj) { var collection = (ElementControllersCollection?)obj.GetValue(ControllersProperty); - if(collection != null) return collection; + if (collection != null) return collection; collection = []; obj.SetValue(ControllersProperty, collection); return collection; @@ -29,10 +29,10 @@ private static void OnControllersChanged(DependencyObject? obj, DependencyProper { var old_value = (ElementControllersCollection?)args.OldValue; var new_value = (ElementControllersCollection?)args.NewValue; - if(old_value == new_value) return; - if(old_value?.Element != null) old_value.ResetElement(); - if(new_value is null || obj is null) return; - if(new_value.Element != null) throw new InvalidOperationException(); + if (old_value == new_value) return; + if (old_value?.Element != null) old_value.ResetElement(); + if (new_value is null || obj is null) return; + if (new_value.Element != null) throw new InvalidOperationException(); new_value.SetElement(obj); } @@ -57,28 +57,28 @@ public abstract class ElementController : ElementController /// public override void SetElement(DependencyObject? element) { - if(element is null) + if (element is null) { ResetElement(); return; } - if(element is not TElement e) + if (element is not TElement e) throw new ArgumentException($"Целевой объект не является объектом типа {typeof(TElement)}"); SetElement(e); } protected virtual void SetElement(TElement element) { - if(ReferenceEquals(_Element, element)) return; - if(element is null) throw new ArgumentNullException(nameof(element)); + if (ReferenceEquals(_Element, element)) return; + if (element is null) throw new ArgumentNullException(nameof(element)); ResetElement(); ElementSet?.Invoke(this, _Element = element); } public override void ResetElement() { - if(_Element != null) + if (_Element != null) ElementReset?.Invoke(this, _Element); _Element = null; } @@ -94,9 +94,9 @@ public DependencyObject Element get => _Element; set { - if(ReferenceEquals(_Element, value)) return; + if (ReferenceEquals(_Element, value)) return; _Element = value ?? throw new ArgumentNullException(nameof(value)); - for(var i = 0; i < _Items.Count; i++) + for (var i = 0; i < _Items.Count; i++) _Items[i].SetElement(value); } } @@ -115,7 +115,7 @@ public void Add(ElementController controller) public bool Remove(ElementController? controller) { var remove = _Items.Remove(controller); - if(remove) controller.ResetElement(); + if (remove) controller.ResetElement(); return remove; } @@ -176,17 +176,17 @@ public class ConditionalEventTrigger : FrameworkContentElement { private static readonly RoutedEvent TriggerActionsEvent = EventManager .RegisterRoutedEvent( - "TriggerActions", - RoutingStrategy.Direct, - typeof(EventHandler), + "TriggerActions", + RoutingStrategy.Direct, + typeof(EventHandler), typeof(ConditionalEventTrigger)); public RoutedEvent RoutedEvent { get; set; } public static readonly DependencyProperty ExcludedSourceNamesProperty = DependencyProperty .Register( - nameof(ExcludedSourceNames), - typeof(List), - typeof(ConditionalEventTrigger), + nameof(ExcludedSourceNames), + typeof(List), + typeof(ConditionalEventTrigger), new(new List())); public List ExcludedSourceNames @@ -223,7 +223,7 @@ public List Actions { // When "Triggers" is set, register handlers for each trigger in the list var element = (FrameworkElement)s; - foreach(var trigger in (List)e.NewValue) + foreach (var trigger in (List)e.NewValue) element.AddHandler(trigger.RoutedEvent, new RoutedEventHandler((_, e2) => trigger.OnRoutedEvent(element, e2))); } }); @@ -231,12 +231,12 @@ public List Actions // When an event fires, check the condition and if it is true fire the actions private void OnRoutedEvent(FrameworkElement element, RoutedEventArgs args) { - if(args.OriginalSource is not FrameworkElement sender) return; + if (args.OriginalSource is not FrameworkElement sender) return; DataContext = element.DataContext; // Allow data binding to access element properties - if(ExcludedSourceNames.Any(x => x.Equals(sender.Name))) return; + if (ExcludedSourceNames.Any(x => x.Equals(sender.Name))) return; // Construct an EventTrigger containing the actions, then trigger it var trigger = new EventTrigger { RoutedEvent = TriggerActionsEvent }; - foreach(var action in Actions) + foreach (var action in Actions) trigger.Actions.Add(action); element.Triggers.Add(trigger); diff --git a/MathCore.WPF/MathCore.WPF.csproj b/MathCore.WPF/MathCore.WPF.csproj index 4a22d4b9..0275b4d1 100644 --- a/MathCore.WPF/MathCore.WPF.csproj +++ b/MathCore.WPF/MathCore.WPF.csproj @@ -9,12 +9,12 @@ + net10.0-windows; net9.0-windows; net8.0-windows; net7.0-windows; net6.0-windows; net5.0-windows; - netcoreapp3.1-windows; net4.8-windows; net4.7-windows; net4.6.1-windows; @@ -51,7 +51,7 @@ - + @@ -72,10 +72,6 @@ - - - - @@ -97,6 +93,10 @@ + + + + 1701;1702; diff --git a/Tests/MathCore.WPF.ConsoleTest/MathCore.WPF.ConsoleTest.csproj b/Tests/MathCore.WPF.ConsoleTest/MathCore.WPF.ConsoleTest.csproj index 4f46db15..0b8f8f2b 100644 --- a/Tests/MathCore.WPF.ConsoleTest/MathCore.WPF.ConsoleTest.csproj +++ b/Tests/MathCore.WPF.ConsoleTest/MathCore.WPF.ConsoleTest.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable preview diff --git a/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj b/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj index 5bf29084..d51b56f8 100644 --- a/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj +++ b/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj @@ -1,7 +1,7 @@  - net9.0-windows + net10.0-windows false enable enable @@ -9,10 +9,10 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj b/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj index 544ab5e8..e9774e80 100644 --- a/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj +++ b/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj @@ -2,7 +2,7 @@ WinExe - net9.0-windows + net10.0-windows enable enable true @@ -24,10 +24,10 @@ - + - + From b921e976448a3c8454c4f089857fd2fdca4f7193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB?= Date: Tue, 18 Nov 2025 11:47:00 +0300 Subject: [PATCH 13/16] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=86=D0=B8=D0=B9=20?= =?UTF-8?q?Copilot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/copilot-instructions.md | 85 +++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4786551a..2adf293f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,15 +1,70 @@ -Всегда отвечай мне используя русский язык. -Всегда пиши комментарии в коде на русском языке. -Комментарии к классам, структурам делегатам и перечислениям, а также к их членам всегда пиши в системном виде. -При написании комментариев (ели они короткие) в коде предпочитай размещение комментария в конце той же строке, что и сам комментируемый код. -Старайся избегать тривиальных комментариев. -При герерации кода старайся минимизировать количество фигурных скобок. -При генерации кода используй самые современные виды синтаксических конструкций языка. -Всегда старайся минимизировтаь размер кода если не запрошено иное. -Используй стиль именования локальных переменных snake_case. -Используй стиль именования входных переменных методов PascalCase. -Используй стиль именования полей классов _PascalCase для нестатических переменных и __PascalCase для статических переменных. -Ппредпочитай английский язык при именовании переменных, методов, классов и прочих сущностей. -При инициализации массивов, списков и словарей используй выражения инициализации массивов. -При объявлении переменных предпочитай использовать ключевое слово var. -При написании системных комментариев старайся писать их компактно в одну строку, если длина текста небольшая. \ No newline at end of file +# Правила для GitHub Copilot + +- Всегда отвечай, используя русский язык +- Всегда пиши комментарии в коде на русском языке + +## Комментарии +- Короткие пояснительные комментарии располагай в конце той же строки, что и код // кратко по делу +- Старайся избегать тривиальных комментариев + +## XML‑документация +- Документируй классы, структуры, делегаты, перечисления и их члены только XML‑комментариями +- Одинарное предложение пиши в одной строке внутри тега и без точки в конце +- Каждый тег XML‑комментария располагай на отдельной строке +- Порядок тегов: `` → `` → `` → `` → `` → `` +- Для сложных публичных метдов генерируй блок с простым примером использования кода внутри тега `` + +Примеры: +- `Краткое описание сущности` +- `Описание параметра` +- `Описание возвращаемого значения` + +## Синтаксис и минимализм +- При генерации кода используй современные конструкции языка, совместимые с целевыми платформами проекта +- Стремись минимизировать количество фигурных скобок за счёт expression‑bodied членов и switch‑выражений +- Не убирай фигурные скобки в многострочных конструкциях ради читаемости +- Всегда старайся минимизировать размер кода, если не запрошено иное + +Разрешённые современные приёмы (когда поддерживается целевой платформой): +- file‑scoped namespace +- expression‑bodied члены +- switch‑выражения и pattern matching +- target‑typed `new` +- collection expressions и инициализаторы коллекций +- `using var` и `await using` +- операторы `??`, `??=`, `is not`, `with` +- упрощение nullable-присвоения `target?.Property = 15;` вместо `if(target is not null) target.Property = 15;` + +## Именование +- Локальные переменные: `snake_case` +- Параметры методов: `PascalCase` +- Поля экземпляров: `_PascalCase` +- Статические поля: `__PascalCase` +- Константы: `PascalCase` +- Публичные типы и члены API: `PascalCase` +- Предпочитай английский язык при именовании переменных, методов, классов и прочих сущностей + +## Инициализация и объявления +- При инициализации массивов, списков и словарей используй выражения инициализации массивов/коллекций +- При объявлении переменных предпочитай использовать ключевое слово `var` (кроме случаев, когда явный тип заметно повышает понятность) + +## Форматирование +- Короткие системные комментарии пиши компактно в одну строку +- Удаляй неиспользуемые `using`, сортируй и группируй директивы `using` +- Разделяй логические блоки пустыми строками по мере необходимости, избегай лишних переносов + +## Практики .NET +- Включай `#nullable enable` там, где это поддерживается +- Используй guard‑выражения, например `ArgumentNullException.ThrowIfNull(x)` +- Предпочитай Try‑паттерны для контроля потока вместо исключений +- При генерации метода добавляй в его начале блок проверки входных параметров. Отделяй этот блок пустой строкой от остального тела метода +- При генерации публичных свойств у моделей-представления MVVM (классов, реализующих INotifyPropertyChanged) используй следующий формат (в одну строку): +```csharp +/// Описание свойства +public string PropertyName { get; set => Set(ref field, value); } +``` +- Для простых лаконичных методов используй expression‑bodied синтаксис, записанный в одну строку. + +## Совместимость целей +- В рабочем пространстве используются целевые платформы: `.NET Standard 2.0` и `.NET 10` +- Применяй современные возможности языка и платформы только если они доступны для соответствующей целевой платформы проекта \ No newline at end of file From 3974bb819bfbc6026f56c9e7878f2070fbf9e9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB?= Date: Tue, 18 Nov 2025 11:47:24 +0300 Subject: [PATCH 14/16] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20ci-cd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish.yml | 12 ++++++------ .github/workflows/testing.yml | 2 +- MathCore.WPF.sln | 3 +-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d29cd544..6912cf48 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,9 +2,9 @@ name: Publish NuGet.org on: push: - branches: + branches: - master - paths-ignore: + paths-ignore: - '.github/workflows/**' - '**.md' - '**.docx' @@ -18,7 +18,7 @@ env: jobs: build: name: Build - runs-on: windows-latest + runs-on: windows-latest steps: - name: Checkout @@ -29,7 +29,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Cache NuGet uses: actions/cache@v4 @@ -69,7 +69,7 @@ jobs: steps: - name: Get artifact - uses: actions/download-artifact@v4.1.8 + uses: actions/download-artifact@v5 id: download with: name: Release @@ -85,7 +85,7 @@ jobs: steps: - name: Get artifact - uses: actions/download-artifact@v4.1.8 + uses: actions/download-artifact@v5 id: download with: name: Release diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 7ebca5ed..1e5b2533 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -40,7 +40,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Building run: | diff --git a/MathCore.WPF.sln b/MathCore.WPF.sln index ffc59b3c..b123683e 100644 --- a/MathCore.WPF.sln +++ b/MathCore.WPF.sln @@ -25,8 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{40F6 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C83B7C5C-5536-4BDA-9447-7B9A6393F30E}" ProjectSection(SolutionItems) = preProject - .github\workflows\publish-github.yml = .github\workflows\publish-github.yml - .github\workflows\publish-nuget.yml = .github\workflows\publish-nuget.yml + .github\workflows\publish.yml = .github\workflows\publish.yml .github\workflows\testing.yml = .github\workflows\testing.yml EndProjectSection EndProject From bb7abeef8440cd1da005df40e603ad636797cdf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB?= Date: Tue, 18 Nov 2025 12:14:12 +0300 Subject: [PATCH 15/16] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/CollectionSelector.cs | 39 +++++++------------------ MathCore.WPF/CollectionViewShaper.cs | 4 +-- MathCore.WPF/EventsTrigger.cs | 4 +-- MathCore.WPF/FileDialogEx.cs | 18 ++++++++++-- MathCore.WPF/FileInfoCollection.cs | 2 +- MathCore.WPF/GIF.cs | 18 ++++++------ MathCore.WPF/InputCultureManager.cs | 4 +-- MathCore.WPF/LongPress.cs | 14 ++++----- MathCore.WPF/MouseWheelGesture.cs | 2 +- MathCore.WPF/RadialProgressIndicator.cs | 36 +++++++++++------------ 10 files changed, 68 insertions(+), 73 deletions(-) diff --git a/MathCore.WPF/CollectionSelector.cs b/MathCore.WPF/CollectionSelector.cs index 32a43de6..1eaf35b4 100644 --- a/MathCore.WPF/CollectionSelector.cs +++ b/MathCore.WPF/CollectionSelector.cs @@ -13,50 +13,33 @@ public class CollectionSelector(bool SelectFirstItem = true) : ViewModel { #region Items : IEnumerable - Коллекция - /// Коллекция - private IEnumerable? _Items; - /// Коллекция public IEnumerable? Items { - get => _Items; + get; set { - if (Set(ref _Items, value)) + if (Set(ref field, value)) SelectedItem = value switch { - T[] { Length : > 0 } array => array[0], - List { Count : > 0 } list => list[0], - LinkedList { First.Value : { } first_value } => first_value, - ObservableCollection { Count: > 0 } collection => collection[0], - IList { Count : > 0 } list => list[0], - { } items => items.FirstOrDefault(), - _ => default + T[] { Length: > 0 } array => array[0], + List { Count: > 0 } list => list[0], + LinkedList { First.Value: { } first_value } => first_value, + ObservableCollection { Count: > 0 } collection => collection[0], + IList { Count: > 0 } list => list[0], + { } items => items.FirstOrDefault(), + _ => default }; } } #endregion - #region SelectedItem : T - Выбранный элемент - /// Выбранный элемент - private T? _SelectedItem; - - /// Выбранный элемент - public T? SelectedItem { get => _SelectedItem; set => Set(ref _SelectedItem, value); } - - #endregion - - #region SelectFirstItem : bool - Выбирать первый элемент для нового значения коллекции - - /// Выбирать первый элемент для нового значения коллекции - private bool _SelectFirstItem = SelectFirstItem; + public T? SelectedItem { get; set => Set(ref field, value); } /// Выбирать первый элемент для нового значения коллекции - public bool SelectFirstItem { get => _SelectFirstItem; set => Set(ref _SelectFirstItem, value); } - - #endregion + public bool SelectFirstItem { get; set => Set(ref field, value); } = SelectFirstItem; public CollectionSelector(IEnumerable Items, bool SelectFirstItem = true) : this(SelectFirstItem) => this.Items = Items; diff --git a/MathCore.WPF/CollectionViewShaper.cs b/MathCore.WPF/CollectionViewShaper.cs index ccdb1efc..192bfb1f 100644 --- a/MathCore.WPF/CollectionViewShaper.cs +++ b/MathCore.WPF/CollectionViewShaper.cs @@ -6,7 +6,7 @@ namespace MathCore.WPF; -/// +/// Формирует fluent-api для задания представления коллекции WPF с помощью LINQ-подобного синтаксиса /// /// /// // Collection to which the view is bound @@ -128,7 +128,7 @@ public CollectionViewShaper GroupBy(Expression> selector) private static string GetPropertyPath(Expression expression) { var names = new Stack(); - var expr = expression; + var expr = expression; while (expr is not null and not ParameterExpression and not ConstantExpression) { if (expr is not MemberExpression member) diff --git a/MathCore.WPF/EventsTrigger.cs b/MathCore.WPF/EventsTrigger.cs index 717bc50a..811958ef 100644 --- a/MathCore.WPF/EventsTrigger.cs +++ b/MathCore.WPF/EventsTrigger.cs @@ -62,7 +62,7 @@ private class EventTriggersPool : TriggersPool; private static readonly TriggersPool __Pool; - private static TriggersPool Pool => __Pool; + //private static TriggersPool Pool => __Pool; public event PropertyChangedEventHandler PropertyChanged; @@ -90,7 +90,7 @@ public bool Check(object? obj) bool current; lock (_SyncRoot) { - var last = _LastState; + var last = _LastState; current = _LastState = Checker(obj); Checked?.Invoke(this, EventArgs.Empty); if (current == last) return current; diff --git a/MathCore.WPF/FileDialogEx.cs b/MathCore.WPF/FileDialogEx.cs index 449329d7..e3a85c6e 100644 --- a/MathCore.WPF/FileDialogEx.cs +++ b/MathCore.WPF/FileDialogEx.cs @@ -52,6 +52,20 @@ static void AppendFilterText(StringBuilder str, IEnumerable vvv) } public bool Equals(FileFilterItem other) => Title == other.Title && Values.SequenceEqual(other.Values, StringComparer.OrdinalIgnoreCase); + + public override bool Equals(object? obj) => obj is FileFilterItem item && Equals(item); + + public static bool operator ==(FileFilterItem left, FileFilterItem right) => left.Equals(right); + + public static bool operator !=(FileFilterItem left, FileFilterItem right) => !(left == right); + + public override int GetHashCode() + { + var hash = new HashBuilder("FileDialog".GetHashCode()).Append("FileFileter"); + foreach (var item in Values) + hash = hash.Append(item.GetHashCode()); + return hash; + } } public static FileDialogEx OpenFile() => new() { IsSaveFileDialog = false }; @@ -91,8 +105,8 @@ public FileDialogEx AddFilter(string Name, params string[] Ext) => Filter is { } : this with { Filter = [new(Name, Ext)] }; #endif - public FileDialogEx AddFilterAllFiles() => Filter is null || Filter.Last().Title != "Все файлы" - ? AddFilter("Все файлы", "*.*") + public FileDialogEx AddFilterAllFiles() => Filter is null || Filter.Last().Title != "Все файлы" + ? AddFilter("Все файлы", "*.*") : this; public OpenFileDialog CreateOpenFileDialog() diff --git a/MathCore.WPF/FileInfoCollection.cs b/MathCore.WPF/FileInfoCollection.cs index a4a02890..73127d40 100644 --- a/MathCore.WPF/FileInfoCollection.cs +++ b/MathCore.WPF/FileInfoCollection.cs @@ -4,4 +4,4 @@ namespace MathCore.WPF; [Serializable] -public class FileInfoCollection : ObservableCollection; \ No newline at end of file +public class FileInfoCollection : ObservableCollection; diff --git a/MathCore.WPF/GIF.cs b/MathCore.WPF/GIF.cs index 6a943a35..0cc1a1d6 100644 --- a/MathCore.WPF/GIF.cs +++ b/MathCore.WPF/GIF.cs @@ -1,7 +1,7 @@ -using System.Windows.Media.Animation; -using System.Windows.Media.Imaging; -using System.Windows; +using System.Windows; using System.Windows.Controls; +using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; namespace MathCore.WPF; @@ -24,7 +24,7 @@ private void Initialize() _GifDecoder = new(new Uri(GifSource), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); _Animation = new( - fromValue: 0, + fromValue: 0, toValue: _GifDecoder.Frames.Count - 1, // ReSharper disable once PossibleLossOfFraction duration: new(new(0, 0, 0, _GifDecoder.Frames.Count / 10, (int)(1000 * (_GifDecoder.Frames.Count / 10d - _GifDecoder.Frames.Count / 10))))) @@ -53,7 +53,7 @@ private static void VisibilityPropertyChanged(DependencyObject sender, Dependenc public static readonly DependencyProperty FrameIndexProperty = DependencyProperty.Register( nameof(FrameIndex), - typeof(int), + typeof(int), typeof(GIF), new UIPropertyMetadata(0, ChangingFrameIndex)); @@ -76,7 +76,7 @@ public bool AutoStart DependencyProperty.Register( nameof(AutoStart), typeof(bool), - typeof(GIF), + typeof(GIF), new UIPropertyMetadata(false, AutoStartPropertyChanged)); private static void AutoStartPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) @@ -93,9 +93,9 @@ public string GifSource public static readonly DependencyProperty GifSourceProperty = DependencyProperty.Register( - nameof(GifSource), - typeof(string), - typeof(GIF), + nameof(GifSource), + typeof(string), + typeof(GIF), new UIPropertyMetadata(string.Empty, GifSourcePropertyChanged)); private static void GifSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) => (sender as GIF).Initialize(); diff --git a/MathCore.WPF/InputCultureManager.cs b/MathCore.WPF/InputCultureManager.cs index 6c4b0677..60bbb66b 100644 --- a/MathCore.WPF/InputCultureManager.cs +++ b/MathCore.WPF/InputCultureManager.cs @@ -16,14 +16,14 @@ public class InputCultureManager public CultureInfo Culture { get => _Culture; - set => InputLanguageManager.Current.CurrentInputLanguage = value; + set => InputLanguageManager.Current.CurrentInputLanguage = _Culture = value; } /// Событие, возникающее при изменении текущей культуры ввода. public event EventHandler? CultureChanged; /// Конструктор менеджера культуры ввода. - private InputCultureManager() + private InputCultureManager() => InputLanguageManager.Current.InputLanguageChanged += OnInputLanguageChanged; /// Обработчик события изменения текущей культуры ввода. diff --git a/MathCore.WPF/LongPress.cs b/MathCore.WPF/LongPress.cs index 8771abbd..1018cb5d 100644 --- a/MathCore.WPF/LongPress.cs +++ b/MathCore.WPF/LongPress.cs @@ -69,7 +69,7 @@ private static void UnregisterHandlers(Control control) control.MouseLeftButtonDown -= OnMouseDown; control.MouseLeftButtonUp -= OnMouseUp; control.MouseLeave -= OnMouseLeave; - + // Отменяем текущую задачу если она есть if (control.GetValue(__CancellationTokenSource) is CancellationTokenSource cts) { @@ -104,10 +104,10 @@ private static async void OnMouseDown(object sender, MouseButtonEventArgs e) var down_time = DateTime.Now; control.SetValue(__LastClickTime, down_time); - + var cts = new CancellationTokenSource(); control.SetValue(__CancellationTokenSource, cts); - + var timeout = Math.Max(100, GetTimeout(control)); try @@ -139,11 +139,11 @@ private static async void OnMouseDown(object sender, MouseButtonEventArgs e) } } - private static void OnMouseUp(object sender, MouseButtonEventArgs e) + private static void OnMouseUp(object sender, MouseButtonEventArgs e) { var control = (DependencyObject)sender; control.ClearValue(__LastClickTime); - + // Отменяем текущую задачу if (control.GetValue(__CancellationTokenSource) is CancellationTokenSource cts) { @@ -156,7 +156,7 @@ private static void OnMouseLeave(object sender, MouseEventArgs e) { var control = (DependencyObject)sender; control.ClearValue(__LastClickTime); - + // Отменяем текущую задачу при уходе мыши с элемента if (control.GetValue(__CancellationTokenSource) is CancellationTokenSource cts) { @@ -188,7 +188,6 @@ private static void OnMouseLeave(object sender, MouseEventArgs e) #endregion - #region Attached property LongPress.Timeout : int - Задержка (не меньше 100) мс /// Задержка (не меньше 100) мс @@ -208,7 +207,6 @@ private static void OnMouseLeave(object sender, MouseEventArgs e) #endregion - #region Attached property LongPress.AnimationTimeout : int - Шаг анимации /// Шаг анимации diff --git a/MathCore.WPF/MouseWheelGesture.cs b/MathCore.WPF/MouseWheelGesture.cs index 48c66c07..889fb304 100644 --- a/MathCore.WPF/MouseWheelGesture.cs +++ b/MathCore.WPF/MouseWheelGesture.cs @@ -12,6 +12,6 @@ public class MouseWheelGesture : InputGesture public override bool Matches(object element, InputEventArgs e) { // Проверяем, является ли событие ввода событием колесом мыши и не равен ли delta нулю. - return e is MouseWheelEventArgs wheel && wheel.Delta != 0; + return e is MouseWheelEventArgs { Delta: not 0 }; } } \ No newline at end of file diff --git a/MathCore.WPF/RadialProgressIndicator.cs b/MathCore.WPF/RadialProgressIndicator.cs index e476f6c4..97382a98 100644 --- a/MathCore.WPF/RadialProgressIndicator.cs +++ b/MathCore.WPF/RadialProgressIndicator.cs @@ -47,11 +47,11 @@ static RadialProgressIndicator() => /// Initializes a new instance of public RadialProgressIndicator() { - _IsListening = false; - _Radius = 0; - _Center = new(); - _RotationAngle = 0; - Unloaded += OnUnloaded; + _IsListening = false; + _Radius = 0; + _Center = new(); + _RotationAngle = 0; + Unloaded += OnUnloaded; } #endregion @@ -191,7 +191,7 @@ private void OnRendering(object sender, EventArgs e) var tick = Environment.TickCount; _TickCount = tick - _LastTick; - _LastTick = tick; + _LastTick = tick; CreateProgressPath(_TickCount); } @@ -205,7 +205,7 @@ private void CreateProgressPath(int variation) { var path_geometry = (PathGeometry)_CurrentGeometry.Clone(); path_geometry.Transform = new RotateTransform(_RotationAngle, _Center.X, _Center.Y); - _ProgressGeometry = path_geometry.GetFlattenedPathGeometry(); + _ProgressGeometry = path_geometry.GetFlattenedPathGeometry(); } if (_BorderGeometry != null) @@ -239,7 +239,7 @@ private void StartListening() if (_IsListening) return; - _IsListening = true; + _IsListening = true; CompositionTarget.Rendering += OnRendering; } @@ -250,7 +250,7 @@ private void StopListening() if (!_IsListening) return; - _IsListening = false; + _IsListening = false; CompositionTarget.Rendering -= OnRendering; } @@ -277,8 +277,8 @@ private void OnIsEnabledChanged(bool OldValue, bool NewValue) { if (_IsListening) StopListening(); - _RotationAngle = 0; - _CurrentGeometry = null; + _RotationAngle = 0; + _CurrentGeometry = null; _ProgressGeometry = null; } @@ -307,7 +307,7 @@ private void OnActiveForegroundChanged(Brush NewValue) #endregion Property Changes } -internal static class GeometryExtensions +file static class GeometryExtensions { #region Static @@ -327,7 +327,7 @@ public static double EaseAngle(this double angle) var sign = Sign(angle); var normalized_angle = Abs(angle).Normalize(); - var percentage = normalized_angle / 360; + var percentage = normalized_angle / 360; normalized_angle = percentage.EaseInOut(normalized_angle, 5, 2); normalized_angle = Max(normalized_angle, 1); @@ -387,7 +387,7 @@ public static PathGeometry CreatePath(this Point location, double angle, double var is_large_arc = angle > __FullCircleInDegrees / 2; - var arc_point = ConvertRadianToCartesian(angle, radius); + var arc_point = ConvertRadianToCartesian(angle, radius); var inner_arc_point = ConvertRadianToCartesian(angle, InnerRadius); var segments = new PathSegmentCollection @@ -469,8 +469,8 @@ public static Point ConvertRadianToCartesian(this double angle, double radius) throw new ArgumentOutOfRangeException($"{nameof(radius)} '{radius}' must be greater than zero."); var angle_radius = PI / (__FullCircleInDegrees / 2) * (angle - __FullCircleInDegrees / 4); - var x = radius * Cos(angle_radius); - var y = radius * Sin(angle_radius); + var x = radius * Cos(angle_radius); + var y = radius * Sin(angle_radius); return new(x, y); } @@ -551,7 +551,7 @@ public static double EaseInOut(this double TimeFraction, double start, double de #endregion } -internal static class DoubleUtil +file static class DoubleUtil { #region Types @@ -597,7 +597,7 @@ public static bool AreClose(double value1, double value2) // ReSharper restore CompareOfFloatsByEqualityOperator // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DoubleEpsilon - var eps = (Abs(value1) + Abs(value2) + 10.0) * __DoubleEpsilon; + var eps = (Abs(value1) + Abs(value2) + 10.0) * __DoubleEpsilon; var delta = value1 - value2; return (-eps < delta) && (eps > delta); } From a1e0319f9378e4f4d3a17e9d38899aff88a07a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB?= Date: Tue, 18 Nov 2025 12:18:31 +0300 Subject: [PATCH 16/16] v0.0.48.3 --- MathCore.WPF/MathCore.WPF.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MathCore.WPF/MathCore.WPF.csproj b/MathCore.WPF/MathCore.WPF.csproj index 0275b4d1..1362ebdc 100644 --- a/MathCore.WPF/MathCore.WPF.csproj +++ b/MathCore.WPF/MathCore.WPF.csproj @@ -29,9 +29,9 @@ - 0.0.48.2 + 0.0.48.3 - Добавлены стили для кнопок и полей ввода текста в стилистике Bootstrap 5 + Добавлена поддержка .NET10.0