// COPYRIGHT (C) Tom. ALL RIGHTS RESERVED. // THE AntdUI PROJECT IS AN WINFORM LIBRARY LICENSED UNDER THE Apache-2.0 License. // LICENSED UNDER THE Apache License, VERSION 2.0 (THE "License") // YOU MAY NOT USE THIS FILE EXCEPT IN COMPLIANCE WITH THE License. // YOU MAY OBTAIN A COPY OF THE LICENSE AT // // http://www.apache.org/licenses/LICENSE-2.0 // // UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, SOFTWARE // DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. // SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING PERMISSIONS AND // LIMITATIONS UNDER THE License. // GITEE: https://gitee.com/antdui/AntdUI // GITHUB: https://github.com/AntdUI/AntdUI // CSDN: https://blog.csdn.net/v_132 // QQ: 17379620 using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Design; using System.Windows.Forms; namespace AntdUI { partial class Tabs { /// /// 线条样式 /// public class StyleLine : IStyle { Tabs? owner; public StyleLine() { } public StyleLine(Tabs tabs) { owner = tabs; } int size = 3; /// /// 条大小 /// [Description("条大小"), Category("样式"), DefaultValue(3)] public int Size { get => size; set { if (size == value) return; size = value; owner?.LoadLayout(); } } int padding = 8; /// /// 条边距 /// [Description("条边距"), Category("样式"), DefaultValue(8)] public int Padding { get => padding; set { if (padding == value) return; padding = value; owner?.LoadLayout(); } } int radius = 0; /// /// 条圆角 /// [Description("条圆角"), Category("样式"), DefaultValue(0)] public int Radius { get => radius; set { if (radius == value) return; radius = value; owner?.Invalidate(); } } int backsize = 1; /// /// 条背景大小 /// [Description("条背景大小"), Category("样式"), DefaultValue(1)] public int BackSize { get => backsize; set { if (backsize == value) return; backsize = value; owner?.LoadLayout(); } } /// /// 条背景 /// [Description("条背景"), Category("样式"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? Back { get; set; } Rectangle rect_ful, rect_line_top; TabPageRect[] rects = new TabPageRect[0]; public void LoadLayout(Tabs tabs, Rectangle rect, TabCollection items) { rect_ful = rect; owner = tabs; rects = Helper.GDI(g => { int gap = (int)(tabs.Gap * Config.Dpi), gapI = gap / 2, xy = 0, xy2 = 0; int barSize = (int)(Size * Config.Dpi), barPadding = (int)(Padding * Config.Dpi), barPadding2 = barPadding * 2; var rect_list = new List(items.Count); var rect_dir = GetDir(tabs, g, items, gap, gapI, out int ico_size, out xy2); switch (tabs.Alignment) { case TabAlignment.Bottom: int y = rect.Bottom - xy2; foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it; if (it.Key.HasIcon) { rect_it = new Rectangle(rect.X + xy, y, it.Value.Width + gap + ico_size + gapI, xy2); rect_list.Add(new TabPageRect(rect_it, new Rectangle(rect_it.X + barPadding, rect_it.Y, rect_it.Width - barPadding2, barSize), it.Value, ico_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X + xy, y, it.Value.Width + gap, xy2); rect_list.Add(new TabPageRect(rect_it, new Rectangle(rect_it.X + barPadding, rect_it.Y, rect_it.Width - barPadding2, barSize))); } it.Key.SetRect(rect_it); xy += rect_it.Width; } else rect_list.Add(new TabPageRect()); } tabs.SetPadding(0, 0, 0, xy2); if (BackSize > 0) { int barBackSize = (int)(BackSize * Config.Dpi); rect_line_top = new Rectangle(rect.X, rect.Bottom - xy2, rect.Width, barBackSize); } owner.scroll_max = xy - rect.Width; owner.scroll_show = xy > rect.Width; break; case TabAlignment.Left: foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it = new Rectangle(rect.X, rect.Y + xy, xy2, it.Value.Height + gap); if (it.Key.HasIcon) rect_list.Add(new TabPageRect(rect_it, new Rectangle(rect_it.X + xy2 - barSize, rect_it.Y + barPadding, barSize, rect_it.Height - barPadding2), it.Value, ico_size, gap, gapI)); else rect_list.Add(new TabPageRect(rect_it, new Rectangle(rect_it.X + xy2 - barSize, rect_it.Y + barPadding, barSize, rect_it.Height - barPadding2))); it.Key.SetRect(rect_it); xy += rect_it.Height; } else rect_list.Add(new TabPageRect()); } tabs.SetPadding(xy2, 0, 0, 0); if (BackSize > 0) { int barBackSize = (int)(BackSize * Config.Dpi); rect_line_top = new Rectangle(rect.X + xy2 - barBackSize, rect.Y, barBackSize, rect.Height); } owner.scroll_max = xy - rect.Height; owner.scroll_show = xy > rect.Height; break; case TabAlignment.Right: int x = rect.Right - xy2; foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it = new Rectangle(x, rect.Y + xy, xy2, it.Value.Height + gap); if (it.Key.HasIcon) rect_list.Add(new TabPageRect(rect_it, new Rectangle(rect_it.X, rect_it.Y + barPadding, barSize, rect_it.Height - barPadding2), it.Value, ico_size, gap, gapI)); else rect_list.Add(new TabPageRect(rect_it, new Rectangle(rect_it.X, rect_it.Y + barPadding, barSize, rect_it.Height - barPadding2))); it.Key.SetRect(rect_it); xy += rect_it.Height; } else rect_list.Add(new TabPageRect()); } tabs.SetPadding(0, 0, xy2, 0); if (BackSize > 0) { int barBackSize = (int)(BackSize * Config.Dpi); rect_line_top = new Rectangle(x, rect.Y, barBackSize, rect.Height); } owner.scroll_max = xy - rect.Height; owner.scroll_show = xy > rect.Height; break; case TabAlignment.Top: default: foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it; if (it.Key.HasIcon) { rect_it = new Rectangle(rect.X + xy, rect.Y, it.Value.Width + gap + ico_size + gapI, xy2); rect_list.Add(new TabPageRect(rect_it, new Rectangle(rect_it.X + barPadding, rect_it.Bottom - barSize, rect_it.Width - barPadding2, barSize), it.Value, ico_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X + xy, rect.Y, it.Value.Width + gap, xy2); rect_list.Add(new TabPageRect(rect_it, new Rectangle(rect_it.X + barPadding, rect_it.Bottom - barSize, rect_it.Width - barPadding2, barSize))); } it.Key.SetRect(rect_it); xy += rect_it.Width; } else rect_list.Add(new TabPageRect()); } tabs.SetPadding(0, xy2, 0, 0); if (BackSize > 0) { int barBackSize = (int)(BackSize * Config.Dpi); rect_line_top = new Rectangle(rect.Left, rect.Y + xy2 - barBackSize, rect.Width, barBackSize); } owner.scroll_max = xy - rect.Width; owner.scroll_show = xy > rect.Width; break; } if (owner.scroll_show) owner.scroll_max += owner.SizeExceed(rect, rect_list[0].Rect, rect_list[rect_list.Count - 1].Rect); else { owner.scroll_x = owner.scroll_y = 0; if (tabs.centered) { switch (tabs.Alignment) { case TabAlignment.Left: case TabAlignment.Right: int oy = (rect.Height - xy) / 2; foreach (var item in rect_dir) item.Key.SetOffset(0, oy); foreach (var item in rect_list) { item.Rect.Offset(0, oy); item.Rect_Text.Offset(0, oy); item.Rect_Ico.Offset(0, oy); item.Rect_Line.Offset(0, oy); } break; case TabAlignment.Top: case TabAlignment.Bottom: default: int ox = (rect.Width - xy) / 2; foreach (var item in rect_dir) item.Key.SetOffset(ox, 0); foreach (var item in rect_list) { item.Rect.Offset(ox, 0); item.Rect_Text.Offset(ox, 0); item.Rect_Ico.Offset(ox, 0); item.Rect_Line.Offset(ox, 0); } break; } } } return rect_list.ToArray(); }); } public void Paint(Tabs owner, Graphics g, TabCollection items) { if (rects.Length > 0) { if (BackSize > 0) { using (var brush = new SolidBrush(Back ?? AntdUI.Style.Db.BorderSecondary)) { g.FillRectangle(brush, rect_line_top); } } if (owner.scroll_show) g.SetClip(owner.PaintExceedPre(rect_ful, rects[rects.Length - 1].Rect.Height)); else g.SetClip(rect_ful); using (var brush_fore = new SolidBrush(owner.ForeColor ?? AntdUI.Style.Db.Text)) using (var brush_fill = new SolidBrush(owner.Fill ?? AntdUI.Style.Db.Primary)) using (var brush_active = new SolidBrush(owner.FillActive ?? AntdUI.Style.Db.PrimaryActive)) using (var brush_hover = new SolidBrush(owner.FillHover ?? AntdUI.Style.Db.PrimaryHover)) { if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); if (AnimationBar) { PaintBar(g, AnimationBarValue, brush_fill); int i = 0; foreach (var page in items) { if (page.Visible) { if (owner.SelectedIndex == i) PaintText(g, rects[i], owner, page, brush_fill); else if (owner.hover_i == i) PaintText(g, rects[i], owner, page, brush_hover); else PaintText(g, rects[i], owner, page, brush_fore); } i++; } } else { int i = 0; foreach (var page in items) { if (page.Visible) { if (owner.SelectedIndex == i)//是否选中 { PaintBar(g, rects[i].Rect_Line, brush_fill); PaintText(g, rects[i], owner, page, brush_fill); } else if (owner.hover_i == i) PaintText(g, rects[i], owner, page, page.MDown ? brush_active : brush_hover); else PaintText(g, rects[i], owner, page, brush_fore); } i++; } } if (owner.scroll_show) owner.PaintExceed(g, brush_fore.Color, (int)(radius * Config.Dpi), rect_ful, rects[0].Rect, rects[rects.Length - 1].Rect, false); } } } #region 辅助 Dictionary GetDir(Tabs owner, Graphics g, TabCollection items, int gap, int gapI, out int ico_size, out int sizewh) { sizewh = 0; var size_t = g.MeasureString(Config.NullText, owner.Font).Size(); var rect_dir = new Dictionary(items.Count); foreach (var item in items) { var size = g.MeasureString(item.Text, owner.Font).Size(); rect_dir.Add(item, size); } ico_size = (int)(size_t.Height * owner.IconRatio); switch (owner.Alignment) { case TabAlignment.Left: case TabAlignment.Right: foreach (var it in rect_dir) { if (it.Key.Visible) { int w; if (it.Key.HasIcon) w = it.Value.Width + ico_size + gap + gapI; else w = it.Value.Width + gap; if (sizewh < w) sizewh = w; } } break; case TabAlignment.Top: case TabAlignment.Bottom: default: foreach (var it in rect_dir) { if (it.Key.Visible) { int h = it.Value.Height + gap; if (sizewh < h) sizewh = h; } } break; } return owner.HandItemSize(rect_dir, ref sizewh); } void PaintText(Graphics g, TabPageRect rects, Tabs owner, TabPage page, SolidBrush brush) { if (page.HasIcon) { if (page.Icon != null) g.DrawImage(page.Icon, rects.Rect_Ico); else if (page.IconSvg != null) g.GetImgExtend(page.IconSvg, rects.Rect_Ico, brush.Color); } g.DrawStr(page.Text, owner.Font, brush, rects.Rect_Text, owner.s_c); owner.PaintBadge(g, page, rects.Rect_Text); } void PaintBar(Graphics g, RectangleF rect, SolidBrush brush) { if (radius > 0) { using (var path = rect.RoundPath(radius * Config.Dpi)) { g.FillPath(brush, path); } } else g.FillRectangle(brush, rect); } void PaintBar(Graphics g, Rectangle rect, SolidBrush brush) { if (radius > 0) { using (var path = rect.RoundPath(radius * Config.Dpi)) { g.FillPath(brush, path); } } else g.FillRectangle(brush, rect); } internal class TabPageRect { public TabPageRect() { } public TabPageRect(Rectangle rect, Rectangle rect_line) { Rect = Rect_Text = rect; Rect_Line = rect_line; } public TabPageRect(Rectangle rect_it, Rectangle rect_line, Size size, int ico_size, int gap, int gapI) { Rect = rect_it; Rect_Line = rect_line; Rect_Text = new Rectangle(rect_it.X + ico_size + gapI, rect_it.Y, size.Width + gap, rect_it.Height); Rect_Ico = new Rectangle(rect_it.X + gapI, rect_it.Y + (rect_it.Height - ico_size) / 2, ico_size, ico_size); } public Rectangle Rect; public Rectangle Rect_Line; public Rectangle Rect_Text; public Rectangle Rect_Ico; } #endregion #region 动画 bool AnimationBar = false; RectangleF AnimationBarValue; ITask? ThreadBar = null; void SetRect(int old, int value) { if (owner == null || owner.items == null || rects.Length == 0) return; if (owner.items.ListExceed(value)) return; if (owner.items.ListExceed(old)) { AnimationBarValue = rects[value].Rect_Line; return; } ThreadBar?.Dispose(); Helper.GDI(g => { RectangleF OldValue = rects[old].Rect_Line, NewValue = rects[value].Rect_Line; if (AnimationBarValue.Height == 0) AnimationBarValue = OldValue; if (Config.Animation) { if (owner.alignment == TabAlignment.Left || owner.alignment == TabAlignment.Right) { if (OldValue.X == NewValue.X) { AnimationBarValue.X = OldValue.X; AnimationBar = true; float p_val = Math.Abs(NewValue.Y - AnimationBarValue.Y) * 0.09F, p_w_val = Math.Abs(NewValue.Height - AnimationBarValue.Height) * 0.1F, p_val2 = (NewValue.Y - AnimationBarValue.Y) * 0.5F; ThreadBar = new ITask(owner, () => { if (AnimationBarValue.Height != NewValue.Height) { if (NewValue.Height > OldValue.Height) { AnimationBarValue.Height += p_w_val; if (AnimationBarValue.Height > NewValue.Height) AnimationBarValue.Height = NewValue.Height; } else { AnimationBarValue.Height -= p_w_val; if (AnimationBarValue.Height < NewValue.Height) AnimationBarValue.Height = NewValue.Height; } } if (NewValue.Y > OldValue.Y) { if (AnimationBarValue.Y > p_val2) AnimationBarValue.Y += p_val / 2F; else AnimationBarValue.Y += p_val; if (AnimationBarValue.Y > NewValue.Y) { AnimationBarValue.Y = NewValue.Y; owner.Invalidate(); return false; } } else { AnimationBarValue.Y -= p_val; if (AnimationBarValue.Y < NewValue.Y) { AnimationBarValue.Y = NewValue.Y; owner.Invalidate(); return false; } } owner.Invalidate(); return true; }, 10, () => { AnimationBarValue = NewValue; AnimationBar = false; owner.Invalidate(); }); return; } } else { if (OldValue.Y == NewValue.Y) { AnimationBarValue.Y = OldValue.Y; AnimationBar = true; float p_val = Math.Abs(NewValue.X - AnimationBarValue.X) * 0.09F, p_w_val = Math.Abs(NewValue.Width - AnimationBarValue.Width) * 0.1F, p_val2 = (NewValue.X - AnimationBarValue.X) * 0.5F; ThreadBar = new ITask(owner, () => { if (AnimationBarValue.Width != NewValue.Width) { if (NewValue.Width > OldValue.Width) { AnimationBarValue.Width += p_w_val; if (AnimationBarValue.Width > NewValue.Width) AnimationBarValue.Width = NewValue.Width; } else { AnimationBarValue.Width -= p_w_val; if (AnimationBarValue.Width < NewValue.Width) AnimationBarValue.Width = NewValue.Width; } } if (NewValue.X > OldValue.X) { if (AnimationBarValue.X > p_val2) AnimationBarValue.X += p_val / 2F; else AnimationBarValue.X += p_val; if (AnimationBarValue.X > NewValue.X) { AnimationBarValue.X = NewValue.X; owner.Invalidate(); return false; } } else { AnimationBarValue.X -= p_val; if (AnimationBarValue.X < NewValue.X) { AnimationBarValue.X = NewValue.X; owner.Invalidate(); return false; } } owner.Invalidate(); return true; }, 10, () => { AnimationBarValue = NewValue; AnimationBar = false; owner.Invalidate(); }); return; } } } AnimationBarValue = NewValue; owner.Invalidate(); }); } #endregion public void SelectedIndexChanged(int i, int old) { SetRect(old, i); } public void Dispose() { ThreadBar?.Dispose(); } public void MouseWheel(int delta) { if (owner != null && owner.scroll_show) { switch (owner.Alignment) { case TabAlignment.Left: case TabAlignment.Right: owner.scroll_x = 0; owner.scroll_y -= delta; break; case TabAlignment.Top: case TabAlignment.Bottom: default: owner.scroll_y = 0; owner.scroll_x -= delta; break; } } } public void MouseMove(int x, int y) { } public bool MouseClick(TabPage page, int i, int x, int y) => false; public void MouseLeave() { } } /// /// 卡片样式 /// public class StyleCard : IStyle { Tabs? owner; public StyleCard() { } public StyleCard(Tabs tabs) { owner = tabs; } int radius = 6; /// /// 卡片圆角 /// [Description("卡片圆角"), Category("卡片"), DefaultValue(6)] public int Radius { get => radius; set { if (radius == value) return; radius = value; owner?.Invalidate(); } } int bordersize = 1; /// /// 边框大小 /// [Description("边框大小"), Category("卡片"), DefaultValue(1)] public int Border { get => bordersize; set { if (bordersize == value) return; bordersize = value; owner?.LoadLayout(); } } Color? border; /// /// 卡片边框颜色 /// [Description("卡片边框颜色"), Category("卡片"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? BorderColor { get => border; set { if (border == value) return; border = value; owner?.Invalidate(); } } /// /// 卡片边框激活颜色 /// [Description("卡片边框激活颜色"), Category("卡片"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? BorderActive { get; set; } Color? fill; /// /// 卡片颜色 /// [Description("卡片颜色"), Category("卡片"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? Fill { get => fill; set { if (fill == value) return; fill = value; owner?.Invalidate(); } } /// /// 卡片悬停颜色 /// [Description("卡片悬停颜色"), Category("卡片"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? FillHover { get; set; } /// /// 卡片激活颜色 /// [Description("卡片激活颜色"), Category("卡片"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? FillActive { get; set; } int gap = 2; /// /// 卡片间距 /// [Description("卡片间距"), Category("卡片"), DefaultValue(2)] public int Gap { get => gap; set { if (gap == value) return; gap = value; owner?.LoadLayout(); } } bool closable = false; /// /// 可关闭 /// [Description("可关闭"), Category("卡片"), DefaultValue(false)] public bool Closable { get => closable; set { if (closable == value) return; closable = value; owner?.LoadLayout(); } } TabPageRect[] rects = new TabPageRect[0]; public void LoadLayout(Tabs tabs, Rectangle rect, TabCollection items) { owner = tabs; rects = Helper.GDI(g => { int gap = (int)(tabs.Gap * Config.Dpi), gapI = gap / 2, xy = 0, xy2 = 0; int cardgap = (int)(Gap * Config.Dpi); var rect_list = new List(items.Count); var rect_dir = GetDir(tabs, g, items, gap, out int ico_size, out int close_size, out xy2); switch (tabs.Alignment) { case TabAlignment.Bottom: int y = rect.Bottom - xy2; foreach (var it in rect_dir) { if (it.Key.Visible) { bool close = closable && !it.Key.ReadOnly; Rectangle rect_it; if (close) { if (it.Key.HasIcon) { rect_it = new Rectangle(rect.X + xy, y, it.Value.Width + gap + ico_size + close_size + gap * 2, xy2); rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, close_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X + xy, y, it.Value.Width + gap + close_size + gap, xy2); rect_list.Add(new TabPageRect(rect_it, false, it.Value, close_size, gap, gapI)); } } else { if (it.Key.HasIcon) { rect_it = new Rectangle(rect.X + xy, y, it.Value.Width + gap + ico_size + gap, xy2); rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X + xy, y, it.Value.Width + gap, xy2); rect_list.Add(new TabPageRect(rect_it)); } } it.Key.SetRect(rect_it); xy += rect_it.Width + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(0, 0, 0, xy2); owner.scroll_max = xy - rect.Width; owner.scroll_show = xy > rect.Width; break; case TabAlignment.Left: foreach (var it in rect_dir) { if (it.Key.Visible) { bool close = closable && !it.Key.ReadOnly; Rectangle rect_it; if (close) { rect_it = new Rectangle(rect.X, rect.Y + xy, xy2, it.Value.Height + gap); if (it.Key.HasIcon) rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, close_size, gap, gapI)); else rect_list.Add(new TabPageRect(rect_it, false, it.Value, close_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X, rect.Y + xy, xy2, it.Value.Height + gap); if (it.Key.HasIcon) rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, gap, gapI)); else rect_list.Add(new TabPageRect(rect_it)); } it.Key.SetRect(rect_it); xy += rect_it.Height + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(xy2, 0, 0, 0); owner.scroll_max = xy - rect.Height; owner.scroll_show = xy > rect.Height; break; case TabAlignment.Right: int x = rect.Right - xy2; foreach (var it in rect_dir) { if (it.Key.Visible) { bool close = closable && !it.Key.ReadOnly; Rectangle rect_it; if (close) { rect_it = new Rectangle(x, rect.Y + xy, xy2, it.Value.Height + gap); if (it.Key.HasIcon) rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, close_size, gap, gapI)); else rect_list.Add(new TabPageRect(rect_it, false, it.Value, close_size, gap, gapI)); } else { rect_it = new Rectangle(x, rect.Y + xy, xy2, it.Value.Height + gap); if (it.Key.HasIcon) rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, gap, gapI)); else rect_list.Add(new TabPageRect(rect_it)); } it.Key.SetRect(rect_it); xy += rect_it.Height + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(0, 0, xy2, 0); owner.scroll_max = xy - rect.Height; owner.scroll_show = xy > rect.Height; break; case TabAlignment.Top: default: foreach (var it in rect_dir) { if (it.Key.Visible) { bool close = closable && !it.Key.ReadOnly; Rectangle rect_it; if (close) { if (it.Key.HasIcon) { rect_it = new Rectangle(rect.X + xy, rect.Y, it.Value.Width + gap + ico_size + close_size + gap * 2, xy2); rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, close_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X + xy, rect.Y, it.Value.Width + gap + close_size + gap, xy2); rect_list.Add(new TabPageRect(rect_it, false, it.Value, close_size, gap, gapI)); } } else { if (it.Key.HasIcon) { rect_it = new Rectangle(rect.X + xy, rect.Y, it.Value.Width + gap + ico_size + gap, xy2); rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X + xy, rect.Y, it.Value.Width + gap, xy2); rect_list.Add(new TabPageRect(rect_it)); } } it.Key.SetRect(rect_it); xy += rect_it.Width + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(0, xy2, 0, 0); owner.scroll_max = xy - rect.Width; owner.scroll_show = xy > rect.Width; break; } if (owner.scroll_show) owner.scroll_max += owner.SizeExceed(owner.ClientRectangle, rect_list[0].Rect, rect_list[rect_list.Count - 1].Rect); else { owner.scroll_x = owner.scroll_y = 0; if (tabs.centered) { switch (tabs.Alignment) { case TabAlignment.Left: case TabAlignment.Right: int oy = (rect.Height - xy) / 2; foreach (var item in rect_dir) item.Key.SetOffset(0, oy); foreach (var item in rect_list) { item.Rect.Offset(0, oy); item.Rect_Text.Offset(0, oy); item.Rect_Ico.Offset(0, oy); item.Rect_Close.Offset(0, oy); } break; case TabAlignment.Top: case TabAlignment.Bottom: default: int ox = (rect.Width - xy) / 2; foreach (var item in rect_dir) item.Key.SetOffset(ox, 0); foreach (var item in rect_list) { item.Rect.Offset(ox, 0); item.Rect_Text.Offset(ox, 0); item.Rect_Ico.Offset(ox, 0); item.Rect_Close.Offset(ox, 0); } break; } } } return rect_list.ToArray(); }); } public void Paint(Tabs owner, Graphics g, TabCollection items) { if (rects.Length == items.Count) { using (var brush_fore = new SolidBrush(owner.ForeColor ?? AntdUI.Style.Db.Text)) using (var brush_fill = new SolidBrush(owner.Fill ?? AntdUI.Style.Db.Primary)) using (var brush_active = new SolidBrush(owner.FillActive ?? AntdUI.Style.Db.PrimaryActive)) using (var brush_hover = new SolidBrush(owner.FillHover ?? AntdUI.Style.Db.PrimaryHover)) using (var brush_bg = new SolidBrush(Fill ?? AntdUI.Style.Db.FillQuaternary)) using (var brush_bg_hover = new SolidBrush(FillHover ?? AntdUI.Style.Db.FillQuaternary)) using (var brush_bg_active = new SolidBrush(FillActive ?? AntdUI.Style.Db.BgContainer)) { var rect_t = owner.ClientRectangle; int radius = (int)(Radius * Config.Dpi), bor = (int)(bordersize * Config.Dpi), bor2 = bor * 6, bor22 = bor2 * 2; float borb2 = bor / 2F; TabPage? sel = null; int i = 0, select = owner.SelectedIndex; switch (owner.Alignment) { case TabAlignment.Bottom: int read_b_h = rects[0].Rect.Height + rects[0].Rect.X; var rect_s_b = new Rectangle(rect_t.X, rect_t.Bottom - read_b_h, rect_t.Width, read_b_h); if (owner.scroll_show) { g.SetClip(owner.PaintExceedPre(rect_s_b, rects[0].Rect.Height)); g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); } else g.SetClip(rect_s_b); foreach (var page in items) { if (page.Visible) { if (select == i) sel = page; else { using (var path = Helper.RoundPath(page.Rect, radius, false, false, true, true)) { g.FillPath(owner.hover_i == i ? brush_bg_hover : brush_bg, path); if (bor > 0) { using (var pen_bg = new Pen(border ?? AntdUI.Style.Db.BorderSecondary, bor)) { g.DrawPath(pen_bg, path); } } if (owner.hover_i == i) PaintText(g, rects[i], owner, page, page.MDown ? brush_active : brush_hover); else PaintText(g, rects[i], owner, page, brush_fore); } } } i++; } g.ResetClip(); g.ResetTransform(); if (sel != null)//是否选中 { var rect_page = sel.Rect; using (var path = Helper.RoundPath(rect_page, radius, false, false, true, true)) { if (bor > 0) { using (var pen_bg = new Pen(BorderActive ?? AntdUI.Style.Db.BorderColor, bor)) { float ly = rect_page.Y + borb2; g.DrawLine(pen_bg, rect_t.X, ly, rect_t.Right, ly); if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); using (var path2 = Helper.RoundPath(new RectangleF(rect_page.X + borb2, rect_page.Y - borb2, rect_page.Width - bor, rect_page.Height + borb2), radius, false, false, true, true)) { g.FillPath(brush_bg_active, path2); } g.SetClip(new Rectangle(rect_page.X - bor, rect_page.Y + bor, rect_page.Width + bor2, rect_page.Height + bor)); g.DrawPath(pen_bg, path); g.ResetClip(); } } else { if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); g.FillPath(brush_bg_active, path); } PaintText(g, rects[select], owner, sel, brush_fill); } } break; case TabAlignment.Left: var rect_s_l = new Rectangle(rect_t.X, rect_t.Y, rects[0].Rect.Right, rect_t.Height); if (owner.scroll_show) { g.SetClip(owner.PaintExceedPre(rect_s_l, rects[0].Rect.Height)); g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); } else g.SetClip(rect_s_l); foreach (var page in items) { if (page.Visible) { if (owner.SelectedIndex == i) sel = page; else { using (var path = Helper.RoundPath(page.Rect, radius, true, false, false, true)) { g.FillPath(owner.hover_i == i ? brush_bg_hover : brush_bg, path); if (bor > 0) { using (var pen_bg = new Pen(border ?? AntdUI.Style.Db.BorderSecondary, bor)) { g.DrawPath(pen_bg, path); } } if (owner.hover_i == i) PaintText(g, rects[i], owner, page, page.MDown ? brush_active : brush_hover); else PaintText(g, rects[i], owner, page, brush_fore); } } } i++; } g.ResetClip(); g.ResetTransform(); if (sel != null)//是否选中 { var rect_page = sel.Rect; using (var path = Helper.RoundPath(rect_page, radius, true, false, false, true)) { if (bor > 0) { using (var pen_bg = new Pen(BorderActive ?? AntdUI.Style.Db.BorderColor, bor)) { float lx = rect_page.Right - borb2; g.DrawLine(pen_bg, lx, rect_t.Y, lx, rect_t.Bottom); if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); using (var path2 = Helper.RoundPath(new RectangleF(rect_page.X - borb2, rect_page.Y + borb2, rect_page.Width + borb2, rect_page.Height - bor), radius, true, false, false, true)) { g.FillPath(brush_bg_active, path2); } g.SetClip(new RectangleF(rect_page.X - borb2, rect_page.Y - bor, rect_page.Width, rect_page.Height + bor2)); g.DrawPath(pen_bg, path); g.ResetClip(); } } else { if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); g.FillPath(brush_bg_active, path); } PaintText(g, rects[select], owner, sel, brush_fill); } } break; case TabAlignment.Right: int read_r_w = rects[0].Rect.Width + rects[0].Rect.Y; var rect_s_r = new Rectangle(rect_t.Right - read_r_w, rect_t.Y, read_r_w, rect_t.Height); if (owner.scroll_show) { g.SetClip(owner.PaintExceedPre(rect_s_r, rects[0].Rect.Height)); g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); } else g.SetClip(rect_s_r); foreach (var page in items) { if (page.Visible) { if (owner.SelectedIndex == i) sel = page; else { using (var path = Helper.RoundPath(page.Rect, radius, false, true, true, false)) { g.FillPath(owner.hover_i == i ? brush_bg_hover : brush_bg, path); if (bor > 0) { using (var pen_bg = new Pen(border ?? AntdUI.Style.Db.BorderSecondary, bor)) { g.DrawPath(pen_bg, path); } } if (owner.hover_i == i) PaintText(g, rects[i], owner, page, page.MDown ? brush_active : brush_hover); else PaintText(g, rects[i], owner, page, brush_fore); } } } i++; } g.ResetClip(); g.ResetTransform(); if (sel != null)//是否选中 { var rect_page = sel.Rect; using (var path = Helper.RoundPath(rect_page, radius, false, true, true, false)) { if (bor > 0) { using (var pen_bg = new Pen(BorderActive ?? AntdUI.Style.Db.BorderColor, bor)) { float lx = rect_page.X + borb2; g.DrawLine(pen_bg, lx, rect_t.Y, lx, rect_t.Bottom); if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); using (var path2 = Helper.RoundPath(new RectangleF(rect_page.X - borb2, rect_page.Y + borb2, rect_page.Width + borb2, rect_page.Height - bor), radius, false, true, true, false)) { g.FillPath(brush_bg_active, path2); } g.SetClip(new Rectangle(rect_page.X + bor, rect_page.Y - bor, rect_page.Width + bor, rect_page.Height + bor2)); g.DrawPath(pen_bg, path); g.ResetClip(); } } else { if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); g.FillPath(brush_bg_active, path); } PaintText(g, rects[select], owner, sel, brush_fill); } } break; case TabAlignment.Top: default: var rect_s_t = new Rectangle(rect_t.X, rect_t.Y, rect_t.Width, rects[0].Rect.Bottom); if (owner.scroll_show) { g.SetClip(owner.PaintExceedPre(rect_s_t, rects[0].Rect.Height)); g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); } else g.SetClip(rect_s_t); foreach (var page in items) { if (page.Visible) { if (owner.SelectedIndex == i) sel = page; else { using (var path = Helper.RoundPath(page.Rect, radius, true, true, false, false)) { g.FillPath(owner.hover_i == i ? brush_bg_hover : brush_bg, path); if (bor > 0) { using (var pen_bg = new Pen(border ?? AntdUI.Style.Db.BorderSecondary, bor)) { g.DrawPath(pen_bg, path); } } if (owner.hover_i == i) PaintText(g, rects[i], owner, page, page.MDown ? brush_active : brush_hover); else PaintText(g, rects[i], owner, page, brush_fore); } } } i++; } g.ResetClip(); g.ResetTransform(); if (sel != null)//是否选中 { var rect_page = sel.Rect; using (var path = Helper.RoundPath(rect_page, radius, true, true, false, false)) { if (bor > 0) { using (var pen_bg = new Pen(BorderActive ?? AntdUI.Style.Db.BorderColor, bor)) { float ly = rect_page.Bottom - borb2; g.DrawLine(pen_bg, rect_t.X, ly, rect_t.Right, ly); if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); using (var path2 = Helper.RoundPath(new RectangleF(rect_page.X + borb2, rect_page.Y - borb2, rect_page.Width - bor, rect_page.Height + borb2), radius, true, true, false, false)) { g.FillPath(brush_bg_active, path2); } g.SetClip(new Rectangle(rect_page.X - bor, rect_page.Y - bor, rect_page.Width + bor2, rect_page.Height)); g.DrawPath(pen_bg, path); g.ResetClip(); } } else { if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); g.FillPath(brush_bg_active, path); } PaintText(g, rects[select], owner, sel, brush_fill); } } break; } if (owner.scroll_show) owner.PaintExceed(g, brush_fore.Color, radius, rect_t, rects[0].Rect, rects[rects.Length - 1].Rect, true); } } } #region 辅助 Dictionary GetDir(Tabs owner, Graphics g, TabCollection items, int gap, out int ico_size, out int close_size, out int sizewh) { sizewh = 0; var size_t = g.MeasureString(Config.NullText, owner.Font).Size(); var rect_dir = new Dictionary(items.Count); foreach (var item in items) { var size = g.MeasureString(item.Text, owner.Font).Size(); rect_dir.Add(item, size); } ico_size = (int)(size_t.Height * owner.IconRatio); close_size = (int)(size_t.Height * (owner.IconRatio * .8F)); switch (owner.Alignment) { case TabAlignment.Left: case TabAlignment.Right: if (closable) { foreach (var it in rect_dir) { if (it.Key.Visible) { int w; if (it.Key.HasIcon) w = it.Value.Width + ico_size + gap * 2; else w = it.Value.Width + gap; w += ico_size + gap; if (sizewh < w) sizewh = w; } } } else { foreach (var it in rect_dir) { if (it.Key.Visible) { int w; if (it.Key.HasIcon) w = it.Value.Width + ico_size + gap * 2; else w = it.Value.Width + gap; if (sizewh < w) sizewh = w; } } } break; case TabAlignment.Top: case TabAlignment.Bottom: default: foreach (var it in rect_dir) { if (it.Key.Visible) { int h = it.Value.Height + gap; if (sizewh < h) sizewh = h; } } break; } return owner.HandItemSize(rect_dir, ref sizewh); } void PaintText(Graphics g, TabPageRect rects, Tabs owner, TabPage page, SolidBrush brush) { if (page.HasIcon) { if (page.Icon != null) g.DrawImage(page.Icon, rects.Rect_Ico); else if (page.IconSvg != null) g.GetImgExtend(page.IconSvg, rects.Rect_Ico, brush.Color); } if (closable) { if (rects.hover_close == null) g.PaintIconClose(rects.Rect_Close, AntdUI.Style.Db.TextQuaternary); else if (rects.hover_close.Animation) g.PaintIconClose(rects.Rect_Close, Helper.ToColor(rects.hover_close.Value + AntdUI.Style.Db.TextQuaternary.A, AntdUI.Style.Db.Text)); else if (rects.hover_close.Switch) g.PaintIconClose(rects.Rect_Close, AntdUI.Style.Db.Text); else g.PaintIconClose(rects.Rect_Close, AntdUI.Style.Db.TextQuaternary); } g.DrawStr(page.Text, owner.Font, brush, rects.Rect_Text, owner.s_c); owner.PaintBadge(g, page, rects.Rect_Text); } internal class TabPageRect { public TabPageRect() { } public TabPageRect(Rectangle rect) { Rect = Rect_Text = rect; } public TabPageRect(Rectangle rect_it, Size size, int ico_size, int gap, int gapI) { Rect = rect_it; Rect_Text = new Rectangle(rect_it.X + ico_size + gap, rect_it.Y + gapI, size.Width + gap, rect_it.Height - gap); Rect_Ico = new Rectangle(rect_it.X + gap, rect_it.Y + (rect_it.Height - ico_size) / 2, ico_size, ico_size); } public TabPageRect(Rectangle rect_it, Size size, int ico_size, int close_size, int gap, int gapI) { Rect = rect_it; Rect_Text = new Rectangle(rect_it.X + ico_size + gap, rect_it.Y + gapI, size.Width + gap, rect_it.Height - gap); Rect_Ico = new Rectangle(rect_it.X + gap, rect_it.Y + (rect_it.Height - ico_size) / 2, ico_size, ico_size); Rect_Close = new Rectangle(rect_it.Right - gap - close_size, rect_it.Y + (rect_it.Height - close_size) / 2, close_size, close_size); } public TabPageRect(Rectangle rect_it, bool test, Size size, int close_size, int gap, int gapI) { Rect = rect_it; int y = rect_it.Y + (rect_it.Height - close_size) / 2; Rect_Text = new Rectangle(rect_it.X, rect_it.Y + gapI, size.Width + gap, rect_it.Height - gap); Rect_Close = new Rectangle(rect_it.Right - gap - close_size, y, close_size, close_size); } public Rectangle Rect; public Rectangle Rect_Text; public Rectangle Rect_Ico; public Rectangle Rect_Close; public ITaskOpacity? hover_close; } #endregion public void SelectedIndexChanged(int i, int old) { owner?.Invalidate(); } public void Dispose() { foreach (var item in rects) { item.hover_close?.Dispose(); } } public bool MouseClick(TabPage page, int i, int x, int y) { if (owner == null) return false; if (closable && !page.ReadOnly) { if (rects[i].Rect_Close.Contains(x, y)) { bool flag = true; if (owner.ClosingPage != null) flag = owner.ClosingPage.Invoke(owner, new ClosingPageEventArgs(page)); if (flag) owner.Pages.Remove(page); return true; } } return false; } public void MouseMove(int x, int y) { if (owner == null) return; if (closable) { int i = 0; foreach (var item in rects) { if (item.hover_close == null) item.hover_close = new ITaskOpacity(owner); if (i == owner.hover_i) { item.hover_close.MaxValue = AntdUI.Style.Db.Text.A - AntdUI.Style.Db.TextQuaternary.A; item.hover_close.Switch = item.Rect_Close.Contains(x, y); } else item.hover_close.Switch = false; i++; } } } public void MouseLeave() { if (closable) { foreach (var item in rects) { if (item.hover_close == null) continue; item.hover_close.Switch = false; } } } public void MouseWheel(int delta) { if (owner != null && owner.scroll_show) { switch (owner.Alignment) { case TabAlignment.Left: case TabAlignment.Right: owner.scroll_x = 0; owner.scroll_y -= delta; break; case TabAlignment.Top: case TabAlignment.Bottom: default: owner.scroll_y = 0; owner.scroll_x -= delta; break; } } } } /// /// 卡片样式2 /// public class StyleCard2 : IStyle { Tabs? owner; public StyleCard2() { } public StyleCard2(Tabs tabs) { owner = tabs; } int radius = 6; /// /// 卡片圆角 /// [Description("卡片圆角"), Category("卡片"), DefaultValue(6)] public int Radius { get => radius; set { if (radius == value) return; radius = value; owner?.Invalidate(); } } int bordersize = 1; /// /// 边框大小 /// [Description("边框大小"), Category("卡片"), DefaultValue(1)] public int Border { get => bordersize; set { if (bordersize == value) return; bordersize = value; owner?.LoadLayout(); } } Color? border; /// /// 卡片边框颜色 /// [Description("卡片边框颜色"), Category("卡片"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? BorderColor { get => border; set { if (border == value) return; border = value; owner?.Invalidate(); } } /// /// 卡片边框激活颜色 /// [Description("卡片边框激活颜色"), Category("卡片"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? BorderActive { get; set; } Color? fill; /// /// 卡片颜色 /// [Description("卡片颜色"), Category("卡片"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? Fill { get => fill; set { if (fill == value) return; fill = value; owner?.Invalidate(); } } /// /// 卡片悬停颜色 /// [Description("卡片悬停颜色"), Category("卡片"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? FillHover { get; set; } /// /// 卡片激活颜色 /// [Description("卡片激活颜色"), Category("卡片"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? FillActive { get; set; } int gap = 2; /// /// 卡片间距 /// [Description("卡片间距"), Category("卡片"), DefaultValue(2)] public int Gap { get => gap; set { if (gap == value) return; gap = value; owner?.LoadLayout(); } } public enum CloseType { none, always, activate } CloseType closable = CloseType.none; /// /// 可关闭 /// [Description("可关闭"), Category("卡片"), DefaultValue(false)] public CloseType Closable { get => closable; set { if (closable == value) return; closable = value; owner?.LoadLayout(); } } TabPageRect[] rects = new TabPageRect[0]; public void LoadLayout(Tabs tabs, Rectangle rect, TabCollection items) { owner = tabs; rects = Helper.GDI(g => { int gap = (int)(tabs.Gap * Config.Dpi), gapI = gap / 2, xy = 0, xy2 = 0; int cardgap = (int)(Gap * Config.Dpi); var rect_list = new List(items.Count); var rect_dir = GetDir(tabs, g, items, gap, out int ico_size, out int close_size, out xy2); if (closable != CloseType.none) { switch (tabs.Alignment) { case TabAlignment.Bottom: int y = rect.Bottom - xy2; foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it; if (it.Key.HasIcon) { rect_it = new Rectangle(rect.X + xy, y + 2 + bordersize, it.Value.Width + gap + ico_size + close_size + gap * 2, xy2 - 2 - bordersize); rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, close_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X + xy, y + 2 + bordersize, it.Value.Width + gap + close_size + gap, xy2 - 2 - bordersize); rect_list.Add(new TabPageRect(rect_it, false, it.Value, close_size, gap, gapI)); } it.Key.SetRect(rect_it); xy += rect_it.Width + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(0, 0, 0, xy2); owner.scroll_max = xy - rect.Width; owner.scroll_show = xy > rect.Width; break; case TabAlignment.Left: foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it = new Rectangle(rect.X, rect.Y + xy, xy2 - 2 - bordersize, it.Value.Height + gap); if (it.Key.HasIcon) rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, close_size, gap, gapI)); else rect_list.Add(new TabPageRect(rect_it, false, it.Value, close_size, gap, gapI)); it.Key.SetRect(rect_it); xy += rect_it.Height + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(xy2, 0, 0, 0); owner.scroll_max = xy - rect.Height; owner.scroll_show = xy > rect.Height; break; case TabAlignment.Right: int x = rect.Right - xy2; foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it = new Rectangle(x + 2 + bordersize, rect.Y + xy, xy2 - 2 - bordersize, it.Value.Height + gap); if (it.Key.HasIcon) rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, close_size, gap, gapI)); else rect_list.Add(new TabPageRect(rect_it, false, it.Value, close_size, gap, gapI)); it.Key.SetRect(rect_it); xy += rect_it.Height + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(0, 0, xy2, 0); owner.scroll_max = xy - rect.Height; owner.scroll_show = xy > rect.Height; break; case TabAlignment.Top: default: foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it; if (it.Key.HasIcon) { rect_it = new Rectangle(rect.X + xy, rect.Y, it.Value.Width + gap + ico_size + close_size + gap * 2, xy2 - 2 - bordersize); rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, close_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X + xy, rect.Y, it.Value.Width + gap + close_size + gap, xy2 - 2 - bordersize); rect_list.Add(new TabPageRect(rect_it, false, it.Value, close_size, gap, gapI)); } it.Key.SetRect(rect_it); xy += rect_it.Width + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(0, xy2, 0, 0); owner.scroll_max = xy - rect.Width; owner.scroll_show = xy > rect.Width; break; } } else { switch (tabs.Alignment) { case TabAlignment.Bottom: int y = rect.Bottom - xy2; foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it; if (it.Key.HasIcon) { rect_it = new Rectangle(rect.X + xy, y + 2 + bordersize, it.Value.Width + gap + ico_size + gap, xy2 - 2 - bordersize); rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X + xy, y + 2 + bordersize, it.Value.Width + gap, xy2 - 2 - bordersize); rect_list.Add(new TabPageRect(rect_it)); } it.Key.SetRect(rect_it); xy += rect_it.Width + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(0, 0, 0, xy2); owner.scroll_max = xy - rect.Width; owner.scroll_show = xy > rect.Width; break; case TabAlignment.Left: foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it = new Rectangle(rect.X, rect.Y + xy, xy2 - 2 - bordersize, it.Value.Height + gap); if (it.Key.HasIcon) rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, gap, gapI)); else rect_list.Add(new TabPageRect(rect_it)); it.Key.SetRect(rect_it); xy += rect_it.Height + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(xy2, 0, 0, 0); owner.scroll_max = xy - rect.Height; owner.scroll_show = xy > rect.Height; break; case TabAlignment.Right: int x = rect.Right - xy2; foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it = new Rectangle(x + 2 + bordersize, rect.Y + xy, xy2 - 2 - bordersize, it.Value.Height + gap); if (it.Key.HasIcon) rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, gap, gapI)); else rect_list.Add(new TabPageRect(rect_it)); it.Key.SetRect(rect_it); xy += rect_it.Height + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(0, 0, xy2, 0); owner.scroll_max = xy - rect.Height; owner.scroll_show = xy > rect.Height; break; case TabAlignment.Top: default: foreach (var it in rect_dir) { if (it.Key.Visible) { Rectangle rect_it; if (it.Key.HasIcon) { rect_it = new Rectangle(rect.X + xy, rect.Y, it.Value.Width + gap + ico_size + gap, xy2 - 2 - bordersize); rect_list.Add(new TabPageRect(rect_it, it.Value, ico_size, gap, gapI)); } else { rect_it = new Rectangle(rect.X + xy, rect.Y, it.Value.Width + gap, xy2 - 2 - bordersize); rect_list.Add(new TabPageRect(rect_it)); } it.Key.SetRect(rect_it); xy += rect_it.Width + cardgap; } else rect_list.Add(new TabPageRect()); } xy -= cardgap; tabs.SetPadding(0, xy2, 0, 0); owner.scroll_max = xy - rect.Width; owner.scroll_show = xy > rect.Width; break; } } if (owner.scroll_show) owner.scroll_max += owner.SizeExceed(owner.ClientRectangle, rect_list[0].Rect, rect_list[rect_list.Count - 1].Rect); else { owner.scroll_x = owner.scroll_y = 0; if (tabs.centered) { switch (tabs.Alignment) { case TabAlignment.Left: case TabAlignment.Right: int oy = (rect.Height - xy) / 2; foreach (var item in rect_dir) item.Key.SetOffset(0, oy); foreach (var item in rect_list) { item.Rect.Offset(0, oy); item.Rect_Text.Offset(0, oy); item.Rect_Ico.Offset(0, oy); item.Rect_Close.Offset(0, oy); } break; case TabAlignment.Top: case TabAlignment.Bottom: default: int ox = (rect.Width - xy) / 2; foreach (var item in rect_dir) item.Key.SetOffset(ox, 0); foreach (var item in rect_list) { item.Rect.Offset(ox, 0); item.Rect_Text.Offset(ox, 0); item.Rect_Ico.Offset(ox, 0); item.Rect_Close.Offset(ox, 0); } break; } } } return rect_list.ToArray(); }); } public void Paint(Tabs owner, Graphics g, TabCollection items) { if (rects.Length == items.Count) { using (var brush_fore = new SolidBrush(owner.ForeColor ?? AntdUI.Style.Db.Text)) using (var brush_fill = new SolidBrush(owner.Fill ?? AntdUI.Style.Db.Primary)) using (var brush_active = new SolidBrush(owner.FillActive ?? AntdUI.Style.Db.PrimaryActive)) using (var brush_hover = new SolidBrush(owner.FillHover ?? AntdUI.Style.Db.PrimaryHover)) using (var brush_bg = new SolidBrush(Fill ?? AntdUI.Style.Db.FillQuaternary)) using (var brush_bg_hover = new SolidBrush(FillHover ?? AntdUI.Style.Db.FillQuaternary)) using (var brush_bg_active = new SolidBrush(FillActive ?? AntdUI.Style.Db.BgContainer)) { var rect_t = owner.ClientRectangle; int radius = (int)(Radius * Config.Dpi), bor = (int)(bordersize * Config.Dpi), bor2 = bor * 6, bor22 = bor2 * 2; float borb2 = bor / 2F; TabPage? sel = null; int i = 0, select = owner.SelectedIndex; switch (owner.Alignment) { case TabAlignment.Bottom: int read_b_h = rects[0].Rect.Height + rects[0].Rect.X; var rect_s_b = new Rectangle(rect_t.X, rect_t.Bottom - read_b_h, rect_t.Width, read_b_h); if (owner.scroll_show) { g.SetClip(owner.PaintExceedPre(rect_s_b, rects[0].Rect.Height)); g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); } else g.SetClip(rect_s_b); foreach (var page in items) { if (page.Visible) { if (select == i) sel = page; else { using (var path = Helper.RoundPath(page.Rect, radius, true, true, true, true)) { g.FillPath(owner.hover_i == i ? brush_bg_hover : brush_bg, path); if (bor > 0) { using (var pen_bg = new Pen(border ?? AntdUI.Style.Db.BorderSecondary, bor)) { g.DrawPath(pen_bg, path); } } if (owner.hover_i == i) PaintText(g, rects[i], owner, page, page.MDown ? brush_active : brush_hover, true); else PaintText(g, rects[i], owner, page, brush_fore, closable == CloseType.always ? true : false); } } } i++; } g.ResetTransform(); if (sel != null)//是否选中 { var rect_page = sel.Rect; using (var path = Helper.RoundPath(rect_page, radius, true, true, true, true)) { if (bor > 0) { using (var pen_bg = new Pen(BorderActive ?? AntdUI.Style.Db.BorderColor, bor)) { float ly = rect_page.Y + borb2; if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); using (var path2 = Helper.RoundPath(new RectangleF(rect_page.X + borb2, rect_page.Y - borb2, rect_page.Width - bor, rect_page.Height + borb2), radius, true, true, true, true)) { g.FillPath(brush_bg_active, path2); } g.DrawPath(pen_bg, path); } } else { if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); g.FillPath(brush_bg_active, path); } PaintText(g, rects[select], owner, sel, brush_fill, true); } } break; case TabAlignment.Left: var rect_s_l = new Rectangle(rect_t.X, rect_t.Y, rects[0].Rect.Right, rect_t.Height); if (owner.scroll_show) { g.SetClip(owner.PaintExceedPre(rect_s_l, rects[0].Rect.Height)); g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); } else g.SetClip(rect_s_l); foreach (var page in items) { if (page.Visible) { if (owner.SelectedIndex == i) sel = page; else { using (var path = Helper.RoundPath(page.Rect, radius, true, true, true, true)) { g.FillPath(owner.hover_i == i ? brush_bg_hover : brush_bg, path); if (bor > 0) { using (var pen_bg = new Pen(border ?? AntdUI.Style.Db.BorderSecondary, bor)) { g.DrawPath(pen_bg, path); } } if (owner.hover_i == i) PaintText(g, rects[i], owner, page, page.MDown ? brush_active : brush_hover, true); else PaintText(g, rects[i], owner, page, brush_fore, closable == CloseType.always ? true : false); } } } i++; } g.ResetTransform(); if (sel != null)//是否选中 { var rect_page = sel.Rect; using (var path = Helper.RoundPath(rect_page, radius, true, true, true, true)) { if (bor > 0) { using (var pen_bg = new Pen(BorderActive ?? AntdUI.Style.Db.BorderColor, bor)) { float lx = rect_page.Right - borb2; if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); using (var path2 = Helper.RoundPath(new RectangleF(rect_page.X - borb2, rect_page.Y + borb2, rect_page.Width + borb2, rect_page.Height - bor), radius, true, true, true, true)) { g.FillPath(brush_bg_active, path2); } g.DrawPath(pen_bg, path); } } else { if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); g.FillPath(brush_bg_active, path); } PaintText(g, rects[select], owner, sel, brush_fill, true); } } break; case TabAlignment.Right: int read_r_w = rects[0].Rect.Width + rects[0].Rect.Y; var rect_s_r = new Rectangle(rect_t.Right - read_r_w, rect_t.Y, read_r_w, rect_t.Height); if (owner.scroll_show) { g.SetClip(owner.PaintExceedPre(rect_s_r, rects[0].Rect.Height)); g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); } else g.SetClip(rect_s_r); foreach (var page in items) { if (page.Visible) { if (owner.SelectedIndex == i) sel = page; else { using (var path = Helper.RoundPath(page.Rect, radius, true, true, true, true)) { g.FillPath(owner.hover_i == i ? brush_bg_hover : brush_bg, path); if (bor > 0) { using (var pen_bg = new Pen(border ?? AntdUI.Style.Db.BorderSecondary, bor)) { g.DrawPath(pen_bg, path); } } if (owner.hover_i == i) PaintText(g, rects[i], owner, page, page.MDown ? brush_active : brush_hover, true); else PaintText(g, rects[i], owner, page, brush_fore, closable == CloseType.always ? true : false); } } } i++; } g.ResetTransform(); if (sel != null)//是否选中 { var rect_page = sel.Rect; using (var path = Helper.RoundPath(rect_page, radius, true, true, true, true)) { if (bor > 0) { using (var pen_bg = new Pen(BorderActive ?? AntdUI.Style.Db.BorderColor, bor)) { float lx = rect_page.X + borb2; if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); using (var path2 = Helper.RoundPath(new RectangleF(rect_page.X - borb2, rect_page.Y + borb2, rect_page.Width + borb2, rect_page.Height - bor), radius, true, true, true, true)) { g.FillPath(brush_bg_active, path2); } g.DrawPath(pen_bg, path); } } else { if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); g.FillPath(brush_bg_active, path); } PaintText(g, rects[select], owner, sel, brush_fill, true); } } break; case TabAlignment.Top: default: var rect_s_t = new Rectangle(rect_t.X, rect_t.Y, rect_t.Width, rects[0].Rect.Bottom); if (owner.scroll_show) { g.SetClip(owner.PaintExceedPre(rect_s_t, rects[0].Rect.Height)); g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); } else g.SetClip(rect_s_t); foreach (var page in items) { if (page.Visible) { if (owner.SelectedIndex == i) sel = page; else { using (var path = Helper.RoundPath(page.Rect, radius, true, true, true, true)) { g.FillPath(owner.hover_i == i ? brush_bg_hover : brush_bg, path); if (bor > 0) { using (var pen_bg = new Pen(border ?? AntdUI.Style.Db.BorderSecondary, bor)) { g.DrawPath(pen_bg, path); } } if (owner.hover_i == i) PaintText(g, rects[i], owner, page, page.MDown ? brush_active : brush_hover, true); else PaintText(g, rects[i], owner, page, brush_fore, closable == CloseType.always ? true : false); } } } i++; } g.ResetTransform(); if (sel != null)//是否选中 { var rect_page = sel.Rect; using (var path = Helper.RoundPath(rect_page, radius, true, true, true, true)) { if (bor > 0) { using (var pen_bg = new Pen(BorderActive ?? AntdUI.Style.Db.BorderColor, bor)) { float ly = rect_page.Bottom - borb2; if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); using (var path2 = Helper.RoundPath(new RectangleF(rect_page.X + borb2, rect_page.Y - borb2, rect_page.Width - bor, rect_page.Height + borb2), radius, true, true, true, true)) { g.FillPath(brush_bg_active, path2); } g.DrawPath(pen_bg, path); } } else { if (owner.scroll_show) g.TranslateTransform(-owner.scroll_x, -owner.scroll_y); g.FillPath(brush_bg_active, path); } PaintText(g, rects[select], owner, sel, brush_fill, true); } } break; } if (owner.scroll_show) owner.PaintExceed(g, brush_fore.Color, radius, rect_t, rects[0].Rect, rects[rects.Length - 1].Rect, true); } } } #region 辅助 Dictionary GetDir(Tabs owner, Graphics g, TabCollection items, int gap, out int ico_size, out int close_size, out int sizewh) { sizewh = 0; var size_t = g.MeasureString(Config.NullText, owner.Font).Size(); var rect_dir = new Dictionary(items.Count); foreach (var item in items) { var size = g.MeasureString(item.Text, owner.Font).Size(); rect_dir.Add(item, size); } ico_size = (int)(size_t.Height * owner.IconRatio); close_size = (int)(size_t.Height * (owner.IconRatio * .8F)); switch (owner.Alignment) { case TabAlignment.Left: case TabAlignment.Right: if (closable != CloseType.none) { foreach (var it in rect_dir) { if (it.Key.Visible) { int w; if (it.Key.HasIcon) w = it.Value.Width + ico_size + gap * 2; else w = it.Value.Width + gap; w += ico_size + gap; if (sizewh < w) sizewh = w; } } } else { foreach (var it in rect_dir) { if (it.Key.Visible) { int w; if (it.Key.HasIcon) w = it.Value.Width + ico_size + gap * 2; else w = it.Value.Width + gap; if (sizewh < w) sizewh = w; } } } break; case TabAlignment.Top: case TabAlignment.Bottom: default: foreach (var it in rect_dir) { if (it.Key.Visible) { int h = it.Value.Height + gap; if (sizewh < h) sizewh = h; } } break; } return owner.HandItemSize(rect_dir, ref sizewh); } void PaintText(Graphics g, TabPageRect rects, Tabs owner, TabPage page, SolidBrush brush, bool closshow = false) { if (page.HasIcon) { if (page.Icon != null) g.DrawImage(page.Icon, rects.Rect_Ico); else if (page.IconSvg != null) g.GetImgExtend(page.IconSvg, rects.Rect_Ico, brush.Color); } if (closable != CloseType.none && closshow) { if (rects.hover_close == null) g.PaintIconClose(rects.Rect_Close, AntdUI.Style.Db.TextQuaternary); else if (rects.hover_close.Animation) g.PaintIconClose(rects.Rect_Close, Helper.ToColor(rects.hover_close.Value + AntdUI.Style.Db.TextQuaternary.A, AntdUI.Style.Db.Text)); else if (rects.hover_close.Switch) g.PaintIconClose(rects.Rect_Close, AntdUI.Style.Db.Text); else g.PaintIconClose(rects.Rect_Close, AntdUI.Style.Db.TextQuaternary); } g.DrawStr(page.Text, owner.Font, brush, rects.Rect_Text, owner.s_c); owner.PaintBadge(g, page, rects.Rect_Text); } internal class TabPageRect { public TabPageRect() { } public TabPageRect(Rectangle rect) { Rect = Rect_Text = rect; } public TabPageRect(Rectangle rect_it, Size size, int ico_size, int gap, int gapI) { Rect = rect_it; Rect_Text = new Rectangle(rect_it.X + ico_size + gap, rect_it.Y + gapI, size.Width + gap, rect_it.Height - gap); Rect_Ico = new Rectangle(rect_it.X + gap, rect_it.Y + (rect_it.Height - ico_size) / 2, ico_size, ico_size); } public TabPageRect(Rectangle rect_it, Size size, int ico_size, int close_size, int gap, int gapI) { Rect = rect_it; Rect_Text = new Rectangle(rect_it.X + ico_size + gap, rect_it.Y + gapI, size.Width + gap, rect_it.Height - gap); Rect_Ico = new Rectangle(rect_it.X + gap, rect_it.Y + (rect_it.Height - ico_size) / 2, ico_size, ico_size); Rect_Close = new Rectangle(rect_it.Right - gap - close_size, rect_it.Y + (rect_it.Height - close_size) / 2, close_size, close_size); } public TabPageRect(Rectangle rect_it, bool test, Size size, int close_size, int gap, int gapI) { Rect = rect_it; int y = rect_it.Y + (rect_it.Height - close_size) / 2; Rect_Text = new Rectangle(rect_it.X, rect_it.Y + gapI, size.Width + gap, rect_it.Height - gap); Rect_Close = new Rectangle(rect_it.Right - gap - close_size, y, close_size, close_size); } public Rectangle Rect; public Rectangle Rect_Text; public Rectangle Rect_Ico; public Rectangle Rect_Close; public ITaskOpacity? hover_close; } #endregion public void SelectedIndexChanged(int i, int old) { owner?.Invalidate(); } public void Dispose() { foreach (var item in rects) { item.hover_close?.Dispose(); } } public bool MouseClick(TabPage page, int i, int x, int y) { if (owner == null) return false; if (closable != CloseType.none) { if (rects[i].Rect_Close.Contains(x, y)) { bool flag = true; if (owner.ClosingPage != null) flag = owner.ClosingPage.Invoke(owner, new ClosingPageEventArgs(page)); if (flag) owner.Pages.Remove(page); return true; } } return false; } public void MouseMove(int x, int y) { if (owner == null) return; if (closable != CloseType.none) { int i = 0; foreach (var item in rects) { if (item.hover_close == null) item.hover_close = new ITaskOpacity(owner); if (i == owner.hover_i) { item.hover_close.MaxValue = AntdUI.Style.Db.Text.A - AntdUI.Style.Db.TextQuaternary.A; item.hover_close.Switch = item.Rect_Close.Contains(x, y); } else item.hover_close.Switch = false; i++; } } } public bool MouseMovePre(int x, int y) => false; public void MouseLeave() { if (closable != CloseType.none) { foreach (var item in rects) { if (item.hover_close == null) continue; item.hover_close.Switch = false; } } } public void MouseWheel(int delta) { if (owner != null && owner.scroll_show) { switch (owner.Alignment) { case TabAlignment.Left: case TabAlignment.Right: owner.scroll_x = 0; owner.scroll_y -= delta; break; case TabAlignment.Top: case TabAlignment.Bottom: default: owner.scroll_y = 0; owner.scroll_x -= delta; break; } } } } [TypeConverter(typeof(ExpandableObjectConverter))] public interface IStyle { void LoadLayout(Tabs owner, Rectangle rect, TabCollection items); void Paint(Tabs owner, Graphics g, TabCollection items); void SelectedIndexChanged(int i, int old); bool MouseClick(TabPage page, int i, int x, int y); void MouseMove(int x, int y); void MouseLeave(); void MouseWheel(int delta); void Dispose(); } } }