diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..beb1ecd --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,26 @@ +# Copilot instructions — Электронная Фармация + +## Stack + +- WinForms (.NET Framework 4.7.2), **not** WinUI 3 +- UI library: `src/Elfisa.UI` (custom Modern* controls + theming) +- Apply **Fluent Design principles** from `winui-design` via theme tokens, not XAML + +## Design rules (from winui-design, adapted) + +1. Use `ThemeManager.Colors` / `UiThemeHelper` — never hard-code colors in forms. +2. Typography: Segoe UI 12–14; icons: Segoe Fluent Icons via `FluentFonts`. +3. Spacing: 8px grid (8, 16, 24, 32). +4. App shape: developer tool — top nav + document tabs + content cards/grids. +5. Feedback: `ToastNotification` / `UiDialogs`, not blocking `MessageBox` for routine messages. +6. Data grids: styled `ModernDataGridView`; no WPF DataGrid patterns. + +## When changing UI + +- Prefer editing `Elfisa.UI` theme/palette over per-form colors. +- Test light theme (`FluentWin11`) after visual changes. +- Keep business logic in `ElectronicPharmacy`; keep visuals in `Elfisa.UI`. + +## WinUI plugin + +See `docs/WINUI-PLUGIN-SETUP.md` for `winui@awesome-copilot` install and CLI usage. diff --git a/docs/WINUI-PLUGIN-SETUP.md b/docs/WINUI-PLUGIN-SETUP.md new file mode 100644 index 0000000..fcbcd02 --- /dev/null +++ b/docs/WINUI-PLUGIN-SETUP.md @@ -0,0 +1,57 @@ +# WinUI agent plugin (Microsoft) — setup for this repo + +The [WinUI agent plugin](https://learn.microsoft.com/en-us/windows/apps/develop/ai-assisted/winui-agent-plugin) targets **GitHub Copilot CLI** and **Claude Code**. It does **not** plug into Cursor chat directly. + +This WinForms project applies **Fluent Design tokens** from the plugin's `winui-design` skill in `Elfisa.UI` (`FluentWin11` theme). + +## Prerequisites + +```powershell +winget install Microsoft.winappcli --source winget +winget install GitHub.cli +``` + +GitHub Copilot subscription required for Copilot CLI. + +## Install Copilot CLI + WinUI plugin + +```powershell +gh auth login +gh extension install github/gh-copilot +gh copilot plugin install winui@awesome-copilot +gh copilot plugin list +``` + +## Use with Copilot CLI + +```powershell +gh copilot -p "@winui-dev Review the WinForms shell in src/ElectronicPharmacy for Fluent alignment" +``` + +Interactive session: + +```powershell +copilot +# then: @winui-dev Suggest toolbar layout improvements for FOrders +``` + +## WinForms adaptation (this repo) + +| WinUI guidance | WinForms implementation | +|----------------|-------------------------| +| Semantic brushes, no hard-coded `#RRGGBB` in UI code | `ThemeColors` + `FluentWin11Palette` | +| Segoe UI typography | `Segoe UI` 12–14 in palette fonts | +| 8px spacing grid | Toolbar layout in `FOrders.ApplyToolbarLayout` | +| Developer-tool silhouette | `ModernAppHeader` + `ModernDocumentTabStrip` | +| WinUI `AccentButton` / `Button` | `ModernButton` + `ModernButtonStyles.ApplyAuto` | +| Focus ring | keyboard focus outline in `ModernButton` | +| Disabled opacity | muted fill/text when `Enabled = false` | + +Default theme: `ThemeVariant.FluentWin11` in `Program.cs`. + +## Claude Code (optional) + +```powershell +claude plugin marketplace add microsoft/win-dev-skills +claude plugin install winui@win-dev-skills +``` diff --git a/src/ElectronicPharmacy/Classes/UiThemeHelper.cs b/src/ElectronicPharmacy/Classes/UiThemeHelper.cs index b46f464..82b6b4b 100644 --- a/src/ElectronicPharmacy/Classes/UiThemeHelper.cs +++ b/src/ElectronicPharmacy/Classes/UiThemeHelper.cs @@ -30,6 +30,7 @@ namespace Электронная_Фармация.Classes if (!modernButton.HeaderNavMode && !modernButton.TopNavMode && !modernButton.SidebarMode) { modernButton.AllowTextEllipsis = false; + ModernButtonStyles.ApplyAuto(modernButton); } } else if (control is ModernTextBox modernTextBox) @@ -44,10 +45,6 @@ namespace Электронная_Фармация.Classes { modernSearch.ApplyTheme(theme); } - else if (control is ModernComboBox modernCombo) - { - modernCombo.ApplyTheme(theme); - } else if (control is DataGridView grid) { grid.EnableHeadersVisualStyles = false; diff --git a/src/ElectronicPharmacy/Form1.UiShell.cs b/src/ElectronicPharmacy/Form1.UiShell.cs index 3b3005a..39e80b4 100644 --- a/src/ElectronicPharmacy/Form1.UiShell.cs +++ b/src/ElectronicPharmacy/Form1.UiShell.cs @@ -8,6 +8,7 @@ using Elfisa.UI.Loading; using Elfisa.UI.Notifications; using Elfisa.UI.Theming; using Электронная_Фармация.Classes; +using Электронная_Фармация.Properties; using Электронная_Фармация.Forms; using Электронная_Фармация.HelpForms; using Электронная_Фармация.UserControls; @@ -23,11 +24,13 @@ namespace Электронная_Фармация private ModernAppHeader _appHeader; private ModernDocumentTabStrip _documentTabStrip; private Panel _documentHost; + private Panel _statusFooter; + private Label _lblCurrentUser; private ContextMenuStrip _directoryMenu; partial void InitializeModernShell() { - Font = new Font("Segoe UI", 13f); + Font = new Font("Segoe UI", 12f); AutoScaleMode = AutoScaleMode.Dpi; KeyPreview = true; MinimumSize = new Size(900, 620); @@ -54,8 +57,36 @@ namespace Электронная_Фармация _documentTabStrip = new ModernDocumentTabStrip { Name = "documentTabStrip", Dock = DockStyle.Top }; _documentHost = new Panel { Name = "documentHost", Dock = DockStyle.Fill, Tag = "chrome-child" }; + _statusFooter = new Panel + { + Name = "statusFooter", + Dock = DockStyle.Bottom, + Height = 30, + Tag = "status-footer", + Padding = new Padding(12, 0, 16, 0) + }; + _statusFooter.Paint += (_, e) => + { + var border = ThemeManager.Colors.Border; + using (var pen = new Pen(border)) + { + e.Graphics.DrawLine(pen, 0, 0, _statusFooter.Width, 0); + } + }; + + _lblCurrentUser = new Label + { + Name = "lblCurrentUser", + Dock = DockStyle.Fill, + TextAlign = ContentAlignment.MiddleRight, + AutoEllipsis = true, + Tag = "muted" + }; + _statusFooter.Controls.Add(_lblCurrentUser); + Controls.Add(_loadingOverlay); Controls.Add(_documentHost); + Controls.Add(_statusFooter); Controls.Add(_documentTabStrip); Controls.Add(_appHeader); @@ -88,6 +119,31 @@ namespace Электронная_Фармация ThemeManager.CurrentVariant = ThemeVariant.PharmaTrust; _themeService.Initialize(this); UiThemeHelper.ApplyToControlTree(this); + RefreshCurrentUserStatus(); + } + + private void RefreshCurrentUserStatus() + { + if (_lblCurrentUser == null) + { + return; + } + + var login = Settings.Default.stringLogin?.Trim(); + var hasToken = !string.IsNullOrWhiteSpace(Settings.Default.stringToken); + + if (!string.IsNullOrWhiteSpace(login) && hasToken) + { + _lblCurrentUser.Text = $"Пользователь: {login}"; + } + else if (!string.IsNullOrWhiteSpace(login)) + { + _lblCurrentUser.Text = $"Пользователь: {login} (не авторизован)"; + } + else + { + _lblCurrentUser.Text = "Пользователь не авторизован"; + } } private void OnAppHeaderItemSelected(object sender, string key) diff --git a/src/ElectronicPharmacy/Form1.cs b/src/ElectronicPharmacy/Form1.cs index af98918..93323d3 100644 --- a/src/ElectronicPharmacy/Form1.cs +++ b/src/ElectronicPharmacy/Form1.cs @@ -573,7 +573,10 @@ namespace Электронная_Фармация { var hF_Registration = new HF_Registration(); UiThemeHelper.ApplyToControlTree(hF_Registration); - hF_Registration.ShowDialog(); + if (hF_Registration.ShowDialog() == DialogResult.OK) + { + RefreshCurrentUserStatus(); + } } private async void tsBtnSentData_Click(object sender, EventArgs e) diff --git a/src/ElectronicPharmacy/HelpForms/HFConsignees.Designer.cs b/src/ElectronicPharmacy/HelpForms/HFConsignees.Designer.cs index 71d4faf..3594399 100644 --- a/src/ElectronicPharmacy/HelpForms/HFConsignees.Designer.cs +++ b/src/ElectronicPharmacy/HelpForms/HFConsignees.Designer.cs @@ -30,8 +30,8 @@ { this.dgvConsignees = new Elfisa.UI.Controls.ModernDataGridView(); this.label1 = new System.Windows.Forms.Label(); - this.btnConfirmConsignee = new System.Windows.Forms.Button(); - this.btnCloseThis = new System.Windows.Forms.Button(); + this.btnConfirmConsignee = new Elfisa.UI.Controls.ModernButton(); + this.btnCloseThis = new Elfisa.UI.Controls.ModernButton(); ((System.ComponentModel.ISupportInitialize)(this.dgvConsignees)).BeginInit(); this.SuspendLayout(); // @@ -113,7 +113,7 @@ private Elfisa.UI.Controls.ModernDataGridView dgvConsignees; private System.Windows.Forms.Label label1; - private System.Windows.Forms.Button btnConfirmConsignee; - private System.Windows.Forms.Button btnCloseThis; + private Elfisa.UI.Controls.ModernButton btnConfirmConsignee; + private Elfisa.UI.Controls.ModernButton btnCloseThis; } } \ No newline at end of file diff --git a/src/ElectronicPharmacy/HelpForms/HF_Comment.Designer.cs b/src/ElectronicPharmacy/HelpForms/HF_Comment.Designer.cs index 299b95a..3d354cd 100644 --- a/src/ElectronicPharmacy/HelpForms/HF_Comment.Designer.cs +++ b/src/ElectronicPharmacy/HelpForms/HF_Comment.Designer.cs @@ -59,34 +59,22 @@ // // btnCancel // - this.btnCancel.BackColor = System.Drawing.Color.Firebrick; - this.btnCancel.FlatAppearance.BorderSize = 0; - this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnCancel.Font = new System.Drawing.Font("Segoe UI Semibold", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204))); - this.btnCancel.ForeColor = System.Drawing.Color.White; this.btnCancel.Location = new System.Drawing.Point(335, 224); this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); this.btnCancel.Name = "btnCancel"; this.btnCancel.Size = new System.Drawing.Size(100, 41); this.btnCancel.TabIndex = 2; this.btnCancel.Text = "Отмена"; - this.btnCancel.UseVisualStyleBackColor = false; this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); // // btnAddComment // - this.btnAddComment.BackColor = System.Drawing.SystemColors.HotTrack; - this.btnAddComment.FlatAppearance.BorderSize = 0; - this.btnAddComment.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnAddComment.Font = new System.Drawing.Font("Segoe UI Semibold", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204))); - this.btnAddComment.ForeColor = System.Drawing.Color.White; this.btnAddComment.Location = new System.Drawing.Point(472, 224); this.btnAddComment.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); this.btnAddComment.Name = "btnAddComment"; this.btnAddComment.Size = new System.Drawing.Size(100, 41); this.btnAddComment.TabIndex = 3; this.btnAddComment.Text = "ОК"; - this.btnAddComment.UseVisualStyleBackColor = false; this.btnAddComment.Click += new System.EventHandler(this.btnAddComment_Click); // // HF_Comment diff --git a/src/ElectronicPharmacy/HelpForms/HF_Registration.Designer.cs b/src/ElectronicPharmacy/HelpForms/HF_Registration.Designer.cs index 09e7947..dedb66b 100644 --- a/src/ElectronicPharmacy/HelpForms/HF_Registration.Designer.cs +++ b/src/ElectronicPharmacy/HelpForms/HF_Registration.Designer.cs @@ -83,32 +83,20 @@ // // btnConfirmRegistration // - this.btnConfirmRegistration.BackColor = System.Drawing.SystemColors.HotTrack; - this.btnConfirmRegistration.FlatAppearance.BorderSize = 0; - this.btnConfirmRegistration.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnConfirmRegistration.Font = new System.Drawing.Font("Segoe UI Semibold", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204))); - this.btnConfirmRegistration.ForeColor = System.Drawing.Color.White; this.btnConfirmRegistration.Location = new System.Drawing.Point(188, 401); this.btnConfirmRegistration.Name = "btnConfirmRegistration"; this.btnConfirmRegistration.Size = new System.Drawing.Size(215, 43); this.btnConfirmRegistration.TabIndex = 5; this.btnConfirmRegistration.Text = "Зарегистрировать"; - this.btnConfirmRegistration.UseVisualStyleBackColor = false; this.btnConfirmRegistration.Click += new System.EventHandler(this.btnConfirmRegistration_Click); // // btnCloseThis // - this.btnCloseThis.BackColor = System.Drawing.Color.Firebrick; - this.btnCloseThis.FlatAppearance.BorderSize = 0; - this.btnCloseThis.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnCloseThis.Font = new System.Drawing.Font("Segoe UI Semibold", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204))); - this.btnCloseThis.ForeColor = System.Drawing.Color.White; this.btnCloseThis.Location = new System.Drawing.Point(28, 401); this.btnCloseThis.Name = "btnCloseThis"; this.btnCloseThis.Size = new System.Drawing.Size(125, 43); this.btnCloseThis.TabIndex = 6; this.btnCloseThis.Text = "Отмена"; - this.btnCloseThis.UseVisualStyleBackColor = false; this.btnCloseThis.Click += new System.EventHandler(this.btnCloseThis_Click); // // checkTokenGained diff --git a/src/ElectronicPharmacy/Program.cs b/src/ElectronicPharmacy/Program.cs index a8be127..b7d1b4c 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.PharmaTrust; + ThemeManager.CurrentVariant = ThemeVariant.FluentWin11; Application.Run(new ElectroPharmacy()); } } diff --git a/src/ElectronicPharmacy/UserControls/FOrders.Designer.cs b/src/ElectronicPharmacy/UserControls/FOrders.Designer.cs index 2fcdc47..4fa6a2e 100644 --- a/src/ElectronicPharmacy/UserControls/FOrders.Designer.cs +++ b/src/ElectronicPharmacy/UserControls/FOrders.Designer.cs @@ -32,9 +32,10 @@ System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FOrders)); this.panelToolbar = new System.Windows.Forms.Panel(); + this.panelToolbar = new System.Windows.Forms.Panel(); this.txtGoodFromOrders = new Elfisa.UI.Controls.ModernTextBox(); this.btnClearSuppliers = new Elfisa.UI.Controls.ModernButton(); - this.comboSuppliers = new System.Windows.Forms.ComboBox(); + this.comboSuppliers = new Elfisa.UI.Controls.ModernComboBox(); this.panelOrderItems = new System.Windows.Forms.Panel(); this.dataGridView1 = new Elfisa.UI.Controls.ModernDataGridView(); this.dgvOrderItems = new Elfisa.UI.Controls.ModernDataGridView(); @@ -98,12 +99,12 @@ // // comboSuppliers // - this.comboSuppliers.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204))); + this.comboSuppliers.Font = new System.Drawing.Font("Segoe UI", 11.25F); this.comboSuppliers.FormattingEnabled = true; this.comboSuppliers.Location = new System.Drawing.Point(874, 31); this.comboSuppliers.Margin = new System.Windows.Forms.Padding(4); this.comboSuppliers.Name = "comboSuppliers"; - this.comboSuppliers.Size = new System.Drawing.Size(160, 29); + this.comboSuppliers.Size = new System.Drawing.Size(170, 33); this.comboSuppliers.TabIndex = 21; this.comboSuppliers.Text = "Поставщик"; this.comboSuppliers.Visible = false; @@ -182,6 +183,7 @@ this.panelOrdersHeader.Location = new System.Drawing.Point(0, 0); this.panelOrdersHeader.Margin = new System.Windows.Forms.Padding(2); this.panelOrdersHeader.Name = "panelOrdersHeader"; + this.panelOrdersHeader.Padding = new System.Windows.Forms.Padding(8, 14, 8, 6); this.panelOrdersHeader.Size = new System.Drawing.Size(1178, 200); this.panelOrdersHeader.TabIndex = 0; // @@ -260,7 +262,7 @@ // panel1 // this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; - this.panel1.Location = new System.Drawing.Point(0, 96); + this.panel1.Location = new System.Drawing.Point(0, 114); this.panel1.Margin = new System.Windows.Forms.Padding(2); this.panel1.Name = "panel1"; this.panel1.Size = new System.Drawing.Size(1201, 354); @@ -285,8 +287,8 @@ this.panelToolbar.Dock = System.Windows.Forms.DockStyle.Top; this.panelToolbar.Location = new System.Drawing.Point(0, 0); this.panelToolbar.Name = "panelToolbar"; - this.panelToolbar.Padding = new System.Windows.Forms.Padding(10, 8, 10, 8); - this.panelToolbar.Size = new System.Drawing.Size(1201, 96); + this.panelToolbar.Padding = new System.Windows.Forms.Padding(12, 10, 12, 14); + this.panelToolbar.Size = new System.Drawing.Size(1201, 114); this.panelToolbar.TabIndex = 31; // // label2 @@ -340,33 +342,21 @@ // btnClearGoodSearch // this.btnClearGoodSearch.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.btnClearGoodSearch.BackColor = System.Drawing.Color.Firebrick; - this.btnClearGoodSearch.FlatAppearance.BorderSize = 0; - this.btnClearGoodSearch.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnClearGoodSearch.Font = new System.Drawing.Font("Segoe UI Semibold", 7.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204))); - this.btnClearGoodSearch.Image = ((System.Drawing.Image)(resources.GetObject("btnClearGoodSearch.Image"))); this.btnClearGoodSearch.Location = new System.Drawing.Point(252, 48); this.btnClearGoodSearch.Margin = new System.Windows.Forms.Padding(4); this.btnClearGoodSearch.Name = "btnClearGoodSearch"; - this.btnClearGoodSearch.Size = new System.Drawing.Size(36, 32); + this.btnClearGoodSearch.Size = new System.Drawing.Size(34, 32); this.btnClearGoodSearch.TabIndex = 26; - this.btnClearGoodSearch.UseVisualStyleBackColor = false; this.btnClearGoodSearch.Click += new System.EventHandler(this.btnClearGoodSearch_Click); // // btnClearOrderNumberFilter // this.btnClearOrderNumberFilter.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.btnClearOrderNumberFilter.BackColor = System.Drawing.Color.Firebrick; - this.btnClearOrderNumberFilter.FlatAppearance.BorderSize = 0; - this.btnClearOrderNumberFilter.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnClearOrderNumberFilter.Font = new System.Drawing.Font("Segoe UI Semibold", 7.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204))); - this.btnClearOrderNumberFilter.Image = ((System.Drawing.Image)(resources.GetObject("btnClearOrderNumberFilter.Image"))); this.btnClearOrderNumberFilter.Location = new System.Drawing.Point(540, 48); this.btnClearOrderNumberFilter.Margin = new System.Windows.Forms.Padding(4); this.btnClearOrderNumberFilter.Name = "btnClearOrderNumberFilter"; - this.btnClearOrderNumberFilter.Size = new System.Drawing.Size(36, 32); + this.btnClearOrderNumberFilter.Size = new System.Drawing.Size(34, 32); this.btnClearOrderNumberFilter.TabIndex = 28; - this.btnClearOrderNumberFilter.UseVisualStyleBackColor = false; this.btnClearOrderNumberFilter.Click += new System.EventHandler(this.btnClearOrderNumberFilter_Click); // // txtOrderNumberFilter @@ -384,42 +374,24 @@ // // btnAddComment // - this.btnAddComment.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; - this.btnAddComment.BackColor = System.Drawing.SystemColors.HotTrack; - this.btnAddComment.FlatAppearance.BorderSize = 0; - this.btnAddComment.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnAddComment.Font = new System.Drawing.Font("Segoe UI Semibold", 10.2F, System.Drawing.FontStyle.Bold); - this.btnAddComment.ForeColor = System.Drawing.Color.White; - this.btnAddComment.Image = ((System.Drawing.Image)(resources.GetObject("btnAddComment.Image"))); - this.btnAddComment.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.btnAddComment.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; this.btnAddComment.Location = new System.Drawing.Point(584, 46); this.btnAddComment.Margin = new System.Windows.Forms.Padding(4); this.btnAddComment.Name = "btnAddComment"; - this.btnAddComment.Size = new System.Drawing.Size(240, 36); + this.btnAddComment.Size = new System.Drawing.Size(200, 34); this.btnAddComment.TabIndex = 29; this.btnAddComment.Text = "КОММЕНТАРИЙ"; - this.btnAddComment.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - this.btnAddComment.UseVisualStyleBackColor = false; this.btnAddComment.Click += new System.EventHandler(this.btnAddComment_Click); // // btnDeleteOrder // - this.btnDeleteOrder.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; - this.btnDeleteOrder.BackColor = System.Drawing.Color.Firebrick; - this.btnDeleteOrder.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; - this.btnDeleteOrder.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnDeleteOrder.Font = new System.Drawing.Font("Segoe UI Semibold", 10.2F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204))); - this.btnDeleteOrder.ForeColor = System.Drawing.Color.White; - this.btnDeleteOrder.Image = ((System.Drawing.Image)(resources.GetObject("btnDeleteOrder.Image"))); - this.btnDeleteOrder.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.btnDeleteOrder.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; this.btnDeleteOrder.Location = new System.Drawing.Point(390, 6); this.btnDeleteOrder.Margin = new System.Windows.Forms.Padding(4); this.btnDeleteOrder.Name = "btnDeleteOrder"; - this.btnDeleteOrder.Size = new System.Drawing.Size(160, 36); + this.btnDeleteOrder.Size = new System.Drawing.Size(130, 34); this.btnDeleteOrder.TabIndex = 30; this.btnDeleteOrder.Text = "УДАЛИТЬ"; - this.btnDeleteOrder.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - this.btnDeleteOrder.UseVisualStyleBackColor = false; this.btnDeleteOrder.Click += new System.EventHandler(this.btnDeleteOrder_Click); // // FOrders @@ -454,7 +426,7 @@ #endregion private Elfisa.UI.Controls.ModernButton btnClearSuppliers; - private System.Windows.Forms.ComboBox comboSuppliers; + private Elfisa.UI.Controls.ModernComboBox comboSuppliers; private System.Windows.Forms.Panel panelOrderItems; private System.Windows.Forms.Label label3; private System.Windows.Forms.Panel panelOrdersHeader; diff --git a/src/ElectronicPharmacy/UserControls/FOrders.cs b/src/ElectronicPharmacy/UserControls/FOrders.cs index add1943..73cc359 100644 --- a/src/ElectronicPharmacy/UserControls/FOrders.cs +++ b/src/ElectronicPharmacy/UserControls/FOrders.cs @@ -31,6 +31,8 @@ namespace Электронная_Фармация.UserControls { UiThemeHelper.ApplyToControlTree(this); Tag = "chrome-child"; + panelToolbar.Tag = "toolbar"; + panelOrdersHeader.Padding = new Padding(8, 14, 8, 6); dtpDateFrom.Value = DateTime.Now.AddDays(-3); dtpDateTo.Value = DateTime.Now; @@ -45,16 +47,15 @@ namespace Электронная_Фармация.UserControls panelToolbar.Resize += (_, __) => ApplyToolbarLayout(); } - private ComboBox _comboOrderStatus; - private ComboBox _comboConsignees; + private ModernComboBox _comboOrderStatus; + private ModernComboBox _comboConsignees; private ModernButton _btnSendOrder; private void InitOrderToolbar() { - _comboOrderStatus = new ComboBox + _comboOrderStatus = new ModernComboBox { - DropDownStyle = ComboBoxStyle.DropDownList, - Font = new Font("Segoe UI", 9.75f), + Font = new Font("Segoe UI", 11.25f), Name = "comboOrderStatus" }; _comboOrderStatus.Items.AddRange(new object[] { "Все статусы", "НОВЫЙ", "ОТПРАВЛЕН" }); @@ -62,10 +63,9 @@ namespace Электронная_Фармация.UserControls _comboOrderStatus.SelectionChangeCommitted += (_, __) => ApplyOrderFilters(); panelToolbar.Controls.Add(_comboOrderStatus); - _comboConsignees = new ComboBox + _comboConsignees = new ModernComboBox { - DropDownStyle = ComboBoxStyle.DropDownList, - Font = new Font("Segoe UI", 9.75f), + Font = new Font("Segoe UI", 11.25f), Name = "comboConsigneesFilter" }; _comboConsignees.SelectionChangeCommitted += (_, __) => ApplyOrderFilters(); @@ -74,25 +74,28 @@ namespace Электронная_Фармация.UserControls _btnSendOrder = new ModernButton { Text = "ОТПРАВИТЬ", - Font = new Font("Segoe UI Semibold", 10f, FontStyle.Bold), - BackColor = Color.FromArgb(0, 120, 212), - ForeColor = Color.White, - FlatStyle = FlatStyle.Flat, Name = "btnSendOrder" }; - _btnSendOrder.FlatAppearance.BorderSize = 0; _btnSendOrder.Click += async (_, __) => await SendSelectedOrderAsync(); panelToolbar.Controls.Add(_btnSendOrder); + + UiThemeHelper.ApplyToControlTree(_comboOrderStatus); + UiThemeHelper.ApplyToControlTree(_comboConsignees); + UiThemeHelper.ApplyToControlTree(_btnSendOrder); } private void ApplyToolbarLayout() { - const int pad = 10; + const int pad = 12; const int gap = 8; - const int row1Y = 8; - const int row2Y = 50; + const int row1Y = 10; const int controlHeight = 32; + const int filterHeight = 33; const int buttonHeight = 34; + const int rowGap = 14; + const int row2Y = row1Y + buttonHeight + rowGap; + const int commentWidth = 200; + const int filterY = row1Y + (buttonHeight - filterHeight) / 2; int x = pad; @@ -112,13 +115,13 @@ namespace Электронная_Фармация.UserControls _btnSendOrder.SetBounds(right - 150, row1Y, 150, buttonHeight); right = _btnSendOrder.Left - gap; - _comboConsignees.SetBounds(right - 190, row1Y, 190, controlHeight); + _comboConsignees.SetBounds(right - 190, filterY, 190, filterHeight); right = _comboConsignees.Left - gap; - comboSuppliers.SetBounds(right - 170, row1Y, 170, controlHeight); + comboSuppliers.SetBounds(right - 170, filterY, 170, filterHeight); right = comboSuppliers.Left - gap; - _comboOrderStatus.SetBounds(right - 150, row1Y, 150, controlHeight); + _comboOrderStatus.SetBounds(right - 150, filterY, 150, filterHeight); x = pad; txtGoodFromOrders.SetBounds(x, row2Y, 220, controlHeight); @@ -130,10 +133,12 @@ namespace Электронная_Фармация.UserControls btnClearOrderNumberFilter.SetBounds(x, row2Y, 34, controlHeight); x = btnClearOrderNumberFilter.Right + gap; - int commentWidth = Math.Max(180, panelToolbar.ClientSize.Width - x - pad); btnAddComment.SetBounds(x, row2Y, commentWidth, buttonHeight); btnClearSuppliers.Visible = false; + + panelToolbar.Height = row2Y + buttonHeight + pad + 8; + panelToolbar.Padding = new Padding(pad, 10, pad, 14); } private void ApplyOrderFilters() @@ -166,6 +171,14 @@ namespace Электронная_Фармация.UserControls parts.Add($"ConsigneeName = '{consignee}'"); } + if (_comboConsignees != null && + _comboConsignees.SelectedIndex > 0 && + !string.IsNullOrWhiteSpace(_comboConsignees.Text)) + { + var consignee = _comboConsignees.Text.Replace("'", "''"); + parts.Add($"ConsigneeName = '{consignee}'"); + } + var addon = parts.Count == 0 ? string.Empty : " and " + string.Join(" and ", parts); LoadOrders( dtpDateFrom.Value.ToString("dd.MM.yyyy"), @@ -241,6 +254,10 @@ and OrderDate <= '{dateTo}' { tableOrders.Columns[9].ColumnName = "Грузополучатель"; } + if (tableOrders.Columns.Count > 9) + { + tableOrders.Columns[9].ColumnName = "Грузополучатель"; + } //DGVOrders.DataSource = tableOrders; diff --git a/src/ElectronicPharmacy/UserControls/UCOrderItems.Designer.cs b/src/ElectronicPharmacy/UserControls/UCOrderItems.Designer.cs index 66740fc..80cfc67 100644 --- a/src/ElectronicPharmacy/UserControls/UCOrderItems.Designer.cs +++ b/src/ElectronicPharmacy/UserControls/UCOrderItems.Designer.cs @@ -83,38 +83,26 @@ // // btnEraseOrder // - this.btnEraseOrder.BackColor = System.Drawing.Color.Firebrick; this.btnEraseOrder.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.btnEraseOrder.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold); - this.btnEraseOrder.ForeColor = System.Drawing.Color.White; - this.btnEraseOrder.Image = ((System.Drawing.Image)(resources.GetObject("btnEraseOrder.Image"))); - this.btnEraseOrder.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; this.btnEraseOrder.Location = new System.Drawing.Point(272, 36); this.btnEraseOrder.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.btnEraseOrder.Name = "btnEraseOrder"; this.btnEraseOrder.Size = new System.Drawing.Size(260, 38); this.btnEraseOrder.TabIndex = 13; this.btnEraseOrder.Text = "Очистить корзину"; - this.btnEraseOrder.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - this.btnEraseOrder.UseVisualStyleBackColor = false; this.btnEraseOrder.Click += new System.EventHandler(this.btnEraseOrder_Click); // // btnSendOrder // - this.btnSendOrder.BackColor = System.Drawing.SystemColors.HotTrack; this.btnSendOrder.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.btnSendOrder.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold); - this.btnSendOrder.ForeColor = System.Drawing.Color.White; - this.btnSendOrder.Image = ((System.Drawing.Image)(resources.GetObject("btnSendOrder.Image"))); - this.btnSendOrder.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; this.btnSendOrder.Location = new System.Drawing.Point(4, 36); this.btnSendOrder.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.btnSendOrder.Name = "btnSendOrder"; this.btnSendOrder.Size = new System.Drawing.Size(260, 38); this.btnSendOrder.TabIndex = 12; this.btnSendOrder.Text = "Сохранить заказ"; - this.btnSendOrder.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - this.btnSendOrder.UseVisualStyleBackColor = false; this.btnSendOrder.Click += new System.EventHandler(this.btnSendOrder_Click); // // button1 diff --git a/src/ElectronicPharmacy/UserControls/UCPriceList.Designer.cs b/src/ElectronicPharmacy/UserControls/UCPriceList.Designer.cs index 6ce1b52..50322c0 100644 --- a/src/ElectronicPharmacy/UserControls/UCPriceList.Designer.cs +++ b/src/ElectronicPharmacy/UserControls/UCPriceList.Designer.cs @@ -164,19 +164,13 @@ // // btnDeleteGood // - this.btnDeleteGood.BackColor = System.Drawing.Color.Firebrick; - this.btnDeleteGood.FlatAppearance.BorderSize = 0; this.btnDeleteGood.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.btnDeleteGood.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold); - this.btnDeleteGood.ForeColor = System.Drawing.Color.White; - this.btnDeleteGood.Image = ((System.Drawing.Image)(resources.GetObject("btnDeleteGood.Image"))); - this.btnDeleteGood.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; this.btnDeleteGood.Location = new System.Drawing.Point(8, 62); this.btnDeleteGood.Name = "btnDeleteGood"; this.btnDeleteGood.Size = new System.Drawing.Size(360, 34); this.btnDeleteGood.TabIndex = 13; this.btnDeleteGood.Text = "Удалить товар (BACKSPACE)"; - this.btnDeleteGood.UseVisualStyleBackColor = false; this.btnDeleteGood.Click += new System.EventHandler(this.btnDeleteGood_Click); // // checkUsePercentageAsDefault @@ -372,16 +366,12 @@ // btnClearSearchInPriceList // this.btnClearSearchInPriceList.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.btnClearSearchInPriceList.BackColor = System.Drawing.Color.Firebrick; - this.btnClearSearchInPriceList.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("btnClearSearchInPriceList.BackgroundImage"))); - this.btnClearSearchInPriceList.FlatAppearance.BorderSize = 0; - this.btnClearSearchInPriceList.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.btnClearSearchInPriceList.Font = new System.Drawing.Font("Segoe UI", 9.75F); this.btnClearSearchInPriceList.Location = new System.Drawing.Point(393, 7); this.btnClearSearchInPriceList.Name = "btnClearSearchInPriceList"; this.btnClearSearchInPriceList.Size = new System.Drawing.Size(32, 30); this.btnClearSearchInPriceList.TabIndex = 9; - this.btnClearSearchInPriceList.UseVisualStyleBackColor = false; + this.btnClearSearchInPriceList.Text = "×"; this.btnClearSearchInPriceList.Click += new System.EventHandler(this.btnClearSearchInPriceList_Click); // // btnImport @@ -443,15 +433,11 @@ // btnClearSupplierFilter // this.btnClearSupplierFilter.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.btnClearSupplierFilter.BackColor = System.Drawing.Color.Firebrick; - this.btnClearSupplierFilter.FlatAppearance.BorderSize = 0; - this.btnClearSupplierFilter.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.btnClearSupplierFilter.Font = new System.Drawing.Font("Segoe UI", 9.75F); this.btnClearSupplierFilter.Location = new System.Drawing.Point(818, 7); this.btnClearSupplierFilter.Name = "btnClearSupplierFilter"; this.btnClearSupplierFilter.Size = new System.Drawing.Size(32, 30); this.btnClearSupplierFilter.TabIndex = 15; - this.btnClearSupplierFilter.UseVisualStyleBackColor = false; this.btnClearSupplierFilter.Text = "×"; this.btnClearSupplierFilter.Click += new System.EventHandler(this.btnClearSupplierFilter_Click); // diff --git a/src/Elfisa.UI/Controls/ModernButton.cs b/src/Elfisa.UI/Controls/ModernButton.cs index c6bacb3..2e0213b 100644 --- a/src/Elfisa.UI/Controls/ModernButton.cs +++ b/src/Elfisa.UI/Controls/ModernButton.cs @@ -21,6 +21,7 @@ namespace Elfisa.UI.Controls private string _navIconGlyph; private string _iconGlyph; private bool _compactIconMode; + private bool _focused; private bool _allowTextEllipsis = true; private readonly AnimationTimer _animTimer; @@ -30,7 +31,6 @@ namespace Elfisa.UI.Controls set { _compactIconMode = value; - UpdateRoundedRegion(); Invalidate(); } } @@ -106,15 +106,13 @@ namespace Elfisa.UI.Controls SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | - ControlStyles.ResizeRedraw | - ControlStyles.SupportsTransparentBackColor, true); + ControlStyles.ResizeRedraw, true); FlatStyle = FlatStyle.Flat; FlatAppearance.BorderSize = 0; - UseVisualStyleBackColor = false; - BackColor = Color.Transparent; Cursor = Cursors.Hand; - Font = new Font("Segoe UI Semibold", 13f); - Height = 36; + Font = new Font("Segoe UI Semibold", 12f); + Height = ModernButtonStyles.DefaultHeight; + MinimumSize = new Size(64, ModernButtonStyles.DefaultHeight); Padding = new Padding(14, 6, 14, 6); TabStop = true; _animTimer = new AnimationTimer(v => { _hoverAnim = v; Invalidate(); }); @@ -125,7 +123,7 @@ namespace Elfisa.UI.Controls _theme = theme; if (_headerNavMode) { - Font = new Font("Segoe UI", 12f, FontStyle.Bold); + Font = new Font("Segoe UI", 11f, FontStyle.Bold); } else if (_compactIconMode) { @@ -135,32 +133,9 @@ namespace Elfisa.UI.Controls { Font = theme.FontSemibold14; } - UpdateRoundedRegion(); Invalidate(); } - protected override void OnResize(EventArgs e) - { - base.OnResize(e); - UpdateRoundedRegion(); - } - - private void UpdateRoundedRegion() - { - Region?.Dispose(); - Region = null; - - if (_headerNavMode || _topNavMode || _sidebarMode || _compactIconMode || Width <= 0 || Height <= 0) - { - return; - } - - var theme = _theme ?? ThemeManager.Colors; - var path = GraphicsExtensions.CreateRoundedRectangle(new Rectangle(0, 0, Width, Height), theme.CornerRadiusSmall); - Region = new Region(path); - path.Dispose(); - } - protected override void OnMouseEnter(EventArgs e) { _hover = true; @@ -201,13 +176,25 @@ namespace Elfisa.UI.Controls base.OnEnabledChanged(e); } + protected override void OnGotFocus(EventArgs e) + { + _focused = true; + Invalidate(); + base.OnGotFocus(e); + } + + protected override void OnLostFocus(EventArgs e) + { + _focused = false; + Invalidate(); + base.OnLostFocus(e); + } + protected override void Dispose(bool disposing) { if (disposing) { _animTimer.Dispose(); - Region?.Dispose(); - Region = null; } base.Dispose(disposing); } @@ -217,10 +204,11 @@ namespace Elfisa.UI.Controls var theme = _theme ?? ThemeManager.Colors; var g = pevent.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; - var inset = _pressed && !_headerNavMode ? 1 : 0; - var bounds = new Rectangle(inset, inset, Width - (inset * 2), Height - (inset * 2)); + var bounds = new Rectangle(0, 0, Width - 1, Height - 1); var radius = theme.CornerRadiusSmall; var baseColor = ResolveBaseColor(theme); var fill = baseColor; @@ -260,9 +248,25 @@ namespace Elfisa.UI.Controls fill = theme.SidebarItemHover; } } + else if (_buttonStyle == ModernButtonStyle.Subtle) + { + fill = Color.Transparent; + if (!Enabled) + { + fill = Color.Transparent; + } + else if (_pressed) + { + fill = GraphicsExtensions.Blend(theme.GridRowHover, theme.BorderSubtle, 0.35f); + } + else if (_hover) + { + fill = GraphicsExtensions.Blend(theme.GridRowHover, Color.Transparent, 1f - (_hoverAnim * 0.65f)); + } + } else if (!Enabled) { - fill = GraphicsExtensions.Blend(baseColor, theme.BorderSubtle, 0.45f); + fill = ApplyDisabledFill(theme, baseColor); } else if (_pressed) { @@ -278,39 +282,11 @@ namespace Elfisa.UI.Controls if (!_topNavMode) { - if (!_sidebarMode && !_headerNavMode && Enabled && IsSolidActionStyle(_buttonStyle) && !_pressed) + if (fill.A > 0) { - var shadowAlpha = (int)(14 + (_hoverAnim * 10)); - var shadowRect = new Rectangle(bounds.X, bounds.Y + 1, bounds.Width, bounds.Height - 1); - using (var shadowBrush = new SolidBrush(Color.FromArgb(shadowAlpha, 0, 0, 0))) - using (var clipPath = GraphicsExtensions.CreateRoundedRectangle(bounds, radius)) + using (var brush = new SolidBrush(fill)) { - var state = g.Save(); - g.SetClip(clipPath); - GraphicsExtensions.FillRoundedRectangle(g, shadowBrush, shadowRect, radius); - g.Restore(state); - } - } - - using (var brush = new SolidBrush(fill)) - { - GraphicsExtensions.FillRoundedRectangle(g, brush, bounds, radius); - } - - if (!_sidebarMode && !_headerNavMode && Enabled && IsSolidActionStyle(_buttonStyle)) - { - var glossRect = new Rectangle(bounds.X + 1, bounds.Y + 1, bounds.Width - 2, Math.Max(4, bounds.Height / 2)); - using (var glossBrush = new LinearGradientBrush( - glossRect, - Color.FromArgb(_pressed ? 0 : 42, 255, 255, 255), - Color.FromArgb(0, 255, 255, 255), - LinearGradientMode.Vertical)) - using (var clipPath = GraphicsExtensions.CreateRoundedRectangle(bounds, radius)) - { - var state = g.Save(); - g.SetClip(clipPath); - g.FillRectangle(glossBrush, glossRect); - g.Restore(state); + GraphicsExtensions.FillRoundedRectangle(g, brush, bounds, radius); } } } @@ -332,14 +308,39 @@ namespace Elfisa.UI.Controls } } } - else if ((_buttonStyle == ModernButtonStyle.Secondary || _buttonStyle == ModernButtonStyle.DangerSoft) && !_sidebarMode) + else if ((_buttonStyle == ModernButtonStyle.Secondary + || _buttonStyle == ModernButtonStyle.DangerSoft + || _buttonStyle == ModernButtonStyle.Subtle) && !_sidebarMode) { - var borderColor = _buttonStyle == ModernButtonStyle.DangerSoft - ? GraphicsExtensions.Blend(theme.Danger, Color.White, 0.65f) - : theme.Border; - using (var pen = new Pen(borderColor, 1f)) + if (_buttonStyle == ModernButtonStyle.Subtle && !_hover && !_pressed && !_focused) { - GraphicsExtensions.DrawRoundedRectangle(g, pen, bounds, radius); + // WinUI SubtleButton — no border at rest. + } + else + { + var borderColor = _buttonStyle == ModernButtonStyle.DangerSoft + ? GraphicsExtensions.Blend(theme.Danger, Color.White, 0.55f) + : _buttonStyle == ModernButtonStyle.Subtle + ? theme.Border + : theme.InputBorder; + if (_hover && Enabled) + { + borderColor = theme.InputBorderFocus; + } + + using (var pen = new Pen(borderColor, 1f)) + { + GraphicsExtensions.DrawRoundedRectangle(g, pen, bounds, radius); + } + } + } + + if (_focused && Enabled && ShowFocusCues && !_headerNavMode && !_topNavMode && !_sidebarMode) + { + var focusBounds = Rectangle.Inflate(bounds, -2, -2); + using (var pen = new Pen(theme.InputBorderFocus, 2f)) + { + GraphicsExtensions.DrawRoundedRectangle(g, pen, focusBounds, Math.Max(2, radius - 1)); } } @@ -349,6 +350,8 @@ namespace Elfisa.UI.Controls ? (_isActiveItem ? theme.SidebarItemActiveText : theme.SidebarItem) : _buttonStyle == ModernButtonStyle.DangerSoft ? theme.Danger + : _buttonStyle == ModernButtonStyle.Subtle + ? theme.TextPrimary : _buttonStyle == ModernButtonStyle.Secondary ? theme.TextPrimary : theme.TextOnAccent; @@ -360,7 +363,7 @@ namespace Elfisa.UI.Controls if (!Enabled) { - textColor = theme.TextMuted; + textColor = GraphicsExtensions.Blend(textColor, theme.TextMuted, 0.55f); } DrawActionButtonContent(g, bounds, textColor); @@ -404,7 +407,7 @@ namespace Elfisa.UI.Controls ? bounds.X + horizontalPad : bounds.X + horizontalPad + Math.Max(0, (innerWidth - contentWidth) / 2); - using (var iconFont = new Font("Segoe MDL2 Assets", 13f)) + using (var iconFont = FluentFonts.CreateIconFont(13f)) { var iconRect = new Rectangle(startX, bounds.Y, iconWidth, bounds.Height); TextRenderer.DrawText( @@ -448,44 +451,49 @@ namespace Elfisa.UI.Controls private void DrawHeaderNavContent(Graphics g, Rectangle bounds, ThemeColors theme) { - var iconFont = new Font("Segoe MDL2 Assets", 18f); - var textColor = Color.White; - var iconRect = new Rectangle(bounds.X, bounds.Y + 8, bounds.Width, 28); - var textRect = new Rectangle(bounds.X, bounds.Y + 34, bounds.Width, bounds.Height - 34); - - if (!string.IsNullOrEmpty(_navIconGlyph)) + using (var iconFont = FluentFonts.CreateIconFont(18f)) { + var textColor = Color.White; + var iconRect = new Rectangle(bounds.X, bounds.Y + 8, bounds.Width, 28); + var textRect = new Rectangle(bounds.X, bounds.Y + 34, bounds.Width, bounds.Height - 34); + + if (!string.IsNullOrEmpty(_navIconGlyph)) + { + TextRenderer.DrawText( + g, + _navIconGlyph, + iconFont, + iconRect, + textColor, + TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine); + } + TextRenderer.DrawText( g, - _navIconGlyph, - iconFont, - iconRect, + Text, + Font, + textRect, textColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine); } - - TextRenderer.DrawText( - g, - Text, - Font, - textRect, - textColor, - TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine); } - private static bool IsSolidActionStyle(ModernButtonStyle style) + private static Color ApplyDisabledFill(ThemeColors theme, Color baseColor) { - return style == ModernButtonStyle.Primary - || style == ModernButtonStyle.Success - || style == ModernButtonStyle.Danger - || style == ModernButtonStyle.Warning; + if (baseColor == theme.Surface || baseColor == theme.DangerSoft) + { + return GraphicsExtensions.Blend(baseColor, theme.BorderSubtle, 0.35f); + } + + return GraphicsExtensions.Blend(baseColor, theme.BorderSubtle, 0.55f); } private Color ResolveBaseColor(ThemeColors theme) { switch (_buttonStyle) { - case ModernButtonStyle.Secondary: return theme.AccentSecondary; + case ModernButtonStyle.Secondary: return theme.Surface; + case ModernButtonStyle.Subtle: return Color.Transparent; case ModernButtonStyle.Success: return theme.Success; case ModernButtonStyle.Warning: return theme.Warning; case ModernButtonStyle.Danger: return theme.Danger; @@ -497,6 +505,8 @@ namespace Elfisa.UI.Controls private static Color ResolveHoverColor(ThemeColors theme, Color baseColor) { 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.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); diff --git a/src/Elfisa.UI/Controls/ModernButtonStyle.cs b/src/Elfisa.UI/Controls/ModernButtonStyle.cs index 5ab3408..f7d1254 100644 --- a/src/Elfisa.UI/Controls/ModernButtonStyle.cs +++ b/src/Elfisa.UI/Controls/ModernButtonStyle.cs @@ -7,6 +7,7 @@ namespace Elfisa.UI.Controls Success, Warning, Danger, - DangerSoft + DangerSoft, + Subtle } } diff --git a/src/Elfisa.UI/Controls/ModernButtonStyles.cs b/src/Elfisa.UI/Controls/ModernButtonStyles.cs new file mode 100644 index 0000000..df7cf1b --- /dev/null +++ b/src/Elfisa.UI/Controls/ModernButtonStyles.cs @@ -0,0 +1,176 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using Elfisa.UI.Theming; + +namespace Elfisa.UI.Controls +{ + /// + /// WinUI 3–style presets for in WinForms. + /// + public static class ModernButtonStyles + { + public const int DefaultHeight = 32; + + public static void ApplyAuto(ModernButton button) + { + if (button == null || button.HeaderNavMode || button.TopNavMode || button.SidebarMode) + { + return; + } + + var text = (button.Text ?? string.Empty).Trim(); + if (IsClearButton(button, text)) + { + ApplyClear(button); + return; + } + + if (IsDangerAction(text)) + { + ApplyAction(button, ModernButtonStyle.Danger, ResolveIcon(text, "\uE74D")); + return; + } + + if (IsPrimaryAction(text)) + { + ApplyAction(button, ModernButtonStyle.Primary, ResolveIcon(text, "\uE725")); + return; + } + + ApplyAction(button, ModernButtonStyle.Secondary, ResolveIcon(text, null)); + } + + public static void ApplyAction(ModernButton button, ModernButtonStyle style, string iconGlyph = null) + { + if (button == null) + { + return; + } + + button.ButtonStyle = style; + button.CompactIconMode = false; + button.Image = null; + button.BackgroundImage = null; + button.UseVisualStyleBackColor = false; + button.AutoSizeMode = AutoSizeMode.GrowAndShrink; + button.MinimumSize = new Size(64, DefaultHeight); + if (button.Height < DefaultHeight) + { + button.Height = DefaultHeight; + } + + button.Padding = new Padding(14, 6, 14, 6); + button.Font = (ThemeManager.Colors?.FontSemibold14) ?? new Font("Segoe UI Semibold", 12f); + button.IconGlyph = iconGlyph; + } + + public static void ApplyClear(ModernButton button) + { + if (button == null) + { + return; + } + + button.ButtonStyle = ModernButtonStyle.DangerSoft; + 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.IconGlyph = null; + } + + private static bool IsClearButton(ModernButton button, string text) + { + return text == "×" + || text.Equals("X", StringComparison.OrdinalIgnoreCase) + || (button.Width <= 40 && button.Height <= 36 && text.Length <= 2); + } + + private static bool IsDangerAction(string text) + { + var lower = text.ToLowerInvariant(); + return lower.Contains("удал") + || lower.Contains("отмен") + || lower.Contains("очист") + || lower.Contains("delete") + || lower.Contains("cancel") + || lower.Contains("erase"); + } + + private static bool IsPrimaryAction(string text) + { + var lower = text.ToLowerInvariant(); + return lower.Contains("отправ") + || lower.Contains("сохран") + || lower.Contains("зарег") + || lower.Contains("выполн") + || lower.Contains("коммент") + || lower.Contains("подтвер") + || lower.Contains("send") + || 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/FluentFonts.cs b/src/Elfisa.UI/Helpers/FluentFonts.cs new file mode 100644 index 0000000..145c448 --- /dev/null +++ b/src/Elfisa.UI/Helpers/FluentFonts.cs @@ -0,0 +1,36 @@ +using System; +using System.Drawing; + +namespace Elfisa.UI.Helpers +{ + /// + /// Segoe Fluent Icons with MDL2 fallback (WinUI design guidance). + /// + public static class FluentFonts + { + private static readonly Lazy IconFamily = new Lazy(ResolveIconFamily); + + public static Font CreateIconFont(float size, FontStyle style = FontStyle.Regular) + { + return new Font(IconFamily.Value, size, style, GraphicsUnit.Point); + } + + private static FontFamily ResolveIconFamily() + { + if (FontFamilyExists("Segoe Fluent Icons")) + { + return new FontFamily("Segoe Fluent Icons"); + } + + return new FontFamily("Segoe MDL2 Assets"); + } + + private static bool FontFamilyExists(string familyName) + { + using (var font = new Font(familyName, 10f)) + { + return string.Equals(font.FontFamily.Name, familyName, StringComparison.OrdinalIgnoreCase); + } + } + } +} diff --git a/src/Elfisa.UI/Theming/FluentWin11Palette.cs b/src/Elfisa.UI/Theming/FluentWin11Palette.cs new file mode 100644 index 0000000..63d451c --- /dev/null +++ b/src/Elfisa.UI/Theming/FluentWin11Palette.cs @@ -0,0 +1,99 @@ +using System.Drawing; + +namespace Elfisa.UI.Theming +{ + /// + /// WinUI 3 / Fluent Design light tokens adapted for WinForms + /// (see microsoft/win-dev-skills winui-design skill). + /// + public sealed class FluentWin11Palette : IThemePalette + { + public ThemeVariant Variant => ThemeVariant.FluentWin11; + + public ThemeColors Colors { get; } = new ThemeColors + { + Name = "Fluent Win11", + Mode = ThemeMode.Light, + + // SolidBackgroundFillColorBase / Secondary + BackgroundPrimary = Color.FromArgb(243, 243, 243), + BackgroundSecondary = Color.FromArgb(249, 249, 249), + Surface = Color.White, + SurfaceElevated = Color.White, + + // ControlStrokeColorDefault / Divider + Border = Color.FromArgb(229, 229, 229), + BorderSubtle = Color.FromArgb(240, 240, 240), + + // TextFillColorPrimary / Secondary / Tertiary + TextPrimary = Color.FromArgb(26, 26, 26), + TextSecondary = Color.FromArgb(97, 97, 97), + TextMuted = Color.FromArgb(138, 138, 138), + TextOnAccent = Color.White, + + // AccentFillColorDefault (pharmacy green on Fluent chrome) + AccentPrimary = Color.FromArgb(15, 108, 189), + AccentPrimaryHover = Color.FromArgb(14, 94, 163), + AccentPrimaryPressed = Color.FromArgb(12, 80, 140), + AccentPrimarySoft = Color.FromArgb(235, 243, 252), + AccentPrimaryBorder = Color.FromArgb(199, 224, 244), + AccentSecondary = Color.FromArgb(16, 124, 16), + + Success = Color.FromArgb(16, 124, 16), + SuccessSoft = Color.FromArgb(223, 246, 221), + Warning = Color.FromArgb(157, 93, 0), + WarningSoft = Color.FromArgb(255, 244, 206), + Danger = Color.FromArgb(196, 43, 28), + DangerSoft = Color.FromArgb(253, 231, 233), + Info = Color.FromArgb(15, 108, 189), + InfoSoft = Color.FromArgb(235, 243, 252), + + // Commanding layer — light Mica-style header (not heavy gradient) + TitleBarTop = Color.FromArgb(249, 249, 249), + TitleBarBottom = Color.FromArgb(237, 237, 237), + NavBackground = Color.FromArgb(249, 249, 249), + CartHeaderBackground = Color.FromArgb(243, 243, 243), + ToolbarBackground = Color.FromArgb(249, 249, 249), + SectionHeaderBackground = Color.FromArgb(243, 243, 243), + + SidebarBackground = Color.FromArgb(249, 249, 249), + SidebarItem = Color.FromArgb(97, 97, 97), + SidebarItemHover = Color.FromArgb(243, 243, 243), + SidebarItemActive = Color.FromArgb(235, 243, 252), + SidebarItemActiveText = Color.FromArgb(15, 108, 189), + + GridHeaderBackground = Color.FromArgb(243, 243, 243), + GridHeaderForeground = Color.FromArgb(26, 26, 26), + GridRowBackground = Color.White, + GridRowAlternate = Color.FromArgb(249, 249, 249), + GridRowHover = Color.FromArgb(243, 243, 243), + GridRowSelected = Color.FromArgb(15, 108, 189), + GridRowSelectedText = Color.White, + GridBorder = Color.FromArgb(229, 229, 229), + + InputBackground = Color.White, + InputBorder = Color.FromArgb(229, 229, 229), + InputBorderFocus = Color.FromArgb(15, 108, 189), + + OverlayScrim = Color.FromArgb(120, 0, 0, 0), + Shadow = Color.FromArgb(24, 0, 0, 0), + ScrollbarThumb = Color.FromArgb(200, 200, 200), + ScrollbarTrack = Color.FromArgb(243, 243, 243), + + CornerRadiusSmall = 4, + CornerRadiusMedium = 8, + GridRowHeight = 36, + GridHeaderHeight = 40 + }; + + public FluentWin11Palette() + { + Colors.FontRegular12 = new Font("Segoe UI", 12f); + Colors.FontRegular13 = new Font("Segoe UI", 12f); + Colors.FontRegular14 = new Font("Segoe UI", 14f); + Colors.FontSemibold14 = new Font("Segoe UI Semibold", 14f); + Colors.FontSemibold16 = new Font("Segoe UI Semibold", 16f); + Colors.FontBold18 = new Font("Segoe UI Semibold", 18f); + } + } +} diff --git a/src/Elfisa.UI/Theming/ThemeManager.cs b/src/Elfisa.UI/Theming/ThemeManager.cs index 5f64372..5d6e702 100644 --- a/src/Elfisa.UI/Theming/ThemeManager.cs +++ b/src/Elfisa.UI/Theming/ThemeManager.cs @@ -12,10 +12,11 @@ namespace Elfisa.UI.Theming { ThemeVariant.ClinicalPro, new ClinicalProPalette() }, { ThemeVariant.LightOffice, new LightOfficePalette() }, { ThemeVariant.DarkRider, new DarkRiderPalette() }, - { ThemeVariant.DarkFluent, new DarkFluentPalette() } + { ThemeVariant.DarkFluent, new DarkFluentPalette() }, + { ThemeVariant.FluentWin11, new FluentWin11Palette() } }; - private static ThemeVariant _currentVariant = ThemeVariant.PharmaTrust; + private static ThemeVariant _currentVariant = ThemeVariant.FluentWin11; public static event EventHandler ThemeChanged; @@ -39,7 +40,7 @@ namespace Elfisa.UI.Theming public static ThemeMode CurrentMode => Colors.Mode; public static IReadOnlyList AvailableVariants { get; } = - new[] { ThemeVariant.PharmaTrust, ThemeVariant.Apotheca, ThemeVariant.ClinicalPro, ThemeVariant.LightOffice, ThemeVariant.DarkRider, ThemeVariant.DarkFluent }; + new[] { ThemeVariant.PharmaTrust, ThemeVariant.Apotheca, ThemeVariant.ClinicalPro, ThemeVariant.LightOffice, ThemeVariant.DarkRider, ThemeVariant.DarkFluent, ThemeVariant.FluentWin11 }; public static string GetVariantDisplayName(ThemeVariant variant) { diff --git a/src/Elfisa.UI/Theming/ThemeRenderer.cs b/src/Elfisa.UI/Theming/ThemeRenderer.cs index bc59063..ff02593 100644 --- a/src/Elfisa.UI/Theming/ThemeRenderer.cs +++ b/src/Elfisa.UI/Theming/ThemeRenderer.cs @@ -1,6 +1,5 @@ using System.Windows.Forms; using Elfisa.UI.Controls; -using Elfisa.UI.Loading; namespace Elfisa.UI.Theming { @@ -26,6 +25,10 @@ namespace Elfisa.UI.Theming else if (control is ModernButton button) { button.ApplyTheme(colors); + if (!button.HeaderNavMode && !button.TopNavMode && !button.SidebarMode) + { + ModernButtonStyles.ApplyAuto(button); + } } else if (control is ModernDataGridView grid) { @@ -87,14 +90,14 @@ namespace Elfisa.UI.Theming { uploadPanel.ApplyTheme(colors); } - else if (control is LoadingOverlay loadingOverlay) - { - loadingOverlay.ApplyTheme(colors); - } else if (control.Tag as string == "toolbar") { control.BackColor = colors.ToolbarBackground; } + else if (control.Tag as string == "status-footer") + { + control.BackColor = colors.Surface; + } else if (control is Panel || control is UserControl || control is TableLayoutPanel || control is FlowLayoutPanel) { if (control.Tag as string == "chrome-child" || control.Tag as string == "keep-bg") diff --git a/src/Elfisa.UI/Theming/ThemeVariant.cs b/src/Elfisa.UI/Theming/ThemeVariant.cs index 644cbed..01ce683 100644 --- a/src/Elfisa.UI/Theming/ThemeVariant.cs +++ b/src/Elfisa.UI/Theming/ThemeVariant.cs @@ -7,7 +7,8 @@ namespace Elfisa.UI.Theming ClinicalPro = 2, LightOffice = 3, DarkRider = 4, - DarkFluent = 5 + DarkFluent = 5, + FluentWin11 = 6 } public enum ThemeMode