Polish cart tabs and dialog chrome for light/dark themes.

Add ModernTabControl/SplitContainer for cart UI without system borders, fix button corner artifacts and dialog text contrast, and use TitleBarTop green for auth header in light mode.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Magomed 2026-07-14 20:32:54 +03:00
parent 7ed8822bd4
commit 5cc202dea7
13 changed files with 699 additions and 59 deletions

View File

@ -91,6 +91,14 @@ namespace Электронная_Фармация.Classes
label.ForeColor = theme.TextPrimary; label.ForeColor = theme.TextPrimary;
label.Font = theme.FontRegular13; label.Font = theme.FontRegular13;
} }
else if (control is ModernTabControl modernTabControl)
{
modernTabControl.ApplyTheme(theme);
}
else if (control is ModernSplitContainer modernSplitContainer)
{
modernSplitContainer.ApplyTheme(theme);
}
else if (control is TabControl tabControl) else if (control is TabControl tabControl)
{ {
tabControl.BackColor = theme.BackgroundSecondary; tabControl.BackColor = theme.BackgroundSecondary;
@ -101,7 +109,13 @@ namespace Электронная_Фармация.Classes
toolStrip.BackColor = theme.TitleBarBottom; toolStrip.BackColor = theme.TitleBarBottom;
toolStrip.ForeColor = Color.White; toolStrip.ForeColor = Color.White;
} }
else if (control is Panel || control is UserControl || control is TableLayoutPanel || control is SplitContainer) else if (control is SplitContainer splitContainer)
{
splitContainer.BackColor = theme.Mode == ThemeMode.Dark ? theme.BorderSubtle : theme.Border;
splitContainer.Panel1.BackColor = theme.BackgroundPrimary;
splitContainer.Panel2.BackColor = theme.BackgroundPrimary;
}
else if (control is Panel || control is UserControl || control is TableLayoutPanel)
{ {
if (control.Tag as string == "keep-bg") if (control.Tag as string == "keep-bg")
{ {

View File

@ -11,6 +11,7 @@ using System.Data.SQLite;
using Электроннаяармация.UserControls; using Электроннаяармация.UserControls;
using System.Data.Common; using System.Data.Common;
using Электроннаяармация.Classes; using Электроннаяармация.Classes;
using Elfisa.UI.Theming;
namespace Электроннаяармация.UserControls namespace Электроннаяармация.UserControls
{ {
@ -223,6 +224,7 @@ and ConsigneesName = '{consigneeName}'
UiThemeHelper.ApplyToControlTree(this); UiThemeHelper.ApplyToControlTree(this);
_consigneeName = ParentForm?.ConsigneeName ?? string.Empty; _consigneeName = ParentForm?.ConsigneeName ?? string.Empty;
loadDataAboutOrderV2(_supplierName); loadDataAboutOrderV2(_supplierName);
dgvOrder.ApplyTheme(ThemeManager.Colors);
} }
private void btnEraseOrder_Click(object sender, EventArgs e) private void btnEraseOrder_Click(object sender, EventArgs e)

View File

@ -32,7 +32,7 @@
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UCPriceList)); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UCPriceList));
this.PContentPriceList = new System.Windows.Forms.Panel(); this.PContentPriceList = new System.Windows.Forms.Panel();
this.SplitPriceList = new System.Windows.Forms.SplitContainer(); this.SplitPriceList = new Elfisa.UI.Controls.ModernSplitContainer();
this.TLPPriceList = new System.Windows.Forms.TableLayoutPanel(); this.TLPPriceList = new System.Windows.Forms.TableLayoutPanel();
this.dgvPriceList = new Elfisa.UI.Controls.ModernDataGridView(); this.dgvPriceList = new Elfisa.UI.Controls.ModernDataGridView();
this.panel1 = new System.Windows.Forms.Panel(); this.panel1 = new System.Windows.Forms.Panel();
@ -45,7 +45,7 @@
this.label3 = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label();
this.POrderInfo = new System.Windows.Forms.Panel(); this.POrderInfo = new System.Windows.Forms.Panel();
this.dgvOrder = new Elfisa.UI.Controls.ModernDataGridView(); this.dgvOrder = new Elfisa.UI.Controls.ModernDataGridView();
this.tabOrderItems = new System.Windows.Forms.TabControl(); this.tabOrderItems = new Elfisa.UI.Controls.ModernTabControl();
this.lblSumOfOrder = new System.Windows.Forms.Label(); this.lblSumOfOrder = new System.Windows.Forms.Label();
this.btnEraseOrder = new Elfisa.UI.Controls.ModernButton(); this.btnEraseOrder = new Elfisa.UI.Controls.ModernButton();
this.btnSendOrder = new Elfisa.UI.Controls.ModernButton(); this.btnSendOrder = new Elfisa.UI.Controls.ModernButton();
@ -100,6 +100,7 @@
this.SplitPriceList.Panel2.Controls.Add(this.POrderInfo); this.SplitPriceList.Panel2.Controls.Add(this.POrderInfo);
this.SplitPriceList.Size = new System.Drawing.Size(1053, 532); this.SplitPriceList.Size = new System.Drawing.Size(1053, 532);
this.SplitPriceList.SplitterDistance = 290; this.SplitPriceList.SplitterDistance = 290;
this.SplitPriceList.SplitterWidth = 2;
this.SplitPriceList.TabIndex = 0; this.SplitPriceList.TabIndex = 0;
// //
// TLPPriceList // TLPPriceList
@ -288,12 +289,13 @@
this.tabOrderItems.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.tabOrderItems.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.tabOrderItems.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabOrderItems.Font = new System.Drawing.Font("Segoe UI", 11.25F); this.tabOrderItems.Font = new System.Drawing.Font("Segoe UI", 11.25F);
this.tabOrderItems.Location = new System.Drawing.Point(2, 24); this.tabOrderItems.Location = new System.Drawing.Point(0, 0);
this.tabOrderItems.Margin = new System.Windows.Forms.Padding(2); this.tabOrderItems.Margin = new System.Windows.Forms.Padding(2);
this.tabOrderItems.Name = "tabOrderItems"; this.tabOrderItems.Name = "tabOrderItems";
this.tabOrderItems.SelectedIndex = 0; this.tabOrderItems.SelectedIndex = 0;
this.tabOrderItems.Size = new System.Drawing.Size(1048, 215); this.tabOrderItems.Size = new System.Drawing.Size(1048, 238);
this.tabOrderItems.TabIndex = 11; this.tabOrderItems.TabIndex = 11;
// //
// lblSumOfOrder // lblSumOfOrder
@ -485,7 +487,7 @@
#endregion #endregion
private System.Windows.Forms.Panel PContentPriceList; private System.Windows.Forms.Panel PContentPriceList;
private System.Windows.Forms.SplitContainer SplitPriceList; private Elfisa.UI.Controls.ModernSplitContainer SplitPriceList;
private System.Windows.Forms.Panel POrderInfo; private System.Windows.Forms.Panel POrderInfo;
private Elfisa.UI.Controls.ModernTextBox txtSearchGoodNameInPriceList; private Elfisa.UI.Controls.ModernTextBox txtSearchGoodNameInPriceList;
private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label2;
@ -499,7 +501,7 @@
private System.Windows.Forms.NumericUpDown numericPercent; private System.Windows.Forms.NumericUpDown numericPercent;
private System.Windows.Forms.TableLayoutPanel TLPPriceList; private System.Windows.Forms.TableLayoutPanel TLPPriceList;
private Elfisa.UI.Controls.ModernTextBox txtConsignee; private Elfisa.UI.Controls.ModernTextBox txtConsignee;
private System.Windows.Forms.TabControl tabOrderItems; private Elfisa.UI.Controls.ModernTabControl tabOrderItems;
public Elfisa.UI.Controls.ModernDataGridView dgvPriceList; public Elfisa.UI.Controls.ModernDataGridView dgvPriceList;
private Elfisa.UI.Controls.ModernDataGridView dgvOrder; private Elfisa.UI.Controls.ModernDataGridView dgvOrder;
private System.Windows.Forms.Label lblSumOfOrder; private System.Windows.Forms.Label lblSumOfOrder;

