diff --git a/Elfisa.sln b/Elfisa.sln index 0d7cfa2..fc6d9da 100644 --- a/Elfisa.sln +++ b/Elfisa.sln @@ -1,23 +1,95 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elfisa.UI", "src\Elfisa.UI\Elfisa.UI.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronicPharmacy", "src\ElectronicPharmacy\ElectronicPharmacy.csproj", "{145D8060-4187-4871-B4FB-8DF795727B66}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elfisa.MaterialDemo", "src\Elfisa.MaterialDemo\Elfisa.MaterialDemo.csproj", "{7974F646-2F23-46E3-A614-A3D0C5D8C844}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elfisa.ButtonSkillDemo", "src\Elfisa.ButtonSkillDemo\Elfisa.ButtonSkillDemo.csproj", "{1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elfisa.GradientButtonDemo", "src\Elfisa.GradientButtonDemo\Elfisa.GradientButtonDemo.csproj", "{19457083-89B8-4C5B-BCA1-BFB66C273502}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU {145D8060-4187-4871-B4FB-8DF795727B66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {145D8060-4187-4871-B4FB-8DF795727B66}.Debug|Any CPU.Build.0 = Debug|Any CPU + {145D8060-4187-4871-B4FB-8DF795727B66}.Debug|x64.ActiveCfg = Debug|Any CPU + {145D8060-4187-4871-B4FB-8DF795727B66}.Debug|x64.Build.0 = Debug|Any CPU + {145D8060-4187-4871-B4FB-8DF795727B66}.Debug|x86.ActiveCfg = Debug|Any CPU + {145D8060-4187-4871-B4FB-8DF795727B66}.Debug|x86.Build.0 = Debug|Any CPU {145D8060-4187-4871-B4FB-8DF795727B66}.Release|Any CPU.ActiveCfg = Release|Any CPU {145D8060-4187-4871-B4FB-8DF795727B66}.Release|Any CPU.Build.0 = Release|Any CPU + {145D8060-4187-4871-B4FB-8DF795727B66}.Release|x64.ActiveCfg = Release|Any CPU + {145D8060-4187-4871-B4FB-8DF795727B66}.Release|x64.Build.0 = Release|Any CPU + {145D8060-4187-4871-B4FB-8DF795727B66}.Release|x86.ActiveCfg = Release|Any CPU + {145D8060-4187-4871-B4FB-8DF795727B66}.Release|x86.Build.0 = Release|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Debug|x64.ActiveCfg = Debug|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Debug|x64.Build.0 = Debug|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Debug|x86.ActiveCfg = Debug|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Debug|x86.Build.0 = Debug|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Release|Any CPU.Build.0 = Release|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Release|x64.ActiveCfg = Release|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Release|x64.Build.0 = Release|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Release|x86.ActiveCfg = Release|Any CPU + {7974F646-2F23-46E3-A614-A3D0C5D8C844}.Release|x86.Build.0 = Release|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Debug|x64.ActiveCfg = Debug|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Debug|x64.Build.0 = Debug|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Debug|x86.ActiveCfg = Debug|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Debug|x86.Build.0 = Debug|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Release|Any CPU.Build.0 = Release|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Release|x64.ActiveCfg = Release|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Release|x64.Build.0 = Release|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Release|x86.ActiveCfg = Release|Any CPU + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85}.Release|x86.Build.0 = Release|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Debug|x64.ActiveCfg = Debug|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Debug|x64.Build.0 = Debug|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Debug|x86.ActiveCfg = Debug|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Debug|x86.Build.0 = Debug|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Release|Any CPU.Build.0 = Release|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Release|x64.ActiveCfg = Release|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Release|x64.Build.0 = Release|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Release|x86.ActiveCfg = Release|Any CPU + {19457083-89B8-4C5B-BCA1-BFB66C273502}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {7974F646-2F23-46E3-A614-A3D0C5D8C844} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {1A28299F-5E9B-4E2E-AEC1-EB35FB911F85} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {19457083-89B8-4C5B-BCA1-BFB66C273502} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} EndGlobalSection EndGlobal diff --git a/src/ElectronicPharmacy/Program.cs b/src/ElectronicPharmacy/Program.cs index b7d1b4c..a8be127 100644 --- a/src/ElectronicPharmacy/Program.cs +++ b/src/ElectronicPharmacy/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Windows.Forms; using Elfisa.UI.Theming; @@ -11,7 +11,7 @@ namespace Электронная_Фармация { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - ThemeManager.CurrentVariant = ThemeVariant.FluentWin11; + ThemeManager.CurrentVariant = ThemeVariant.PharmaTrust; Application.Run(new ElectroPharmacy()); } } diff --git a/src/ElectronicPharmacy/UserControls/FOrders.cs b/src/ElectronicPharmacy/UserControls/FOrders.cs index 73cc359..f70554b 100644 --- a/src/ElectronicPharmacy/UserControls/FOrders.cs +++ b/src/ElectronicPharmacy/UserControls/FOrders.cs @@ -91,7 +91,7 @@ namespace Электронная_Фармация.UserControls const int row1Y = 10; const int controlHeight = 32; const int filterHeight = 33; - const int buttonHeight = 34; + const int buttonHeight = 37; const int rowGap = 14; const int row2Y = row1Y + buttonHeight + rowGap; const int commentWidth = 200; @@ -109,10 +109,10 @@ namespace Электронная_Фармация.UserControls int right = panelToolbar.ClientSize.Width - pad; - btnDeleteOrder.SetBounds(right - 130, row1Y, 130, buttonHeight); + btnDeleteOrder.SetBounds(right - 148, row1Y, 148, buttonHeight); right = btnDeleteOrder.Left - gap; - _btnSendOrder.SetBounds(right - 150, row1Y, 150, buttonHeight); + _btnSendOrder.SetBounds(right - 168, row1Y, 168, buttonHeight); right = _btnSendOrder.Left - gap; _comboConsignees.SetBounds(right - 190, filterY, 190, filterHeight); @@ -126,14 +126,15 @@ namespace Электронная_Фармация.UserControls x = pad; txtGoodFromOrders.SetBounds(x, row2Y, 220, controlHeight); x = txtGoodFromOrders.Right + gap; - btnClearGoodSearch.SetBounds(x, row2Y, 34, controlHeight); + btnClearGoodSearch.SetBounds(x, row2Y, 32, controlHeight + 3); x = btnClearGoodSearch.Right + gap; txtOrderNumberFilter.SetBounds(x, row2Y, 220, controlHeight); x = txtOrderNumberFilter.Right + gap; - btnClearOrderNumberFilter.SetBounds(x, row2Y, 34, controlHeight); + btnClearOrderNumberFilter.SetBounds(x, row2Y, 32, controlHeight + 3); x = btnClearOrderNumberFilter.Right + gap; btnAddComment.SetBounds(x, row2Y, commentWidth, buttonHeight); + ModernButtonStyles.FitToText(btnAddComment, buttonHeight); btnClearSuppliers.Visible = false; diff --git a/src/ElectronicPharmacy/UserControls/UCPriceList.cs b/src/ElectronicPharmacy/UserControls/UCPriceList.cs index fe2f19b..c667c75 100644 --- a/src/ElectronicPharmacy/UserControls/UCPriceList.cs +++ b/src/ElectronicPharmacy/UserControls/UCPriceList.cs @@ -52,9 +52,9 @@ namespace Электронная_Фармация.UserControls Tag = "chrome-child"; btnDeleteGood.Height = 34; - btnClearSearchInPriceList.Height = 32; + btnClearSearchInPriceList.Height = 35; txtSearchGoodNameInPriceList.Height = 32; - btnClearSupplierFilter.Height = 32; + btnClearSupplierFilter.Height = 35; panel1.SizeChanged += (_, __) => LayoutMarkupRow(); LayoutMarkupRow(); diff --git a/src/Elfisa.ButtonSkillDemo/ButtonSkillDemoForm.cs b/src/Elfisa.ButtonSkillDemo/ButtonSkillDemoForm.cs new file mode 100644 index 0000000..86c5ca2 --- /dev/null +++ b/src/Elfisa.ButtonSkillDemo/ButtonSkillDemoForm.cs @@ -0,0 +1,223 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace Elfisa.ButtonSkillDemo; + +internal sealed class ButtonSkillDemoForm : Form +{ + public ButtonSkillDemoForm() + { + Text = "Skill ModernButton — демо стилей"; + Size = new Size(920, 620); + MinimumSize = new Size(820, 520); + StartPosition = FormStartPosition.CenterScreen; + BackColor = Color.FromArgb(18, 18, 22); + Font = new Font("Segoe UI", 10f); + + var header = new Label + { + Text = "Custom Modern Button (skill) — варианты стилей", + ForeColor = Color.White, + AutoSize = false, + Height = 48, + Dock = DockStyle.Top, + Padding = new Padding(24, 14, 24, 0), + Font = new Font("Segoe UI Semibold", 14f, FontStyle.Bold) + }; + + var scroll = new Panel + { + Dock = DockStyle.Fill, + AutoScroll = true, + Padding = new Padding(24) + }; + + var layout = new TableLayoutPanel + { + Dock = DockStyle.Top, + AutoSize = true, + ColumnCount = 2, + Padding = new Padding(0, 8, 0, 24) + }; + layout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 220)); + layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); + + AddSection(layout, "Default (тёмная)", CreateDefaultButton()); + AddSection(layout, "Светлая тема", CreateLightButton()); + AddSection(layout, "Неоновый стиль", CreateNeonButton()); + AddSection(layout, "Glass (полупрозрачный)", CreateGlassButton()); + AddSection(layout, "Pharma Trust — синяя", CreatePharmaPrimaryButton()); + AddSection(layout, "Pharma Trust — красный ×", CreatePharmaClearButton()); + AddSection(layout, "Большая CTA", CreateLargeCtaButton()); + + var note = new Label + { + AutoSize = true, + MaximumSize = new Size(820, 0), + ForeColor = Color.FromArgb(180, 180, 190), + Text = + "Это отдельный класс SkillModernButton для демо. Основное приложение использует Elfisa.UI.Controls.ModernButton + ThemeManager.\n" + + "Наведите курсор и нажмите кнопки — видны hover и pressed.", + Margin = new Padding(0, 8, 0, 0) + }; + + layout.Controls.Add(note, 0, layout.RowCount); + layout.SetColumnSpan(note, 2); + + scroll.Controls.Add(layout); + Controls.Add(scroll); + Controls.Add(header); + } + + private static void AddSection(TableLayoutPanel layout, string title, Control content) + { + var row = layout.RowCount; + layout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + + var label = new Label + { + Text = title, + ForeColor = Color.FromArgb(200, 200, 210), + AutoSize = true, + Margin = new Padding(0, 16, 12, 8), + Font = new Font("Segoe UI Semibold", 10.5f, FontStyle.Bold) + }; + + content.Margin = new Padding(0, 12, 0, 8); + layout.Controls.Add(label, 0, row); + layout.Controls.Add(content, 1, row); + layout.RowCount++; + } + + private static FlowLayoutPanel Row(params Control[] buttons) + { + var panel = new FlowLayoutPanel + { + AutoSize = true, + WrapContents = true, + FlowDirection = FlowDirection.LeftToRight + }; + foreach (var btn in buttons) + { + panel.Controls.Add(btn); + } + return panel; + } + + private static SkillModernButton CreateDefaultButton() + { + return new SkillModernButton + { + Text = "Красивая кнопка", + Size = new Size(200, 48), + Margin = new Padding(0, 0, 12, 0) + }; + } + + private static SkillModernButton CreateLightButton() + { + return new SkillModernButton + { + Text = "Светлая кнопка", + Size = new Size(200, 48), + BackColor = Color.White, + ForeColor = Color.FromArgb(30, 30, 30), + BorderColor = Color.FromArgb(0, 122, 204), + HoverColor = Color.FromArgb(245, 249, 255), + PressedColor = Color.FromArgb(230, 240, 250), + BorderRadius = 12 + }; + } + + private static SkillModernButton CreateNeonButton() + { + return new SkillModernButton + { + Text = "Неон", + Size = new Size(180, 48), + BackColor = Color.FromArgb(20, 24, 28), + ForeColor = Color.FromArgb(220, 255, 250), + BorderRadius = 8, + BorderColor = Color.FromArgb(0, 255, 200), + HoverColor = Color.FromArgb(50, 255, 220), + PressedColor = Color.FromArgb(0, 180, 140), + BorderSize = 2 + }; + } + + private static SkillModernButton CreateGlassButton() + { + return new SkillModernButton + { + Text = "Glass", + Size = new Size(180, 48), + BackColor = Color.FromArgb(80, 80, 80), + ForeColor = Color.White, + BorderColor = Color.FromArgb(160, 160, 170), + HoverColor = Color.FromArgb(110, 110, 120), + PressedColor = Color.FromArgb(60, 60, 68), + BorderRadius = 16, + BorderSize = 1 + }; + } + + private static SkillModernButton CreatePharmaPrimaryButton() + { + return new SkillModernButton + { + Text = "Сохранить заказ", + Size = new Size(200, 40), + BackColor = Color.FromArgb(3, 105, 161), + ForeColor = Color.White, + BorderColor = Color.FromArgb(3, 105, 161), + HoverColor = Color.FromArgb(7, 89, 133), + PressedColor = Color.FromArgb(12, 74, 110), + BorderRadius = 6, + BorderSize = 0, + Font = new Font("Segoe UI Semibold", 10f, FontStyle.Bold) + }; + } + + private static SkillModernButton CreatePharmaClearButton() + { + return new SkillModernButton + { + Text = "×", + Size = new Size(32, 32), + BackColor = Color.FromArgb(220, 38, 38), + ForeColor = Color.White, + BorderColor = Color.FromArgb(220, 38, 38), + HoverColor = Color.FromArgb(239, 68, 68), + PressedColor = Color.FromArgb(185, 28, 28), + BorderRadius = 6, + BorderSize = 0, + Font = new Font("Segoe UI Semibold", 13f, FontStyle.Bold) + }; + } + + private static Control CreateLargeCtaButton() + { + return Row( + new SkillModernButton + { + Text = "Загрузить на сервер", + Size = new Size(220, 52), + BorderRadius = 20, + BorderColor = Color.FromArgb(64, 158, 255), + BackColor = Color.FromArgb(30, 30, 30), + HoverColor = Color.FromArgb(0, 140, 230), + PressedColor = Color.FromArgb(0, 100, 180) + }, + new SkillModernButton + { + Text = "Отмена", + Size = new Size(140, 52), + BackColor = Color.White, + ForeColor = Color.FromArgb(30, 30, 30), + BorderColor = Color.FromArgb(200, 200, 210), + HoverColor = Color.FromArgb(245, 245, 248), + PressedColor = Color.FromArgb(230, 230, 235), + BorderRadius = 20 + }); + } +} diff --git a/src/Elfisa.ButtonSkillDemo/Elfisa.ButtonSkillDemo.csproj b/src/Elfisa.ButtonSkillDemo/Elfisa.ButtonSkillDemo.csproj new file mode 100644 index 0000000..663fdb8 --- /dev/null +++ b/src/Elfisa.ButtonSkillDemo/Elfisa.ButtonSkillDemo.csproj @@ -0,0 +1,11 @@ + + + + WinExe + net8.0-windows + enable + true + enable + + + \ No newline at end of file diff --git a/src/Elfisa.ButtonSkillDemo/Program.cs b/src/Elfisa.ButtonSkillDemo/Program.cs new file mode 100644 index 0000000..9580233 --- /dev/null +++ b/src/Elfisa.ButtonSkillDemo/Program.cs @@ -0,0 +1,11 @@ +namespace Elfisa.ButtonSkillDemo; + +internal static class Program +{ + [STAThread] + private static void Main() + { + ApplicationConfiguration.Initialize(); + Application.Run(new ButtonSkillDemoForm()); + } +} diff --git a/src/Elfisa.ButtonSkillDemo/README.md b/src/Elfisa.ButtonSkillDemo/README.md new file mode 100644 index 0000000..9ed95d2 --- /dev/null +++ b/src/Elfisa.ButtonSkillDemo/README.md @@ -0,0 +1,24 @@ +# Button Skill Demo + +Демо кнопок из skill **Custom Modern Button** (`SkillModernButton`). + +Не путать с `Elfisa.UI.Controls.ModernButton` — это другой класс для основного приложения. + +## Запуск + +```powershell +dotnet run --project src/Elfisa.ButtonSkillDemo/Elfisa.ButtonSkillDemo.csproj -c Release +``` + +Exe: `src/Elfisa.ButtonSkillDemo/bin/Release/net8.0-windows/Elfisa.ButtonSkillDemo.exe` + +## Стили в демо + +| Секция | Настройки | +|--------|-----------| +| Default | тёмный фон, синяя рамка, radius 12 | +| Светлая | белый фон, тёмный текст | +| Неон | cyan border + hover | +| Glass | серый полупрозрачный вид | +| Pharma Trust | синяя / красная × как в ЭльФиСА | +| CTA | большие кнопки radius 20 | diff --git a/src/Elfisa.ButtonSkillDemo/SkillModernButton.cs b/src/Elfisa.ButtonSkillDemo/SkillModernButton.cs new file mode 100644 index 0000000..c6c79b7 --- /dev/null +++ b/src/Elfisa.ButtonSkillDemo/SkillModernButton.cs @@ -0,0 +1,132 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace Elfisa.ButtonSkillDemo; + +/// +/// Кнопка из skill «Custom Modern Button» (отдельно от Elfisa.UI.ModernButton). +/// +public sealed class SkillModernButton : Button +{ + public Color BorderColor { get; set; } = Color.FromArgb(0, 122, 204); + public Color HoverColor { get; set; } = Color.FromArgb(0, 140, 230); + public Color PressedColor { get; set; } = Color.FromArgb(0, 100, 180); + public int BorderRadius { get; set; } = 12; + public int BorderSize { get; set; } = 2; + + private bool _isHover; + private bool _isPressed; + + public SkillModernButton() + { + FlatStyle = FlatStyle.Flat; + FlatAppearance.BorderSize = 0; + BackColor = Color.FromArgb(30, 30, 30); + ForeColor = Color.White; + Font = new Font("Segoe UI", 10.5f, FontStyle.Bold); + Cursor = Cursors.Hand; + Size = new Size(160, 45); + + SetStyle(ControlStyles.AllPaintingInWmPaint | + ControlStyles.UserPaint | + ControlStyles.OptimizedDoubleBuffer | + ControlStyles.ResizeRedraw, true); + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + } + + protected override void OnPaint(PaintEventArgs e) + { + var g = e.Graphics; + g.SmoothingMode = SmoothingMode.AntiAlias; + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + + var rect = new Rectangle(0, 0, Width - 1, Height - 1); + + var currentColor = BackColor; + if (_isPressed) + { + currentColor = PressedColor; + } + else if (_isHover) + { + currentColor = HoverColor; + } + + using (var path = GetRoundedPath(rect, BorderRadius)) + { + using (var brush = new SolidBrush(currentColor)) + { + g.FillPath(brush, path); + } + + if (BorderSize > 0) + { + using (var pen = new Pen(BorderColor, BorderSize)) + { + g.DrawPath(pen, path); + } + } + } + + var flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine; + TextRenderer.DrawText(g, Text, Font, rect, ForeColor, flags); + } + + private static GraphicsPath GetRoundedPath(Rectangle rect, int radius) + { + var path = new GraphicsPath(); + var r = Math.Max(0, Math.Min(radius, Math.Min(rect.Width, rect.Height) / 2)); + if (r == 0) + { + path.AddRectangle(rect); + return path; + } + + var diameter = r * 2; + path.StartFigure(); + path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90); + path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90); + path.AddArc(rect.Right - diameter, rect.Bottom - diameter, diameter, diameter, 0, 90); + path.AddArc(rect.X, rect.Bottom - diameter, diameter, diameter, 90, 90); + path.CloseFigure(); + return path; + } + + protected override void OnMouseEnter(EventArgs e) + { + _isHover = true; + Invalidate(); + base.OnMouseEnter(e); + } + + protected override void OnMouseLeave(EventArgs e) + { + _isHover = false; + _isPressed = false; + Invalidate(); + base.OnMouseLeave(e); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + _isPressed = true; + Invalidate(); + } + base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + _isPressed = false; + Invalidate(); + base.OnMouseUp(e); + } +} diff --git a/src/Elfisa.GradientButtonDemo/Elfisa.GradientButtonDemo.csproj b/src/Elfisa.GradientButtonDemo/Elfisa.GradientButtonDemo.csproj new file mode 100644 index 0000000..508f715 --- /dev/null +++ b/src/Elfisa.GradientButtonDemo/Elfisa.GradientButtonDemo.csproj @@ -0,0 +1,10 @@ + + + WinExe + net472 + true + latest + Elfisa.GradientButtonDemo + Elfisa.GradientButtonDemo + + diff --git a/src/Elfisa.GradientButtonDemo/GradientButtonDemoForm.cs b/src/Elfisa.GradientButtonDemo/GradientButtonDemoForm.cs new file mode 100644 index 0000000..8c701eb --- /dev/null +++ b/src/Elfisa.GradientButtonDemo/GradientButtonDemoForm.cs @@ -0,0 +1,205 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using ModernControls; + +namespace Elfisa.GradientButtonDemo +{ + internal sealed class GradientButtonDemoForm : Form + { + public GradientButtonDemoForm() + { + Text = "winforms-modern-buttons skill — демо"; + Size = new Size(980, 700); + MinimumSize = new Size(860, 600); + StartPosition = FormStartPosition.CenterScreen; + BackColor = Color.FromArgb(245, 247, 250); + Font = new Font("Segoe UI", 10f); + + var header = new Label + { + Text = "ModernGradientButton — градиент + тень + скругление (.NET Framework 4.7.2)", + Dock = DockStyle.Top, + Height = 52, + Padding = new Padding(24, 16, 24, 0), + Font = new Font("Segoe UI Semibold", 13f, FontStyle.Bold), + ForeColor = Color.FromArgb(30, 41, 59) + }; + + var scroll = new Panel + { + Dock = DockStyle.Fill, + AutoScroll = true, + Padding = new Padding(24, 8, 24, 24) + }; + + var layout = new TableLayoutPanel + { + Dock = DockStyle.Top, + AutoSize = true, + ColumnCount = 2, + Width = 900 + }; + layout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 240)); + layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); + + AddRow(layout, "Primary (skill)", PresetButton(ModernGradientButton.Preset.Primary, "Сохранить")); + AddRow(layout, "Success", PresetButton(ModernGradientButton.Preset.Success, "Подтвердить")); + AddRow(layout, "Danger", PresetButton(ModernGradientButton.Preset.Danger, "Удалить")); + AddRow(layout, "Neutral", PresetButton(ModernGradientButton.Preset.Neutral, "Отмена")); + + AddRow(layout, "Кастом (оранжевый)", CustomOrangeButton()); + AddRow(layout, "Pharma Trust — синяя", PharmaPrimaryButton()); + AddRow(layout, "Крестик × у поиска", ClearSearchButton()); + AddRow(layout, "Toolbar (мелкая)", ToolbarRow()); + + var note = new Label + { + AutoSize = true, + MaximumSize = new Size(860, 0), + Margin = new Padding(0, 20, 0, 0), + ForeColor = Color.FromArgb(100, 116, 139), + Text = + "Skill: winforms-modern-buttons · Класс ModernControls.ModernGradientButton\n" + + "Наведите и нажмите — видны hover/pressed. Тень: ShadowDepth, градиент: GradientTop/Bottom.\n" + + "Основное приложение ЭльФиСА пока на Elfisa.UI.ModernButton — это отдельное демо для сравнения." + }; + layout.Controls.Add(note, 0, layout.RowCount); + layout.SetColumnSpan(note, 2); + + scroll.Controls.Add(layout); + Controls.Add(scroll); + Controls.Add(header); + } + + private static void AddRow(TableLayoutPanel layout, string title, Control content) + { + var row = layout.RowCount; + layout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + + layout.Controls.Add(new Label + { + Text = title, + AutoSize = true, + Margin = new Padding(0, 18, 12, 8), + Font = new Font("Segoe UI Semibold", 10f, FontStyle.Bold), + ForeColor = Color.FromArgb(51, 65, 85) + }, 0, row); + + content.Margin = new Padding(0, 14, 0, 4); + layout.Controls.Add(content, 1, row); + layout.RowCount++; + } + + private static ModernGradientButton PresetButton(ModernGradientButton.Preset preset, string text) + { + var btn = new ModernGradientButton + { + Text = text, + Size = new Size(160, 48), + Margin = new Padding(0, 0, 12, 8) + }; + btn.ApplyPreset(preset); + btn.Click += (_, __) => MessageBox.Show(text + " — клик!", "Демо", MessageBoxButtons.OK, MessageBoxIcon.Information); + return btn; + } + + private static ModernGradientButton CustomOrangeButton() + { + return new ModernGradientButton + { + Text = "Свой стиль", + Size = new Size(180, 50), + CornerRadius = 20, + ShadowDepth = 6, + GradientTop = Color.FromArgb(255, 179, 71), + GradientBottom = Color.FromArgb(230, 126, 34), + HoverGradientTop = Color.FromArgb(255, 195, 110), + HoverGradientBottom = Color.FromArgb(245, 145, 50), + PressedGradientTop = Color.FromArgb(220, 120, 30), + PressedGradientBottom = Color.FromArgb(190, 100, 20) + }; + } + + private static ModernGradientButton PharmaPrimaryButton() + { + var btn = new ModernGradientButton + { + Text = "Сохранить заказ", + Size = new Size(190, 44), + CornerRadius = 6, + ShadowDepth = 4, + GradientTop = Color.FromArgb(14, 116, 181), + GradientBottom = Color.FromArgb(3, 105, 161), + HoverGradientTop = Color.FromArgb(37, 130, 195), + HoverGradientBottom = Color.FromArgb(7, 89, 133), + PressedGradientTop = Color.FromArgb(12, 74, 110), + PressedGradientBottom = Color.FromArgb(8, 60, 90) + }; + return btn; + } + + private static Control ClearSearchRow() + { + var panel = new FlowLayoutPanel { AutoSize = true, WrapContents = false }; + + var search = new TextBox + { + Text = "прокладки", + Width = 280, + Height = 32, + Font = new Font("Segoe UI", 10f), + Margin = new Padding(0, 8, 8, 0) + }; + + var clear = new ModernGradientButton + { + Text = "×", + Size = new Size(36, 36), + CornerRadius = 6, + ShadowDepth = 3, + Font = new Font("Segoe UI Semibold", 13f, FontStyle.Bold), + Margin = new Padding(0, 6, 0, 0) + }; + clear.ApplyPreset(ModernGradientButton.Preset.Danger); + clear.Click += (_, __) => search.Clear(); + + panel.Controls.Add(search); + panel.Controls.Add(clear); + return panel; + } + + private static Control ClearSearchButton() + { + return ClearSearchRow(); + } + + private static Control ToolbarRow() + { + var panel = new FlowLayoutPanel { AutoSize = true }; + + var small = new ModernGradientButton + { + Text = "×", + Size = new Size(32, 32), + CornerRadius = 5, + ShadowDepth = 2, + Font = new Font("Segoe UI", 11f, FontStyle.Bold) + }; + small.ApplyPreset(ModernGradientButton.Preset.Danger); + + var neutral = new ModernGradientButton + { + Text = "Фильтр", + Size = new Size(100, 34), + CornerRadius = 8, + ShadowDepth = 3 + }; + neutral.ApplyPreset(ModernGradientButton.Preset.Neutral); + + panel.Controls.Add(small); + panel.Controls.Add(neutral); + return panel; + } + } +} diff --git a/src/Elfisa.GradientButtonDemo/ModernGradientButton.cs b/src/Elfisa.GradientButtonDemo/ModernGradientButton.cs new file mode 100644 index 0000000..94424a9 --- /dev/null +++ b/src/Elfisa.GradientButtonDemo/ModernGradientButton.cs @@ -0,0 +1,220 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace ModernControls +{ + /// + /// Из skill winforms-modern-buttons: градиент, тень, скругление, hover/pressed. + /// + public class ModernGradientButton : Button + { + public enum ButtonState { Normal, Hover, Pressed } + + private ButtonState _state = ButtonState.Normal; + + [Category("Appearance")] + public Color GradientTop { get; set; } = Color.FromArgb(66, 133, 244); + + [Category("Appearance")] + public Color GradientBottom { get; set; } = Color.FromArgb(30, 87, 194); + + [Category("Appearance")] + public Color HoverGradientTop { get; set; } = Color.FromArgb(90, 150, 255); + + [Category("Appearance")] + public Color HoverGradientBottom { get; set; } = Color.FromArgb(45, 105, 220); + + [Category("Appearance")] + public Color PressedGradientTop { get; set; } = Color.FromArgb(25, 70, 160); + + [Category("Appearance")] + public Color PressedGradientBottom { get; set; } = Color.FromArgb(15, 55, 130); + + [Category("Appearance")] + public int CornerRadius { get; set; } = 12; + + [Category("Appearance")] + public int ShadowDepth { get; set; } = 5; + + [Category("Appearance")] + public Color ShadowColor { get; set; } = Color.FromArgb(60, 0, 0, 0); + + [Category("Appearance")] + public bool AnimateHover { get; set; } = true; + + public ModernGradientButton() + { + SetStyle(ControlStyles.AllPaintingInWmPaint + | ControlStyles.UserPaint + | ControlStyles.ResizeRedraw + | ControlStyles.SupportsTransparentBackColor + | ControlStyles.OptimizedDoubleBuffer, true); + + FlatStyle = FlatStyle.Flat; + FlatAppearance.BorderSize = 0; + BackColor = Color.Transparent; + ForeColor = Color.White; + Font = new Font("Segoe UI", 9.5f, FontStyle.Regular); + Cursor = Cursors.Hand; + Size = new Size(140, 42); + Padding = new Padding(0, 0, 0, ShadowDepth); + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + } + + protected override void OnMouseEnter(EventArgs e) + { + base.OnMouseEnter(e); + _state = ButtonState.Hover; + Invalidate(); + } + + protected override void OnMouseLeave(EventArgs e) + { + base.OnMouseLeave(e); + _state = ButtonState.Normal; + Invalidate(); + } + + protected override void OnMouseDown(MouseEventArgs mevent) + { + base.OnMouseDown(mevent); + _state = ButtonState.Pressed; + Invalidate(); + } + + protected override void OnMouseUp(MouseEventArgs mevent) + { + base.OnMouseUp(mevent); + _state = ClientRectangle.Contains(mevent.Location) ? ButtonState.Hover : ButtonState.Normal; + Invalidate(); + } + + private static GraphicsPath RoundedRect(Rectangle bounds, int radius) + { + var d = radius * 2; + var path = new GraphicsPath(); + + if (radius <= 0) + { + path.AddRectangle(bounds); + return path; + } + + path.StartFigure(); + path.AddArc(bounds.X, bounds.Y, d, d, 180, 90); + path.AddArc(bounds.Right - d, bounds.Y, d, d, 270, 90); + path.AddArc(bounds.Right - d, bounds.Bottom - d, d, d, 0, 90); + path.AddArc(bounds.X, bounds.Bottom - d, d, d, 90, 90); + path.CloseFigure(); + return path; + } + + protected override void OnPaint(PaintEventArgs pevent) + { + var g = pevent.Graphics; + g.SmoothingMode = SmoothingMode.AntiAlias; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + + var buttonRect = new Rectangle(0, 0, Width - 1, Height - 1 - ShadowDepth); + + for (var i = ShadowDepth; i > 0; i--) + { + var alpha = ShadowColor.A * i / (ShadowDepth * 3); + using (var shadowBrush = new SolidBrush(Color.FromArgb(alpha, ShadowColor))) + using (var shadowPath = RoundedRect( + new Rectangle(i, buttonRect.Y + i + 1, buttonRect.Width - i, buttonRect.Height - 1), + CornerRadius)) + { + g.FillPath(shadowBrush, shadowPath); + } + } + + Color top; + Color bottom; + switch (_state) + { + case ButtonState.Hover: + top = HoverGradientTop; + bottom = HoverGradientBottom; + break; + case ButtonState.Pressed: + top = PressedGradientTop; + bottom = PressedGradientBottom; + break; + default: + top = GradientTop; + bottom = GradientBottom; + break; + } + + using (var path = RoundedRect(buttonRect, CornerRadius)) + using (var brush = new LinearGradientBrush(buttonRect, top, bottom, LinearGradientMode.Vertical)) + { + g.FillPath(brush, path); + + using (var highlightPen = new Pen(Color.FromArgb(40, Color.White), 1)) + { + g.DrawArc(highlightPen, buttonRect.X, buttonRect.Y, CornerRadius * 2, CornerRadius * 2, 180, 90); + } + } + + TextRenderer.DrawText( + g, + Text, + Font, + buttonRect, + ForeColor, + TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis); + } + + public void ApplyPreset(Preset preset) + { + switch (preset) + { + case Preset.Primary: + GradientTop = Color.FromArgb(66, 133, 244); + GradientBottom = Color.FromArgb(30, 87, 194); + HoverGradientTop = Color.FromArgb(90, 150, 255); + HoverGradientBottom = Color.FromArgb(45, 105, 220); + PressedGradientTop = Color.FromArgb(25, 70, 160); + PressedGradientBottom = Color.FromArgb(15, 55, 130); + break; + case Preset.Success: + GradientTop = Color.FromArgb(67, 189, 118); + GradientBottom = Color.FromArgb(30, 140, 80); + HoverGradientTop = Color.FromArgb(90, 205, 135); + HoverGradientBottom = Color.FromArgb(40, 160, 95); + PressedGradientTop = Color.FromArgb(20, 120, 65); + PressedGradientBottom = Color.FromArgb(10, 95, 50); + break; + case Preset.Danger: + GradientTop = Color.FromArgb(235, 87, 87); + GradientBottom = Color.FromArgb(200, 40, 40); + HoverGradientTop = Color.FromArgb(245, 110, 110); + HoverGradientBottom = Color.FromArgb(215, 55, 55); + PressedGradientTop = Color.FromArgb(175, 30, 30); + PressedGradientBottom = Color.FromArgb(140, 20, 20); + break; + case Preset.Neutral: + GradientTop = Color.FromArgb(120, 130, 140); + GradientBottom = Color.FromArgb(80, 90, 100); + HoverGradientTop = Color.FromArgb(140, 150, 160); + HoverGradientBottom = Color.FromArgb(95, 105, 115); + PressedGradientTop = Color.FromArgb(65, 75, 85); + PressedGradientBottom = Color.FromArgb(50, 60, 70); + break; + } + + Invalidate(); + } + + public enum Preset { Primary, Success, Danger, Neutral } + } +} diff --git a/src/Elfisa.GradientButtonDemo/Program.cs b/src/Elfisa.GradientButtonDemo/Program.cs new file mode 100644 index 0000000..5186a2d --- /dev/null +++ b/src/Elfisa.GradientButtonDemo/Program.cs @@ -0,0 +1,16 @@ +using System; +using System.Windows.Forms; + +namespace Elfisa.GradientButtonDemo +{ + internal static class Program + { + [STAThread] + private static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new GradientButtonDemoForm()); + } + } +} diff --git a/src/Elfisa.GradientButtonDemo/README.md b/src/Elfisa.GradientButtonDemo/README.md new file mode 100644 index 0000000..82f1013 --- /dev/null +++ b/src/Elfisa.GradientButtonDemo/README.md @@ -0,0 +1,20 @@ +# Gradient Button Demo (skill) + +Демо по skill **winforms-modern-buttons** — класс `ModernControls.ModernGradientButton`. + +- .NET Framework **4.7.2** (как в skill и в ЭльФиСА) +- Градиент, тень, скругление, 4 пресета + +## Запуск + +```powershell +dotnet run --project src/Elfisa.GradientButtonDemo/Elfisa.GradientButtonDemo.csproj -c Release +``` + +Exe: `src/Elfisa.GradientButtonDemo/bin/Release/Elfisa.GradientButtonDemo.exe` + +## Файлы skill + +Исходник skill распакован из `winforms-modern-buttons.skill`: +- `ModernGradientButton.cs` — копия из `assets/` +- Skill также можно установить в `~/.cursor/skills/winforms-modern-buttons/` diff --git a/src/Elfisa.MaterialDemo/Elfisa.MaterialDemo.csproj b/src/Elfisa.MaterialDemo/Elfisa.MaterialDemo.csproj new file mode 100644 index 0000000..91887ed --- /dev/null +++ b/src/Elfisa.MaterialDemo/Elfisa.MaterialDemo.csproj @@ -0,0 +1,15 @@ + + + + WinExe + net8.0-windows + enable + true + enable + + + + + + + \ No newline at end of file diff --git a/src/Elfisa.MaterialDemo/PharmacyMaterialForm.cs b/src/Elfisa.MaterialDemo/PharmacyMaterialForm.cs new file mode 100644 index 0000000..eaa0b01 --- /dev/null +++ b/src/Elfisa.MaterialDemo/PharmacyMaterialForm.cs @@ -0,0 +1,286 @@ +using MaterialSkin; +using MaterialSkin.Controls; + +namespace Elfisa.MaterialDemo; + +/// +/// Demo: как может выглядеть «Электронная Фармация» на MaterialSkin.2 (отдельно от Elfisa.UI). +/// +internal sealed class PharmacyMaterialForm : MaterialForm +{ + private readonly MaterialTabControl _tabs; + private readonly Panel _contentHost; + private readonly MaterialLabel _statusLabel; + + public PharmacyMaterialForm() + { + Text = "ЭльФиСА — MaterialSkin demo"; + MinimumSize = new Size(960, 640); + Size = new Size(1100, 720); + StartPosition = FormStartPosition.CenterScreen; + + MaterialSkinManager.Instance.AddFormToManage(this); + + var header = new MaterialLabel + { + Text = "MaterialSkin.2 — демо для сравнения с Elfisa.UI", + FontType = MaterialSkinManager.fontType.H6, + Dock = DockStyle.Top, + Height = 48, + Padding = new Padding(24, 12, 24, 0) + }; + + _tabs = new MaterialTabControl + { + Dock = DockStyle.Top, + Height = 48, + Depth = 0 + }; + _tabs.TabPages.Add(new TabPage("Прайс-лист") { Tag = "price" }); + _tabs.TabPages.Add(new TabPage("Заказы") { Tag = "orders" }); + _tabs.TabPages.Add(new TabPage("Загрузить") { Tag = "upload" }); + _tabs.SelectedIndexChanged += (_, __) => ShowTab(_tabs.SelectedTab?.Tag as string ?? "price"); + + _contentHost = new Panel + { + Dock = DockStyle.Fill, + Padding = new Padding(24) + }; + + _statusLabel = new MaterialLabel + { + Dock = DockStyle.Bottom, + Height = 36, + Text = "Пользователь: demo@apteka (MaterialSkin dark / teal)", + FontType = MaterialSkinManager.fontType.Caption, + Padding = new Padding(24, 8, 24, 8) + }; + + Controls.Add(_contentHost); + Controls.Add(_statusLabel); + Controls.Add(_tabs); + Controls.Add(header); + + ShowTab("price"); + } + + private void ShowTab(string key) + { + _contentHost.Controls.Clear(); + + switch (key) + { + case "orders": + _contentHost.Controls.Add(BuildOrdersView()); + break; + case "upload": + _contentHost.Controls.Add(BuildUploadView()); + break; + default: + _contentHost.Controls.Add(BuildPriceView()); + break; + } + } + + private Control BuildPriceView() + { + var root = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 1, + RowCount = 3 + }; + root.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + root.RowStyles.Add(new RowStyle(SizeType.Percent, 100f)); + root.RowStyles.Add(new RowStyle(SizeType.Absolute, 120f)); + + var toolbar = new FlowLayoutPanel + { + Dock = DockStyle.Fill, + AutoSize = true, + WrapContents = false, + Padding = new Padding(0, 0, 0, 16) + }; + + var search = new MaterialTextBox + { + Hint = "Поиск по наименованию…", + Width = 320, + Margin = new Padding(0, 8, 12, 0) + }; + var clearBtn = new MaterialButton + { + Text = "×", + Type = MaterialButton.MaterialButtonType.Outlined, + Size = new Size(48, 36), + Margin = new Padding(0, 8, 12, 0) + }; + var supplier = new MaterialComboBox + { + Hint = "Поставщик", + Width = 200, + Margin = new Padding(0, 8, 0, 0) + }; + supplier.Items.AddRange(new object[] { "Катрен", "Протек", "Все поставщики" }); + supplier.SelectedIndex = 0; + + toolbar.Controls.Add(search); + toolbar.Controls.Add(clearBtn); + toolbar.Controls.Add(supplier); + + var grid = new MaterialListView + { + Dock = DockStyle.Fill, + FullRowSelect = true, + View = View.Details, + BorderStyle = BorderStyle.None + }; + grid.Columns.Add("Наименование", 420); + grid.Columns.Add("Производитель", 220); + grid.Columns.Add("Цена", 90); + grid.Columns.Add("Остаток", 90); + grid.Items.Add(new ListViewItem(new[] { "Нурофен 200 мг №10", "Reckitt", "189,00", "42" })); + grid.Items.Add(new ListViewItem(new[] { "Амоксициллин 500 мг №20", "Sandoz", "124,50", "18" })); + grid.Items.Add(new ListViewItem(new[] { "Омез 20 мг №30", "Dr. Reddy's", "312,00", "7" })); + + var cartCard = new MaterialCard + { + Dock = DockStyle.Fill, + Padding = new Padding(16) + }; + var cartLayout = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 4 + }; + cartLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 40f)); + cartLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20f)); + cartLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20f)); + cartLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20f)); + + cartLayout.Controls.Add(new MaterialLabel + { + Text = "Корзина: 2 позиции · 313,50 ₽", + FontType = MaterialSkinManager.fontType.Subtitle1, + Dock = DockStyle.Fill + }, 0, 0); + + cartLayout.Controls.Add(new MaterialButton + { + Text = "Сохранить заказ", + Type = MaterialButton.MaterialButtonType.Contained, + Dock = DockStyle.Fill, + HighEmphasis = true + }, 1, 0); + + cartLayout.Controls.Add(new MaterialButton + { + Text = "Очистить", + Type = MaterialButton.MaterialButtonType.Outlined, + Dock = DockStyle.Fill + }, 2, 0); + + cartLayout.Controls.Add(new MaterialButton + { + Text = "Отправить", + Type = MaterialButton.MaterialButtonType.Contained, + Dock = DockStyle.Fill + }, 3, 0); + + cartCard.Controls.Add(cartLayout); + + root.Controls.Add(toolbar, 0, 0); + root.Controls.Add(grid, 0, 1); + root.Controls.Add(cartCard, 0, 2); + return root; + } + + private Control BuildOrdersView() + { + var card = new MaterialCard + { + Dock = DockStyle.Fill, + Padding = new Padding(24) + }; + + var title = new MaterialLabel + { + Text = "Журнал заказов", + FontType = MaterialSkinManager.fontType.H5, + Dock = DockStyle.Top, + Height = 40 + }; + + var grid = new MaterialListView + { + Dock = DockStyle.Fill, + View = View.Details, + FullRowSelect = true + }; + grid.Columns.Add("№", 80); + grid.Columns.Add("Дата", 120); + grid.Columns.Add("Поставщик", 180); + grid.Columns.Add("Статус", 140); + grid.Columns.Add("Сумма", 100); + grid.Items.Add(new ListViewItem(new[] { "1042", "14.07.2026", "Катрен", "Отправлен", "4 820,00" })); + grid.Items.Add(new ListViewItem(new[] { "1041", "13.07.2026", "Протек", "Черновик", "1 240,00" })); + + card.Controls.Add(grid); + card.Controls.Add(title); + return card; + } + + private Control BuildUploadView() + { + var card = new MaterialCard + { + Dock = DockStyle.Fill, + Padding = new Padding(32) + }; + + var stack = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 1, + RowCount = 4 + }; + stack.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + stack.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + stack.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + stack.RowStyles.Add(new RowStyle(SizeType.Percent, 100f)); + + stack.Controls.Add(new MaterialLabel + { + Text = "Загрузка данных с сервера", + FontType = MaterialSkinManager.fontType.H5, + AutoSize = true, + Margin = new Padding(0, 0, 0, 8) + }, 0, 0); + + stack.Controls.Add(new MaterialLabel + { + Text = "MaterialSkin даёт готовые карточки, ripple-кнопки и тёмную тему из коробки.", + FontType = MaterialSkinManager.fontType.Body1, + AutoSize = true, + Margin = new Padding(0, 0, 0, 24) + }, 0, 1); + + var buttons = new FlowLayoutPanel { AutoSize = true, WrapContents = false }; + buttons.Controls.Add(new MaterialButton + { + Text = "Загрузить прайс", + Type = MaterialButton.MaterialButtonType.Contained, + HighEmphasis = true, + Margin = new Padding(0, 0, 12, 0) + }); + buttons.Controls.Add(new MaterialButton + { + Text = "Отправить заказы", + Type = MaterialButton.MaterialButtonType.Outlined + }); + stack.Controls.Add(buttons, 0, 2); + + card.Controls.Add(stack); + return card; + } +} diff --git a/src/Elfisa.MaterialDemo/Program.cs b/src/Elfisa.MaterialDemo/Program.cs new file mode 100644 index 0000000..d5623db --- /dev/null +++ b/src/Elfisa.MaterialDemo/Program.cs @@ -0,0 +1,24 @@ +using MaterialSkin; +using MaterialSkin.Controls; + +namespace Elfisa.MaterialDemo; + +internal static class Program +{ + [STAThread] + private static void Main() + { + ApplicationConfiguration.Initialize(); + + var skin = MaterialSkinManager.Instance; + skin.Theme = MaterialSkinManager.Themes.DARK; + skin.ColorScheme = new ColorScheme( + Primary.Teal800, + Primary.Teal900, + Primary.Teal200, + Accent.LightBlue200, + TextShade.WHITE); + + Application.Run(new PharmacyMaterialForm()); + } +} diff --git a/src/Elfisa.MaterialDemo/README.md b/src/Elfisa.MaterialDemo/README.md new file mode 100644 index 0000000..cbd9ab0 --- /dev/null +++ b/src/Elfisa.MaterialDemo/README.md @@ -0,0 +1,19 @@ +# MaterialSkin demo + +Отдельное приложение для сравнения **MaterialSkin.2** с текущей библиотекой **Elfisa.UI**. + +Запуск: + +```powershell +dotnet run --project src/Elfisa.MaterialDemo/Elfisa.MaterialDemo.csproj -c Release +``` + +Или exe: + +`src/Elfisa.MaterialDemo/bin/Release/net8.0-windows/Elfisa.MaterialDemo.exe` + +## Зачем отдельно + +Основной клиент (`ElectronicPharmacy`) уже построен на `Elfisa.UI` (owner-draw, PharmaTrust, кастомные контролы). Подмешивать MaterialSkin в ту же форму — конфликт двух систем тем и двойная перерисовка. + +Это демо показывает, как выглядел бы тот же сценарий (прайс / заказы / загрузка) на готовой библиотеке Google Material. diff --git a/src/Elfisa.UI/Controls/ModernButton.cs b/src/Elfisa.UI/Controls/ModernButton.cs index 2e0213b..ce7cb79 100644 --- a/src/Elfisa.UI/Controls/ModernButton.cs +++ b/src/Elfisa.UI/Controls/ModernButton.cs @@ -110,10 +110,10 @@ namespace Elfisa.UI.Controls FlatStyle = FlatStyle.Flat; FlatAppearance.BorderSize = 0; Cursor = Cursors.Hand; - Font = new Font("Segoe UI Semibold", 12f); + Font = new Font("Segoe UI Semibold", 10f, FontStyle.Bold); Height = ModernButtonStyles.DefaultHeight; - MinimumSize = new Size(64, ModernButtonStyles.DefaultHeight); - Padding = new Padding(14, 6, 14, 6); + MinimumSize = new Size(72, ModernButtonStyles.DefaultHeight); + Padding = new Padding(16, 6, 16, 6); TabStop = true; _animTimer = new AnimationTimer(v => { _hoverAnim = v; Invalidate(); }); } @@ -127,11 +127,13 @@ namespace Elfisa.UI.Controls } else if (_compactIconMode) { - Font = new Font("Segoe UI", 16f, FontStyle.Bold); + Font = new Font("Segoe UI Semibold", 13f, FontStyle.Bold); } else { - Font = theme.FontSemibold14; + Font = theme.FontSemibold14 != null + ? new Font(theme.FontSemibold14.FontFamily, 10f, FontStyle.Bold) + : new Font("Segoe UI Semibold", 10f, FontStyle.Bold); } Invalidate(); } @@ -208,34 +210,61 @@ namespace Elfisa.UI.Controls g.CompositingQuality = CompositingQuality.HighQuality; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; - var bounds = new Rectangle(0, 0, Width - 1, Height - 1); - var radius = theme.CornerRadiusSmall; - var baseColor = ResolveBaseColor(theme); - var fill = baseColor; + var bounds = new Rectangle(0, 0, Width, Height); + var usesGradient = SkillGradientButtonPaint.UsesGradient(_buttonStyle, _compactIconMode); if (_headerNavMode) { + var headerBounds = new Rectangle(0, 0, Width - 1, Height - 1); + var headerRadius = theme.CornerRadiusSmall; var activeBoost = _isActiveItem ? 0.22f : 0.06f; var hoverBoost = _hoverAnim * 0.14f; var alpha = (int)(255 * (activeBoost + hoverBoost)); - fill = Color.FromArgb(alpha, 255, 255, 255); - using (var brush = new SolidBrush(fill)) + var headerFill = Color.FromArgb(alpha, 255, 255, 255); + using (var brush = new SolidBrush(headerFill)) { - GraphicsExtensions.FillRoundedRectangle(g, brush, bounds, radius); + GraphicsExtensions.FillRoundedRectangle(g, brush, headerBounds, headerRadius); } if (_isActiveItem) { using (var pen = new Pen(Color.FromArgb(180, 255, 255, 255), 1f)) { - GraphicsExtensions.DrawRoundedRectangle(g, pen, bounds, radius); + GraphicsExtensions.DrawRoundedRectangle(g, pen, headerBounds, headerRadius); } } - DrawHeaderNavContent(g, bounds, theme); + DrawHeaderNavContent(g, headerBounds, theme); return; } + if (usesGradient && !_topNavMode && !_sidebarMode) + { + SkillGradientButtonPaint.Paint( + g, + bounds, + _buttonStyle, + _compactIconMode, + _hover && Enabled, + _pressed && Enabled, + Enabled); + + var contentRect = SkillGradientButtonPaint.GetContentRect( + bounds, + _buttonStyle, + _compactIconMode, + Enabled); + + var gradientTextColor = Enabled ? theme.TextOnAccent : theme.TextMuted; + DrawActionButtonContent(g, contentRect, gradientTextColor); + return; + } + + var flatBounds = new Rectangle(0, 0, Width - 1, Height - 1); + var radius = _compactIconMode ? 6 : 6; + var baseColor = ResolveBaseColor(theme); + var fill = baseColor; + if (_topNavMode) { fill = Color.Transparent; @@ -277,17 +306,14 @@ namespace Elfisa.UI.Controls fill = GraphicsExtensions.Blend( ResolveHoverColor(theme, baseColor), baseColor, - 1f - (_hoverAnim * 0.35f)); + 1f - (_hoverAnim * 0.45f)); } - if (!_topNavMode) + if (!_topNavMode && fill.A > 0) { - if (fill.A > 0) + using (var brush = new SolidBrush(fill)) { - using (var brush = new SolidBrush(fill)) - { - GraphicsExtensions.FillRoundedRectangle(g, brush, bounds, radius); - } + GraphicsExtensions.FillRoundedRectangle(g, brush, flatBounds, radius); } } @@ -310,7 +336,7 @@ namespace Elfisa.UI.Controls } else if ((_buttonStyle == ModernButtonStyle.Secondary || _buttonStyle == ModernButtonStyle.DangerSoft - || _buttonStyle == ModernButtonStyle.Subtle) && !_sidebarMode) + || _buttonStyle == ModernButtonStyle.Subtle) && !_sidebarMode && !_compactIconMode) { if (_buttonStyle == ModernButtonStyle.Subtle && !_hover && !_pressed && !_focused) { @@ -330,14 +356,14 @@ namespace Elfisa.UI.Controls using (var pen = new Pen(borderColor, 1f)) { - GraphicsExtensions.DrawRoundedRectangle(g, pen, bounds, radius); + GraphicsExtensions.DrawRoundedRectangle(g, pen, flatBounds, radius); } } } if (_focused && Enabled && ShowFocusCues && !_headerNavMode && !_topNavMode && !_sidebarMode) { - var focusBounds = Rectangle.Inflate(bounds, -2, -2); + var focusBounds = Rectangle.Inflate(flatBounds, -2, -2); using (var pen = new Pen(theme.InputBorderFocus, 2f)) { GraphicsExtensions.DrawRoundedRectangle(g, pen, focusBounds, Math.Max(2, radius - 1)); @@ -366,7 +392,7 @@ namespace Elfisa.UI.Controls textColor = GraphicsExtensions.Blend(textColor, theme.TextMuted, 0.55f); } - DrawActionButtonContent(g, bounds, textColor); + DrawActionButtonContent(g, flatBounds, textColor); } private void DrawActionButtonContent(Graphics g, Rectangle bounds, Color textColor) @@ -507,9 +533,9 @@ namespace Elfisa.UI.Controls if (baseColor == theme.AccentPrimary) return theme.AccentPrimaryHover; if (baseColor == theme.Surface) return theme.GridRowHover; if (baseColor == theme.DangerSoft) return GraphicsExtensions.Blend(theme.DangerSoft, theme.Danger, 0.08f); + if (baseColor == theme.Danger) return GraphicsExtensions.Blend(theme.Danger, Color.White, 0.08f); if (baseColor == theme.Success) return GraphicsExtensions.Blend(theme.Success, Color.White, 0.12f); if (baseColor == theme.Warning) return GraphicsExtensions.Blend(theme.Warning, Color.White, 0.12f); - if (baseColor == theme.Danger) return GraphicsExtensions.Blend(theme.Danger, Color.White, 0.12f); return GraphicsExtensions.Blend(baseColor, Color.White, 0.08f); } diff --git a/src/Elfisa.UI/Controls/ModernButtonStyles.cs b/src/Elfisa.UI/Controls/ModernButtonStyles.cs index df7cf1b..8105e1b 100644 --- a/src/Elfisa.UI/Controls/ModernButtonStyles.cs +++ b/src/Elfisa.UI/Controls/ModernButtonStyles.cs @@ -6,11 +6,13 @@ using Elfisa.UI.Theming; namespace Elfisa.UI.Controls { /// - /// WinUI 3–style presets for in WinForms. + /// Стили кнопок по макету Pharma Trust (variant-e-pharma-trust.html). /// public static class ModernButtonStyles { - public const int DefaultHeight = 32; + public const int DefaultHeight = 34; + public const int ClearSize = 32; + private static readonly Font ActionFont = new Font("Segoe UI Semibold", 10f, FontStyle.Bold); public static void ApplyAuto(ModernButton button) { @@ -28,20 +30,20 @@ namespace Elfisa.UI.Controls if (IsDangerAction(text)) { - ApplyAction(button, ModernButtonStyle.Danger, ResolveIcon(text, "\uE74D")); + ApplyAction(button, ModernButtonStyle.Danger); return; } if (IsPrimaryAction(text)) { - ApplyAction(button, ModernButtonStyle.Primary, ResolveIcon(text, "\uE725")); + ApplyAction(button, ModernButtonStyle.Primary); return; } - ApplyAction(button, ModernButtonStyle.Secondary, ResolveIcon(text, null)); + ApplyAction(button, ModernButtonStyle.Secondary); } - public static void ApplyAction(ModernButton button, ModernButtonStyle style, string iconGlyph = null) + public static void ApplyAction(ModernButton button, ModernButtonStyle style) { if (button == null) { @@ -54,15 +56,16 @@ namespace Elfisa.UI.Controls button.BackgroundImage = null; button.UseVisualStyleBackColor = false; button.AutoSizeMode = AutoSizeMode.GrowAndShrink; - button.MinimumSize = new Size(64, DefaultHeight); - if (button.Height < DefaultHeight) - { - button.Height = DefaultHeight; - } + button.IconGlyph = null; + button.AllowTextEllipsis = false; + button.Font = ActionFont; + button.Padding = new Padding(16, 6, 16, 6); + var height = style == ModernButtonStyle.Primary || style == ModernButtonStyle.Danger + ? DefaultHeight + 3 + : DefaultHeight; + button.Height = Math.Max(button.Height, height); - button.Padding = new Padding(14, 6, 14, 6); - button.Font = (ThemeManager.Colors?.FontSemibold14) ?? new Font("Segoe UI Semibold", 12f); - button.IconGlyph = iconGlyph; + FitToText(button, height); } public static void ApplyClear(ModernButton button) @@ -72,24 +75,58 @@ namespace Elfisa.UI.Controls return; } - button.ButtonStyle = ModernButtonStyle.DangerSoft; + button.ButtonStyle = ModernButtonStyle.Danger; button.CompactIconMode = true; button.Image = null; button.BackgroundImage = null; button.UseVisualStyleBackColor = false; button.AutoSizeMode = AutoSizeMode.GrowAndShrink; button.Text = "×"; - button.Font = new Font("Segoe UI", 13f, FontStyle.Bold); - button.MinimumSize = new Size(32, DefaultHeight); - button.Size = new Size(Math.Max(button.Width, 32), DefaultHeight); + button.Font = new Font("Segoe UI Semibold", 13f, FontStyle.Bold); button.IconGlyph = null; + button.AllowTextEllipsis = false; + button.MinimumSize = new Size(ClearSize, ClearSize + 3); + button.Size = new Size(ClearSize, ClearSize + 3); + } + + public static void FitToText(ModernButton button, int height = DefaultHeight) + { + if (button == null || button.CompactIconMode) + { + return; + } + + var text = button.Text ?? string.Empty; + if (text.Length == 0) + { + return; + } + + var textWidth = TextRenderer.MeasureText( + text, + button.Font ?? ActionFont, + Size.Empty, + TextFormatFlags.SingleLine).Width; + + var width = textWidth + button.Padding.Horizontal + 8; + width = Math.Max(width, 72); + button.MinimumSize = new Size(width, height); + if (button.Width < width) + { + button.Width = width; + } + + if (button.Height < height) + { + button.Height = height; + } } private static bool IsClearButton(ModernButton button, string text) { return text == "×" || text.Equals("X", StringComparison.OrdinalIgnoreCase) - || (button.Width <= 40 && button.Height <= 36 && text.Length <= 2); + || (button.Width <= 40 && button.Height <= 40 && text.Length <= 2); } private static bool IsDangerAction(string text) @@ -116,61 +153,5 @@ namespace Elfisa.UI.Controls || lower.Contains("save") || lower == "ок"; } - - private static string ResolveIcon(string text, string fallback) - { - var lower = text.ToLowerInvariant(); - if (lower.Contains("отправ") || lower.Contains("send")) - { - return "\uE725"; - } - - if (lower.Contains("удал") || lower.Contains("delete")) - { - return "\uE74D"; - } - - if (lower.Contains("коммент")) - { - return "\uE8BD"; - } - - if (lower.Contains("сохран") || lower.Contains("save")) - { - return "\uE74E"; - } - - if (lower.Contains("отмен") || lower.Contains("cancel") || lower.Contains("закры")) - { - return "\uE711"; - } - - if (lower.Contains("очист") || lower.Contains("clear")) - { - return "\uE894"; - } - - if (lower.Contains("печат") || lower.Contains("print")) - { - return "\uE749"; - } - - if (lower.Contains("импорт") || lower.Contains("import")) - { - return "\uE896"; - } - - if (lower.Contains("подроб") || lower.Contains("info")) - { - return "\uE946"; - } - - if (lower.Contains("зарег") || lower.Contains("подтвер") || lower == "ок") - { - return "\uE73E"; - } - - return fallback; - } } } diff --git a/src/Elfisa.UI/Helpers/SkillGradientButtonPaint.cs b/src/Elfisa.UI/Helpers/SkillGradientButtonPaint.cs new file mode 100644 index 0000000..7565792 --- /dev/null +++ b/src/Elfisa.UI/Helpers/SkillGradientButtonPaint.cs @@ -0,0 +1,159 @@ +using System.Drawing; +using System.Drawing.Drawing2D; +using Elfisa.UI.Controls; + +namespace Elfisa.UI.Helpers +{ + /// + /// Градиент и тень из skill winforms-modern-buttons для ModernButton. + /// + internal static class SkillGradientButtonPaint + { + private static readonly Color ShadowBase = Color.FromArgb(60, 0, 0, 0); + + public static bool UsesGradient(ModernButtonStyle style, bool compactIconMode) + { + return style == ModernButtonStyle.Primary + || style == ModernButtonStyle.Danger + || (compactIconMode && style == ModernButtonStyle.Danger); + } + + public static int GetCornerRadius(ModernButtonStyle style, bool compactIconMode) + { + if (compactIconMode) + { + return 6; + } + + return style == ModernButtonStyle.Primary ? 12 : 8; + } + + public static int GetShadowDepth(ModernButtonStyle style, bool compactIconMode, bool enabled) + { + if (!enabled) + { + return 0; + } + + if (compactIconMode) + { + return 3; + } + + return style == ModernButtonStyle.Primary ? 5 : 4; + } + + public static void GetGradient( + ModernButtonStyle style, + bool hover, + bool pressed, + out Color top, + out Color bottom) + { + if (style == ModernButtonStyle.Danger) + { + if (pressed) + { + top = Color.FromArgb(175, 30, 30); + bottom = Color.FromArgb(140, 20, 20); + } + else if (hover) + { + top = Color.FromArgb(245, 110, 110); + bottom = Color.FromArgb(215, 55, 55); + } + else + { + top = Color.FromArgb(235, 87, 87); + bottom = Color.FromArgb(200, 40, 40); + } + + return; + } + + // Primary preset from skill + if (pressed) + { + top = Color.FromArgb(25, 70, 160); + bottom = Color.FromArgb(15, 55, 130); + } + else if (hover) + { + top = Color.FromArgb(90, 150, 255); + bottom = Color.FromArgb(45, 105, 220); + } + else + { + top = Color.FromArgb(66, 133, 244); + bottom = Color.FromArgb(30, 87, 194); + } + } + + public static void Paint( + Graphics g, + Rectangle clientRect, + ModernButtonStyle style, + bool compactIconMode, + bool hover, + bool pressed, + bool enabled) + { + var shadowDepth = GetShadowDepth(style, compactIconMode, enabled); + var radius = GetCornerRadius(style, compactIconMode); + var buttonRect = new Rectangle( + clientRect.X, + clientRect.Y, + clientRect.Width - 1, + clientRect.Height - 1 - shadowDepth); + + if (!enabled) + { + using (var brush = new SolidBrush(Color.FromArgb(220, 230, 235))) + { + GraphicsExtensions.FillRoundedRectangle(g, brush, buttonRect, radius); + } + + return; + } + + for (var i = shadowDepth; i > 0; i--) + { + var alpha = ShadowBase.A * i / (shadowDepth * 3); + using (var shadowBrush = new SolidBrush(Color.FromArgb(alpha, ShadowBase))) + using (var shadowPath = GraphicsExtensions.CreateRoundedRectangle( + new Rectangle( + clientRect.X + i, + buttonRect.Y + i + 1, + buttonRect.Width - i, + buttonRect.Height - 1), + radius)) + { + g.FillPath(shadowBrush, shadowPath); + } + } + + GetGradient(style, hover, pressed, out var top, out var bottom); + + using (var path = GraphicsExtensions.CreateRoundedRectangle(buttonRect, radius)) + using (var brush = new LinearGradientBrush(buttonRect, top, bottom, LinearGradientMode.Vertical)) + { + g.FillPath(brush, path); + + using (var highlightPen = new Pen(Color.FromArgb(40, Color.White), 1f)) + { + g.DrawArc(highlightPen, buttonRect.X, buttonRect.Y, radius * 2, radius * 2, 180, 90); + } + } + } + + public static Rectangle GetContentRect(Rectangle clientRect, ModernButtonStyle style, bool compactIconMode, bool enabled) + { + var shadowDepth = GetShadowDepth(style, compactIconMode, enabled); + return new Rectangle( + clientRect.X, + clientRect.Y, + clientRect.Width - 1, + clientRect.Height - 1 - shadowDepth); + } + } +}