View File

@ -326,18 +326,18 @@ namespace Электронная_Фармация.UserControls
} }
TabPage pageSupplier = new TabPage($"{tabName}"); TabPage pageSupplier = new TabPage($"{tabName}");
if (SupplierName == string.Empty)
{
pageSupplier.Tag = "cart";
}
UCOrderItems UCOrderItemsBySuppliers = new UCOrderItems(SupplierName); UCOrderItems UCOrderItemsBySuppliers = new UCOrderItems(SupplierName);
UCOrderItemsBySuppliers.ParentForm = this; UCOrderItemsBySuppliers.ParentForm = this;
UCOrderItemsBySuppliers.Dock = DockStyle.Fill;
pageSupplier.SuspendLayout();
pageSupplier.ResumeLayout();
tabOrderItems.TabPages.Add(pageSupplier);
pageSupplier.Controls.Add(UCOrderItemsBySuppliers); pageSupplier.Controls.Add(UCOrderItemsBySuppliers);
UCOrderItemsBySuppliers.Dock = DockStyle.Fill; tabOrderItems.TabPages.Add(pageSupplier);
tabOrderItems.SelectTab(pageSupplier); tabOrderItems.SelectTab(pageSupplier);
} }

View File

@ -83,6 +83,7 @@ namespace Elfisa.UI.Controls
set set
{ {
_buttonStyle = value; _buttonStyle = value;
ApplyRoundedRegion();
Invalidate(); Invalidate();
} }
} }
@ -114,7 +115,56 @@ namespace Elfisa.UI.Controls
return theme.BackgroundSecondary; return theme.BackgroundSecondary;
} }
return Parent?.BackColor ?? theme.ToolbarBackground; var current = Parent;
while (current != null)
{
if (current.Tag as string == "keep-bg")
{
current = current.Parent;
continue;
}
var backColor = current.BackColor;
if (backColor.A > 0 && backColor != Color.Transparent)
{
return backColor;
}
current = current.Parent;
}
return theme.Surface;
}
private int GetCornerRadius()
{
if (_headerNavMode || _topNavMode || _sidebarMode)
{
return 0;
}
if (SkillGradientButtonPaint.UsesGradient(_buttonStyle, _compactIconMode))
{
return SkillGradientButtonPaint.GetCornerRadius(_buttonStyle, _compactIconMode);
}
return 6;
}
private void ApplyRoundedRegion()
{
var radius = GetCornerRadius();
if (radius <= 0 || Width <= 0 || Height <= 0)
{
Region = null;
return;
}
var bounds = new Rectangle(0, 0, Width, Height);
using (var path = GraphicsExtensions.CreateRoundedRectangle(bounds, radius))
{
Region = new Region(path);
}
} }
public ModernButton() public ModernButton()
@ -157,6 +207,7 @@ namespace Elfisa.UI.Controls
BackColor = ResolveChromeBackground(); BackColor = ResolveChromeBackground();
} }
ApplyRoundedRegion();
Invalidate(); Invalidate();
} }
@ -200,6 +251,25 @@ namespace Elfisa.UI.Controls
base.OnEnabledChanged(e); base.OnEnabledChanged(e);
} }
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
ApplyRoundedRegion();
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
ApplyRoundedRegion();
}
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
ApplyRoundedRegion();
Invalidate();
}
protected override void OnGotFocus(EventArgs e) protected override void OnGotFocus(EventArgs e)
{ {
_focused = true; _focused = true;
@ -235,6 +305,14 @@ namespace Elfisa.UI.Controls
var bounds = new Rectangle(0, 0, Width, Height); var bounds = new Rectangle(0, 0, Width, Height);
var usesGradient = SkillGradientButtonPaint.UsesGradient(_buttonStyle, _compactIconMode); var usesGradient = SkillGradientButtonPaint.UsesGradient(_buttonStyle, _compactIconMode);
if (!_headerNavMode && !_topNavMode && !_sidebarMode)
{
using (var bgBrush = new SolidBrush(ResolveChromeBackground()))
{
g.FillRectangle(bgBrush, bounds);
}
}
if (_headerNavMode) if (_headerNavMode)
{ {
var headerBounds = new Rectangle(0, 0, Width - 1, Height - 1); var headerBounds = new Rectangle(0, 0, Width - 1, Height - 1);
@ -262,11 +340,6 @@ namespace Elfisa.UI.Controls
if (usesGradient && !_topNavMode && !_sidebarMode) if (usesGradient && !_topNavMode && !_sidebarMode)
{ {
using (var bgBrush = new SolidBrush(ResolveChromeBackground()))
{
g.FillRectangle(bgBrush, bounds);
}
SkillGradientButtonPaint.Paint( SkillGradientButtonPaint.Paint(
g, g,
bounds, bounds,
@ -288,7 +361,7 @@ namespace Elfisa.UI.Controls
} }
var flatBounds = new Rectangle(0, 0, Width - 1, Height - 1); var flatBounds = new Rectangle(0, 0, Width - 1, Height - 1);
var radius = _compactIconMode ? 6 : 6; var radius = GetCornerRadius();
var baseColor = ResolveBaseColor(theme); var baseColor = ResolveBaseColor(theme);
var fill = baseColor; var fill = baseColor;

View File

@ -23,17 +23,34 @@ namespace Elfisa.UI.Controls
AllowUserToResizeColumns = true; AllowUserToResizeColumns = true;
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None; AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single; ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.None;
DoubleBuffered = true; DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
CellFormatting += OnGridCellFormatting; CellFormatting += OnGridCellFormatting;
ApplyTheme(ThemeManager.Colors); ApplyTheme(ThemeManager.Colors);
} }
private void ApplyBorderStyles(ThemeColors theme)
{
GridColor = theme.GridBorder;
AdvancedCellBorderStyle.All = DataGridViewAdvancedCellBorderStyle.None;
AdvancedCellBorderStyle.Bottom = DataGridViewAdvancedCellBorderStyle.Single;
AdvancedColumnHeadersBorderStyle.All = DataGridViewAdvancedCellBorderStyle.None;
AdvancedColumnHeadersBorderStyle.Bottom = DataGridViewAdvancedCellBorderStyle.Single;
if (Rows.Count > 0)
{
AdvancedRowHeadersBorderStyle.All = DataGridViewAdvancedCellBorderStyle.None;
}
}
public void ApplyTheme(ThemeColors theme) public void ApplyTheme(ThemeColors theme)
{ {
_theme = theme; _theme = theme;
BackgroundColor = theme.GridRowBackground; BackgroundColor = theme.GridRowBackground;
GridColor = theme.GridBorder; ApplyBorderStyles(theme);
ColumnHeadersHeight = theme.GridHeaderHeight; ColumnHeadersHeight = theme.GridHeaderHeight;
RowTemplate.Height = theme.GridRowHeight; RowTemplate.Height = theme.GridRowHeight;
Font = theme.FontRegular13; Font = theme.FontRegular13;
@ -69,6 +86,24 @@ namespace Elfisa.UI.Controls
Invalidate(true); Invalidate(true);
} }
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var theme = _theme ?? ThemeManager.Colors;
using (var pen = new Pen(theme.GridBorder))
{
var rect = new Rectangle(0, 0, ClientSize.Width - 1, ClientSize.Height - 1);
e.Graphics.DrawRectangle(pen, rect);
}
}
protected override void OnRowsAdded(DataGridViewRowsAddedEventArgs e)
{
base.OnRowsAdded(e);
ApplyBorderStyles(_theme ?? ThemeManager.Colors);
}
private void OnGridCellFormatting(object sender, DataGridViewCellFormattingEventArgs e) private void OnGridCellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{ {
if (e.RowIndex < 0 || e.ColumnIndex < 0) if (e.RowIndex < 0 || e.ColumnIndex < 0)

View File

@ -0,0 +1,24 @@
using System.Drawing;
using System.Windows.Forms;
using Elfisa.UI.Theming;
namespace Elfisa.UI.Controls
{
public class ModernSplitContainer : SplitContainer
{
public ModernSplitContainer()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
BorderStyle = BorderStyle.None;
SplitterWidth = 2;
}
public void ApplyTheme(ThemeColors theme)
{
BackColor = theme.Mode == ThemeMode.Dark ? theme.Border : theme.Border;
Panel1.BackColor = theme.BackgroundPrimary;
Panel2.BackColor = theme.BackgroundPrimary;
Invalidate(true);
}
}
}

View File

@ -0,0 +1,389 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using Elfisa.UI.Helpers;
using Elfisa.UI.Theming;
namespace Elfisa.UI.Controls
{
/// <summary>
/// Вкладки в стиле документных. Контент показывается в Panel — без системной рамки TabControl.
/// </summary>
public class ModernTabControl : UserControl
{
private const int StripHeight = 40;
private const int TabTop = 6;
private const int TabHeight = 28;
private const int MinTabWidth = 120;
private const int TabGap = 8;
private const int ContentOverlap = 2;
private const int FramePadding = 1;
private readonly TabControl _inner;
private readonly Panel _contentFrame;
private readonly Panel _contentHost;
private readonly Panel _strip;
private readonly List<Rectangle> _tabHitAreas = new List<Rectangle>();
private ThemeColors _theme;
private int _hoverIndex = -1;
private TabPage _displayedPage;
private bool _isReparenting;
public TabControl.TabPageCollection TabPages => _inner.TabPages;
public int SelectedIndex
{
get => _inner.SelectedIndex;
set => _inner.SelectedIndex = value;
}
public int TabCount => _inner.TabCount;
public ModernTabControl()
{
SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw, true);
_inner = new TabControl
{
Visible = false,
Size = new Size(0, 0),
TabStop = false,
Tag = "keep-bg"
};
_inner.SelectedIndexChanged += (_, __) =>
{
DisplaySelectedTab();
_strip.Invalidate();
};
_inner.ControlAdded += OnInnerControlAdded;
_inner.ControlRemoved += OnInnerControlRemoved;
_contentHost = new Panel
{
Dock = DockStyle.Fill,
Tag = "keep-bg"
};
_contentFrame = new Panel
{
Dock = DockStyle.Fill,
Padding = new Padding(FramePadding),
Tag = "keep-bg"
};
_contentFrame.Controls.Add(_contentHost);
_strip = new Panel
{
Dock = DockStyle.Top,
Height = StripHeight,
Tag = "keep-bg"
};
_strip.Paint += OnStripPaint;
_strip.MouseMove += OnStripMouseMove;
_strip.MouseLeave += OnStripMouseLeave;
_strip.MouseClick += OnStripMouseClick;
Controls.Add(_inner);
Controls.Add(_contentFrame);
Controls.Add(_strip);
}
public void SelectTab(TabPage page)
{
var index = _inner.TabPages.IndexOf(page);
if (index < 0)
{
return;
}
if (_inner.SelectedIndex == index)
{
DisplaySelectedTab();
}
else
{
_inner.SelectedIndex = index;
}
}
public void ApplyTheme(ThemeColors theme)
{
_theme = theme;
BackColor = theme.BackgroundSecondary;
_strip.BackColor = theme.BackgroundSecondary;
_contentFrame.BackColor = theme.Border;
_contentHost.BackColor = theme.BackgroundPrimary;
SyncTabPageTheme();
_strip.Invalidate();
}
private void OnInnerControlAdded(object sender, ControlEventArgs e)
{
SyncTabPageTheme();
_strip.Invalidate();
}
private void OnInnerControlRemoved(object sender, ControlEventArgs e)
{
if (e.Control is TabPage page && page == _displayedPage)
{
_displayedPage = null;
}
if (_inner.TabCount == 0)
{
_displayedPage = null;
_contentHost.Controls.Clear();
}
_strip.Invalidate();
}
private void DisplaySelectedTab()
{
if (_isReparenting)
{
return;
}
_isReparenting = true;
try
{
ReturnControlsToTabPage(_displayedPage);
if (_inner.SelectedIndex < 0 || _inner.SelectedIndex >= _inner.TabCount)
{
_displayedPage = null;
_contentHost.Controls.Clear();
return;
}
_displayedPage = _inner.TabPages[_inner.SelectedIndex];
MoveControlsToHost(_displayedPage);
}
finally
{
_isReparenting = false;
}
}
private void ReturnControlsToTabPage(TabPage page)
{
if (page == null)
{
return;
}
var controls = new Control[_contentHost.Controls.Count];
_contentHost.Controls.CopyTo(controls, 0);
_contentHost.Controls.Clear();
foreach (var control in controls)
{
control.Dock = DockStyle.Fill;
page.Controls.Add(control);
}
}
private void MoveControlsToHost(TabPage page)
{
_contentHost.Controls.Clear();
var controls = new Control[page.Controls.Count];
page.Controls.CopyTo(controls, 0);
page.Controls.Clear();
foreach (var control in controls)
{
control.Dock = DockStyle.Fill;
_contentHost.Controls.Add(control);
}
}
private void OnStripPaint(object sender, PaintEventArgs e)
{
var theme = _theme ?? ThemeManager.Colors;
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
using (var bgBrush = new SolidBrush(theme.BackgroundSecondary))
{
g.FillRectangle(bgBrush, _strip.ClientRectangle);
}
_tabHitAreas.Clear();
if (_inner.TabCount == 0)
{
DrawStripSeparator(g, theme, null, _strip.Width);
return;
}
var x = 10;
Rectangle? activeRect = null;
using (var textFont = theme.FontSemibold14 ?? new Font("Segoe UI", 10f, FontStyle.Bold))
{
for (var i = 0; i < _inner.TabCount; i++)
{
var page = _inner.TabPages[i];
var textSize = TextRenderer.MeasureText(page.Text, textFont);
var tabWidth = Math.Max(MinTabWidth, textSize.Width + 28);
var tabRect = new Rectangle(x, TabTop, tabWidth, TabHeight);
_tabHitAreas.Add(tabRect);
var isActive = _inner.SelectedIndex == i;
var isHover = _hoverIndex == i;
if (isActive)
{
activeRect = new Rectangle(x, TabTop, tabWidth, StripHeight - TabTop + ContentOverlap);
}
else
{
var backColor = theme.Surface;
if (isHover)
{
backColor = GraphicsExtensions.Blend(backColor, theme.AccentPrimarySoft, 0.35f);
}
using (var brush = new SolidBrush(backColor))
{
GraphicsExtensions.FillRoundedRectangle(g, brush, tabRect, theme.CornerRadiusSmall);
}
using (var pen = new Pen(theme.BorderSubtle))
{
GraphicsExtensions.DrawRoundedRectangle(g, pen, tabRect, theme.CornerRadiusSmall);
}
TextRenderer.DrawText(
g,
page.Text,
textFont,
tabRect,
theme.TextSecondary,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
}
x += tabWidth + TabGap;
}
}
if (activeRect.HasValue)
{
var rect = activeRect.Value;
using (var brush = new SolidBrush(theme.BackgroundPrimary))
{
GraphicsExtensions.FillTopRoundedRectangle(g, brush, rect, theme.CornerRadiusSmall);
}
var outlineColor = theme.Mode == ThemeMode.Dark ? theme.BorderSubtle : theme.AccentPrimaryBorder;
using (var pen = new Pen(outlineColor, 1f))
{
GraphicsExtensions.DrawAttachedTabOutline(g, pen, rect, theme.CornerRadiusSmall);
}
using (var seam = new SolidBrush(theme.BackgroundPrimary))
{
g.FillRectangle(seam, rect.Left, StripHeight - ContentOverlap - 1, rect.Width, ContentOverlap + 2);
}
using (var textFont = theme.FontSemibold14 ?? new Font("Segoe UI", 10f, FontStyle.Bold))
{
var textRect = new Rectangle(rect.X, TabTop, rect.Width, TabHeight);
TextRenderer.DrawText(
g,
_inner.TabPages[_inner.SelectedIndex].Text,
textFont,
textRect,
theme.TextPrimary,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
}
}
DrawStripSeparator(g, theme, activeRect, _strip.Width);
}
private static void DrawStripSeparator(Graphics g, ThemeColors theme, Rectangle? activeRect, int width)
{
var y = StripHeight - 1;
using (var separator = new Pen(theme.Border))
{
if (activeRect.HasValue)
{
var rect = activeRect.Value;
if (rect.Left > 0)
{
g.DrawLine(separator, 0, y, rect.Left, y);
}
if (rect.Right < width)
{
g.DrawLine(separator, rect.Right, y, width, y);
}
}
else
{
g.DrawLine(separator, 0, y, width, y);
}
}
}
private void OnStripMouseMove(object sender, MouseEventArgs e)
{
var hoverIndex = HitTestTab(e.Location);
if (hoverIndex != _hoverIndex)
{
_hoverIndex = hoverIndex;
_strip.Invalidate();
}
_strip.Cursor = hoverIndex >= 0 ? Cursors.Hand : Cursors.Default;
}
private void OnStripMouseLeave(object sender, EventArgs e)
{
_hoverIndex = -1;
_strip.Cursor = Cursors.Default;
_strip.Invalidate();
}
private void OnStripMouseClick(object sender, MouseEventArgs e)
{
var index = HitTestTab(e.Location);
if (index >= 0)
{
SelectedIndex = index;
}
}
private int HitTestTab(Point location)
{
for (var i = 0; i < _tabHitAreas.Count; i++)
{
if (_tabHitAreas[i].Contains(location))
{
return i;
}
}
return -1;
}
private void SyncTabPageTheme()
{
var theme = _theme ?? ThemeManager.Colors;
foreach (TabPage page in _inner.TabPages)
{
page.BackColor = theme.BackgroundPrimary;
page.ForeColor = theme.TextPrimary;
}
_strip.Invalidate();
}
}
}

View File

@ -19,21 +19,21 @@ namespace Elfisa.UI.Dialogs
ShowInTaskbar = false; ShowInTaskbar = false;
Size = new Size(440, 220); Size = new Size(440, 220);
Padding = new Padding(20); Padding = new Padding(20);
BackColor = ThemeManager.Colors.Surface;
var titleLabel = new Label var titleLabel = new Label
{ {
Text = title, Text = title,
Dock = DockStyle.Top, Dock = DockStyle.Top,
Height = 34, Height = 34,
Font = ThemeManager.Colors.FontSemibold16 Font = ThemeManager.Colors.FontSemibold16,
BackColor = Color.Transparent
}; };
var messageLabel = new Label var messageLabel = new Label
{ {
Text = message, Text = message,
Dock = DockStyle.Fill, Dock = DockStyle.Fill,
Font = ThemeManager.Colors.FontRegular14, Font = ThemeManager.Colors.FontRegular14,
Tag = "muted" BackColor = Color.Transparent
}; };
var buttonPanel = new FlowLayoutPanel var buttonPanel = new FlowLayoutPanel
@ -63,11 +63,41 @@ namespace Elfisa.UI.Dialogs
Controls.Add(messageLabel); Controls.Add(messageLabel);
Controls.Add(buttonPanel); Controls.Add(buttonPanel);
Controls.Add(titleLabel); Controls.Add(titleLabel);
ApplyTheme(ThemeManager.Colors);
}
private void ApplyTheme(ThemeColors theme)
{
BackColor = theme.Surface;
ForeColor = theme.TextPrimary;
foreach (Control control in Controls)
{
if (control is Label label)
{
label.ForeColor = theme.TextPrimary;
label.BackColor = Color.Transparent;
}
else if (control is FlowLayoutPanel panel)
{
panel.BackColor = theme.Surface;
panel.ForeColor = theme.TextPrimary;
foreach (Control child in panel.Controls)
{
if (child is ModernButton button)
{
button.ApplyTheme(theme);
}
}
}
}
} }
private void AddButton(FlowLayoutPanel panel, string text, ModernButtonStyle style, DialogResult result) private void AddButton(FlowLayoutPanel panel, string text, ModernButtonStyle style, DialogResult result)
{ {
var button = new ModernButton { Text = text, Width = 100, ButtonStyle = style }; var button = new ModernButton { Text = text, Width = 100, ButtonStyle = style };
button.ApplyTheme(ThemeManager.Colors);
button.Click += (_, __) => button.Click += (_, __) =>
{ {
DialogResult = result; DialogResult = result;

View File

@ -29,6 +29,72 @@ namespace Elfisa.UI.Helpers
return path; return path;
} }
public static GraphicsPath CreateTopRoundedRectangle(Rectangle bounds, int radius)
{
var path = new GraphicsPath();
if (radius <= 0 || bounds.Width <= 0 || bounds.Height <= 0)
{
path.AddRectangle(bounds);
return path;
}
var diameter = radius * 2;
var arc = new Rectangle(bounds.X, bounds.Y, diameter, diameter);
path.AddArc(arc, 180, 90);
arc.X = bounds.Right - diameter;
path.AddArc(arc, 270, 90);
path.AddLine(bounds.Right, bounds.Y + radius, bounds.Right, bounds.Bottom);
path.AddLine(bounds.Right, bounds.Bottom, bounds.Left, bounds.Bottom);
path.AddLine(bounds.Left, bounds.Bottom, bounds.Left, bounds.Y + radius);
path.CloseFigure();
return path;
}
public static void FillTopRoundedRectangle(Graphics graphics, Brush brush, Rectangle bounds, int radius)
{
using (var path = CreateTopRoundedRectangle(bounds, radius))
{
graphics.FillPath(brush, path);
}
}
public static void DrawTopRoundedRectangle(Graphics graphics, Pen pen, Rectangle bounds, int radius)
{
using (var path = CreateTopRoundedRectangle(bounds, radius))
{
graphics.DrawPath(pen, path);
}
}
public static void DrawAttachedTabOutline(Graphics graphics, Pen pen, Rectangle bounds, int radius)
{
if (bounds.Width <= 0 || bounds.Height <= 0)
{
return;
}
if (radius <= 0)
{
graphics.DrawLine(pen, bounds.Left, bounds.Top, bounds.Right, bounds.Top);
graphics.DrawLine(pen, bounds.Left, bounds.Top, bounds.Left, bounds.Bottom);
graphics.DrawLine(pen, bounds.Right - 1, bounds.Top, bounds.Right - 1, bounds.Bottom);
return;
}
using (var path = new GraphicsPath())
{
var diameter = radius * 2;
var arc = new Rectangle(bounds.X, bounds.Y, diameter, diameter);
path.AddArc(arc, 180, 90);
arc.X = bounds.Right - diameter;
path.AddArc(arc, 270, 90);
path.AddLine(bounds.Right, bounds.Y + radius, bounds.Right, bounds.Bottom);
graphics.DrawPath(pen, path);
}
graphics.DrawLine(pen, bounds.Left, bounds.Y + radius, bounds.Left, bounds.Bottom);
}
public static void FillRoundedRectangle(Graphics graphics, Brush brush, Rectangle bounds, int radius) public static void FillRoundedRectangle(Graphics graphics, Brush brush, Rectangle bounds, int radius)
{ {
using (var path = CreateRoundedRectangle(bounds, radius)) using (var path = CreateRoundedRectangle(bounds, radius))

View File

@ -80,41 +80,31 @@ namespace Elfisa.UI.Helpers
clientRect.Width - 1, clientRect.Width - 1,
clientRect.Height - 1); clientRect.Height - 1);
g.SetClip(clientRect); using (var clipPath = GraphicsExtensions.CreateRoundedRectangle(buttonRect, radius))
if (!enabled)
{ {
using (var brush = new SolidBrush(Color.FromArgb(220, 230, 235))) g.SetClip(clipPath);
if (!enabled)
{ {
GraphicsExtensions.FillRoundedRectangle(g, brush, buttonRect, radius); using (var brush = new SolidBrush(Color.FromArgb(220, 230, 235)))
{
GraphicsExtensions.FillRoundedRectangle(g, brush, buttonRect, radius);
}
g.ResetClip();
return;
} }
return; GetGradient(style, hover, pressed, out var top, out var bottom);
}
// Лёгкая тень — один слой, без чёрной полосы снизу using (var path = GraphicsExtensions.CreateRoundedRectangle(buttonRect, radius))
var shadowRect = new Rectangle(buttonRect.X, buttonRect.Y + 1, buttonRect.Width, buttonRect.Height); using (var brush = new LinearGradientBrush(buttonRect, top, bottom, LinearGradientMode.Vertical))
using (var shadowPath = GraphicsExtensions.CreateRoundedRectangle(shadowRect, radius))
using (var shadowBrush = new SolidBrush(Color.FromArgb(28, 0, 0, 0)))
{
g.FillPath(shadowBrush, shadowPath);
}
var faceRect = new Rectangle(buttonRect.X, buttonRect.Y, buttonRect.Width, buttonRect.Height - 1);
GetGradient(style, hover, pressed, out var top, out var bottom);
using (var path = GraphicsExtensions.CreateRoundedRectangle(faceRect, radius))
using (var brush = new LinearGradientBrush(faceRect, top, bottom, LinearGradientMode.Vertical))
{
g.FillPath(brush, path);
using (var highlightPen = new Pen(Color.FromArgb(36, Color.White), 1f))
{ {
g.DrawArc(highlightPen, faceRect.X, faceRect.Y, radius * 2, radius * 2, 180, 90); g.FillPath(brush, path);
} }
}
g.ResetClip(); g.ResetClip();
}
} }
public static Rectangle GetContentRect(Rectangle clientRect, ModernButtonStyle style, bool compactIconMode, bool enabled) public static Rectangle GetContentRect(Rectangle clientRect, ModernButtonStyle style, bool compactIconMode, bool enabled)

View File

@ -46,8 +46,8 @@ namespace Elfisa.UI.Helpers
TrySetInt(form.Handle, DwmwaUseImmersiveDarkMode, useDarkMode); TrySetInt(form.Handle, DwmwaUseImmersiveDarkMode, useDarkMode);
TrySetInt(form.Handle, DwmwaUseImmersiveDarkModeLegacy, useDarkMode); TrySetInt(form.Handle, DwmwaUseImmersiveDarkModeLegacy, useDarkMode);
var caption = isDark ? theme.TitleBarTop : Color.White; var caption = theme.TitleBarTop;
var text = isDark ? Color.White : Color.Black; var text = IsLightCaption(caption) ? Color.Black : Color.White;
TrySetUInt(form.Handle, DwmwaCaptionColor, ColorToDwm(caption)); TrySetUInt(form.Handle, DwmwaCaptionColor, ColorToDwm(caption));
TrySetUInt(form.Handle, DwmwaTextColor, ColorToDwm(text)); TrySetUInt(form.Handle, DwmwaTextColor, ColorToDwm(text));
@ -62,18 +62,23 @@ namespace Elfisa.UI.Helpers
return; return;
} }
var isDark = theme.Mode == ThemeMode.Dark;
if (headerPanel != null) if (headerPanel != null)
{ {
headerPanel.BackColor = isDark ? theme.TitleBarTop : Color.White; headerPanel.BackColor = theme.TitleBarTop;
} }
if (headerLabel != null) if (headerLabel != null)
{ {
headerLabel.ForeColor = isDark ? Color.White : Color.Black; headerLabel.ForeColor = IsLightCaption(theme.TitleBarTop) ? Color.Black : Color.White;
} }
} }
private static bool IsLightCaption(Color color)
{
var luminance = (0.299 * color.R) + (0.587 * color.G) + (0.114 * color.B);
return luminance > 180;
}
public static void ApplyDialogChrome(Form form, Panel headerPanel = null, Label headerLabel = null) public static void ApplyDialogChrome(Form form, Panel headerPanel = null, Label headerLabel = null)
{ {
if (form == null) if (form == null)

View File

@ -97,6 +97,14 @@ namespace Elfisa.UI.Theming
{ {
sectionHeader.ApplyTheme(colors, sectionHeader.Tag as string == "cart"); sectionHeader.ApplyTheme(colors, sectionHeader.Tag as string == "cart");
} }
else if (control is ModernTabControl modernTabControl)
{
modernTabControl.ApplyTheme(colors);
}
else if (control is ModernSplitContainer modernSplitContainer)
{
modernSplitContainer.ApplyTheme(colors);
}
else if (control is ModernUploadZone uploadZone) else if (control is ModernUploadZone uploadZone)
{ {
uploadZone.ApplyTheme(colors); uploadZone.ApplyTheme(colors);
@ -140,7 +148,9 @@ namespace Elfisa.UI.Theming
} }
else if (control is SplitContainer splitContainer) else if (control is SplitContainer splitContainer)
{ {
splitContainer.BackColor = colors.BackgroundSecondary; splitContainer.BackColor = colors.Mode == ThemeMode.Dark ? colors.BorderSubtle : colors.Border;
splitContainer.Panel1.BackColor = colors.BackgroundPrimary;
splitContainer.Panel2.BackColor = colors.BackgroundPrimary;
} }
else if (control.Tag as string == "toolbar") else if (control.Tag as string == "toolbar")
{ {