// 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
{
///
/// Menu 导航菜单
///
/// 为页面和功能提供导航的菜单列表。
[Description("Menu 导航菜单")]
[ToolboxItem(true)]
[DefaultProperty("Items")]
[DefaultEvent("SelectChanged")]
public class Menu : IControl, SubLayeredForm
{
#region 属性
///
/// 悬停背景颜色
///
[Description("悬停背景颜色"), Category("外观"), DefaultValue(null)]
[Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))]
public Color? BackHover { get; set; }
///
/// 激活背景颜色
///
[Description("激活背景颜色"), Category("外观"), DefaultValue(null)]
[Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))]
public Color? BackActive { get; set; }
Color? fore;
///
/// 文字颜色
///
[Description("文字颜色"), Category("外观"), DefaultValue(null)]
[Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))]
public new Color? ForeColor
{
get => fore;
set
{
if (fore == value) fore = value;
fore = value;
Invalidate();
}
}
///
/// 激活字体颜色
///
[Description("激活字体颜色"), Category("外观"), DefaultValue(null)]
[Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))]
public Color? ForeActive { get; set; }
int radius = 6;
///
/// 圆角
///
[Description("圆角"), Category("外观"), DefaultValue(6)]
public int Radius
{
get => radius;
set
{
if (radius == value) return;
radius = value;
Invalidate();
}
}
bool round = false;
///
/// 圆角样式
///
[Description("圆角样式"), Category("外观"), DefaultValue(false)]
public bool Round
{
get => round;
set
{
if (round == value) return;
round = value;
Invalidate();
}
}
TAMode theme = TAMode.Auto;
///
/// 色彩模式
///
[Description("色彩模式"), Category("外观"), DefaultValue(TAMode.Auto)]
public TAMode Theme
{
get => theme;
set
{
if (theme == value) return;
theme = value;
Invalidate();
}
}
TMenuMode mode = TMenuMode.Inline;
///
/// 菜单类型
///
[Description("菜单类型"), Category("外观"), DefaultValue(TMenuMode.Inline)]
public TMenuMode Mode
{
get => mode;
set
{
if (mode == value) return;
mode = value;
if (IsHandleCreated)
{
ChangeList();
Invalidate();
}
}
}
///
/// 常规缩进
///
[Description("常规缩进"), Category("外观"), DefaultValue(false)]
public bool Indent { get; set; } = false;
///
/// 只保持一个子菜单的展开
///
[Description("只保持一个子菜单的展开"), Category("外观"), DefaultValue(false)]
public bool Unique { get; set; }
///
/// 显示子菜单背景
///
[Description("显示子菜单背景"), Category("外观"), DefaultValue(false)]
public bool ShowSubBack { get; set; } = false;
///
/// 自动折叠
///
[Description("自动折叠"), Category("外观"), DefaultValue(false)]
public bool AutoCollapse { get; set; }
bool collapsed = false;
///
/// 是否折叠
///
[Description("是否折叠"), Category("外观"), DefaultValue(false)]
public bool Collapsed
{
get => collapsed;
set
{
if (collapsed == value) return;
collapsed = value;
Width = value ? CollapseWidth : CollapsedWidth;
OnSizeChanged(EventArgs.Empty);
}
}
#region 集合操作
public void SelectIndex(int i1, bool focus = true)
{
if (items == null || items.Count == 0) return;
IUSelect(items);
if (items.ListExceed(i1))
{
Invalidate(); return;
}
var it1 = items[i1];
it1.Select = true;
OnSelectIndexChanged(it1);
if (focus && ScrollBar.ShowY) ScrollBar.ValueY = it1.rect.Y;
Invalidate();
}
public void SelectIndex(int i1, int i2, bool focus = true)
{
if (items == null || items.Count == 0) return;
IUSelect(items);
if (items.ListExceed(i1))
{
Invalidate(); return;
}
var it1 = items[i1];
if (it1.items.ListExceed(i2))
{
Invalidate(); return;
}
var it2 = it1.Sub[i2];
it1.Select = it2.Select = true;
OnSelectIndexChanged(it2);
if (focus && ScrollBar.ShowY) ScrollBar.ValueY = it2.rect.Y;
Invalidate();
}
public void SelectIndex(int i1, int i2, int i3, bool focus = true)
{
if (items == null || items.Count == 0) return;
IUSelect(items);
if (items.ListExceed(i1))
{
Invalidate();
return;
}
var it1 = items[i1];
if (it1.items.ListExceed(i2))
{
Invalidate(); return;
}
var it2 = it1.Sub[i2];
if (it2.items.ListExceed(i3))
{
Invalidate();
return;
}
var it3 = it2.Sub[i3];
it1.Select = it2.Select = it3.Select = true;
OnSelectIndexChanged(it3);
if (focus && ScrollBar.ShowY) ScrollBar.ValueY = it3.rect.Y;
Invalidate();
}
///
/// 选中菜单
///
/// 项
/// 设置焦点
public void Select(MenuItem item, bool focus = true)
{
if (items == null || items.Count == 0) return;
IUSelect(items);
Select(item, focus, items);
}
void Select(MenuItem item, bool focus, MenuItemCollection items)
{
foreach (var it in items)
{
if (it == item)
{
it.Select = true;
OnSelectIndexChanged(it);
if (focus && ScrollBar.ShowY) ScrollBar.ValueY = it.rect.Y;
return;
}
else if (it.items != null && it.items.Count > 0) Select(item, focus, it.items);
}
}
///
/// 移除菜单
///
/// 项
public void Remove(MenuItem item)
{
if (items == null || items.Count == 0) return;
Remove(item, items);
}
void Remove(MenuItem item, MenuItemCollection items)
{
foreach (var it in items)
{
if (it == item)
{
items.Remove(it);
return;
}
else if (it.items != null && it.items.Count > 0) Remove(item, it.items);
}
}
#region 事件
///
/// Select 属性值更改时发生
///
[Description("Select 属性值更改时发生"), Category("行为")]
public event SelectEventHandler? SelectChanged = null;
internal void OnSelectIndexChanged(MenuItem item)
{
SelectChanged?.Invoke(this, new MenuSelectEventArgs(item));
}
#endregion
#endregion
MenuItemCollection? items;
///
/// 菜单集合
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Description("菜单集合"), Category("数据")]
public MenuItemCollection Items
{
get
{
items ??= new MenuItemCollection(this);
return items;
}
set => items = value.BindData(this);
}
bool pauseLayout = false;
[Browsable(false), Description("暂停布局"), Category("行为"), DefaultValue(false)]
public bool PauseLayout
{
get => pauseLayout;
set
{
if (pauseLayout == value) return;
pauseLayout = value;
if (!value)
{
ChangeList();
Invalidate();
}
}
}
///
/// 滚动条
///
[Browsable(false)]
public ScrollBar ScrollBar;
#endregion
#region 布局
protected override void OnHandleCreated(EventArgs e)
{
var rect = ChangeList();
ScrollBar.SizeChange(rect);
base.OnHandleCreated(e);
}
protected override void OnFontChanged(EventArgs e)
{
var rect = ChangeList();
ScrollBar.SizeChange(rect);
base.OnFontChanged(e);
}
protected override void OnSizeChanged(EventArgs e)
{
if (IsHandleCreated)
{
var rect = ChangeList();
ScrollBar.SizeChange(rect);
}
base.OnSizeChanged(e);
}
internal int CollapseWidth = 0, CollapsedWidth = 0;
internal Rectangle ChangeList()
{
var _rect = ClientRectangle;
if (_rect.Width == 0 || _rect.Height == 0 || pauseLayout || items == null || items.Count == 0) return _rect;
var rect = _rect.PaddingRect(Padding);
int y = 0;
int icon_count = 0;
Helper.GDI(g =>
{
var lists = items;
var size = g.MeasureString(Config.NullText, Font);
int icon_size = (int)Math.Ceiling(size.Height * 1.2F), gap = icon_size / 2, gapI = gap / 2, height = (int)Math.Ceiling(size.Height + gap * 2);
if (mode == TMenuMode.Horizontal) ChangeListHorizontal(rect, g, lists, 0, icon_size, gap, gapI);
else
{
CollapseWidth = icon_size * 2 + gap + gapI + Padding.Horizontal;
CollapsedWidth = ChangeList(rect, g, null, lists, ref y, ref icon_count, height, icon_size, gap, gapI, 0) + Padding.Horizontal;
if (AutoCollapse)
{
if (icon_count > 0) collapsed = CollapsedWidth > _rect.Width;
else collapsed = false;
}
if (collapsed) ChangeUTitle(lists);
}
});
ScrollBar.SetVrSize(y);
return _rect;
}
int ChangeList(Rectangle rect, Graphics g, MenuItem? Parent, MenuItemCollection items, ref int y, ref int icon_count, int height, int icon_size, int gap, int gapI, int depth)
{
int collapsedWidth = 0;
foreach (var it in items)
{
it.PARENT = this;
it.PARENTITEM = Parent;
if (it.HasIcon) icon_count++;
it.SetRect(depth, Indent, new Rectangle(rect.X, rect.Y + y, rect.Width, height), icon_size, gap);
if (it.Visible)
{
int size = (int)Math.Ceiling(g.MeasureString(it.Text, it.Font ?? Font).Width + gap * 4 + icon_size + it.arr_rect.Width);
if (size > collapsedWidth) collapsedWidth = size;
y += height + gapI;
if (mode == TMenuMode.Inline && it.CanExpand)
{
if (!collapsed)
{
int y_item = y;
int size2 = ChangeList(rect, g, it, it.Sub, ref y, ref icon_count, height, icon_size, gap, gapI, depth + 1);
if (size2 > collapsedWidth) collapsedWidth = size2;
it.SubY = y_item - gapI / 2;
it.SubHeight = y - y_item;
if ((it.Expand || it.ExpandThread) && it.ExpandProg > 0)
{
it.ExpandHeight = y - y_item;
y = y_item + (int)Math.Ceiling(it.ExpandHeight * it.ExpandProg);
}
else if (!it.Expand) y = y_item;
}
else
{
int oldy = y;
int size2 = ChangeList(rect, g, it, it.Sub, ref y, ref icon_count, height, icon_size, gap, gapI, depth + 1);
if (size2 > collapsedWidth) collapsedWidth = size2;
y = oldy;
}
}
}
}
return collapsedWidth;
}
void ChangeListHorizontal(Rectangle rect, Graphics g, MenuItemCollection items, int x, int icon_size, int gap, int gapI)
{
foreach (var it in items)
{
it.PARENT = this;
int size;
if (it.HasIcon) size = (int)Math.Ceiling(g.MeasureString(it.Text, it.Font ?? Font).Width + gap * 3 + icon_size);
else size = (int)Math.Ceiling(g.MeasureString(it.Text, it.Font ?? Font).Width + gap * 2);
it.SetRectNoArr(0, new Rectangle(rect.X + x, rect.Y, size, rect.Height), icon_size, gap);
if (it.Visible) x += size;
}
}
void ChangeUTitle(MenuItemCollection items)
{
foreach (var it in items)
{
var rect = it.Rect;
it.ico_rect = new Rectangle(rect.X + (rect.Width - it.ico_rect.Width) / 2, it.ico_rect.Y, it.ico_rect.Width, it.ico_rect.Height);
if (it.Visible && it.CanExpand) ChangeUTitle(it.Sub);
}
}
#endregion
#region 渲染
public Menu() { ScrollBar = new ScrollBar(this); }
protected override void OnPaint(PaintEventArgs e)
{
if (items == null || items.Count == 0) return;
var rect = ClientRectangle;
if (rect.Width == 0 || rect.Height == 0) return;
var g = e.Graphics.High();
int sy = ScrollBar.Value;
g.TranslateTransform(0, -sy);
Color scroll_color, color_fore, color_fore_active, fore_enabled, back_hover, back_active;
switch (theme)
{
case TAMode.Light:
scroll_color = Color.Black;
fore_enabled = Style.rgba(0, 0, 0, 0.25F);
color_fore = fore ?? Color.Black;
color_fore_active = ForeActive ?? "#1677FF".ToColor();
back_hover = BackHover ?? Style.rgba(0, 0, 0, 0.06F);
back_active = BackActive ?? "#E6F4FF".ToColor();
break;
case TAMode.Dark:
scroll_color = Color.White;
fore_enabled = Style.rgba(255, 255, 255, 0.25F);
color_fore = fore ?? Style.rgba(255, 255, 255, 0.85F);
back_hover = color_fore_active = ForeActive ?? Color.White;
back_active = BackActive ?? "#1668DC".ToColor();
break;
default:
scroll_color = Style.Db.TextBase;
fore_enabled = Style.Db.TextQuaternary;
if (Config.IsDark)
{
color_fore = fore ?? Style.Db.Text;
back_hover = color_fore_active = ForeActive ?? Style.Db.TextBase;
back_active = BackActive ?? Style.Db.Primary;
}
else
{
color_fore = fore ?? Style.Db.TextBase;
color_fore_active = ForeActive ?? Style.Db.Primary;
back_hover = BackHover ?? Style.Db.FillSecondary;
back_active = BackActive ?? Style.Db.PrimaryBg;
}
break;
}
float _radius = radius * Config.Dpi;
using (var sub_bg = new SolidBrush(Style.Db.FillQuaternary))
{
PaintItems(g, rect, sy, items, color_fore, color_fore_active, fore_enabled, back_hover, back_active, _radius, sub_bg);
}
g.ResetTransform();
ScrollBar.Paint(g, scroll_color);
this.PaintBadge(g);
base.OnPaint(e);
}
void PaintItems(Graphics g, Rectangle rect, int sy, MenuItemCollection items, Color fore, Color fore_active, Color fore_enabled, Color back_hover, Color back_active, float radius, SolidBrush sub_bg)
{
foreach (var it in items)
{
it.show = it.Show && it.Visible && it.rect.Y > sy - rect.Height - (it.Expand ? it.SubHeight : 0) && it.rect.Bottom < sy + rect.Height + it.rect.Height;
if (it.show)
{
PaintIt(g, it, fore, fore_active, fore_enabled, back_hover, back_active, radius);
if (!collapsed && (it.Expand || it.ExpandThread) && it.items != null && it.items.Count > 0)
{
if (ShowSubBack) g.FillRectangle(sub_bg, new RectangleF(rect.X, it.SubY, rect.Width, it.SubHeight));
var state = g.Save();
if (it.ExpandThread) g.SetClip(new RectangleF(rect.X, it.rect.Bottom, rect.Width, it.ExpandHeight * it.ExpandProg));
PaintItemExpand(g, rect, sy, it.items, fore, fore_active, fore_enabled, back_hover, back_active, radius);
g.Restore(state);
}
}
}
}
void PaintItemExpand(Graphics g, Rectangle rect, float sy, MenuItemCollection items, Color fore, Color fore_active, Color fore_enabled, Color back_hover, Color back_active, float radius)
{
foreach (var it in items)
{
it.show = it.Show && it.Visible && it.rect.Y > sy - rect.Height - (it.Expand ? it.SubHeight : 0) && it.rect.Bottom < sy + rect.Height + it.rect.Height;
if (it.show)
{
PaintIt(g, it, fore, fore_active, fore_enabled, back_hover, back_active, radius);
if (it.Expand && it.items != null && it.items.Count > 0)
{
PaintItemExpand(g, rect, sy, it.items, fore, fore_active, fore_enabled, back_hover, back_active, radius);
if (it.ExpandThread)
{
using (var brush = new SolidBrush(BackColor))
{
g.FillRectangle(brush, new RectangleF(rect.X, it.rect.Bottom + it.ExpandHeight * it.ExpandProg, rect.Width, it.ExpandHeight));
}
}
}
}
}
}
void PaintIt(Graphics g, MenuItem it, Color fore, Color fore_active, Color fore_enabled, Color back_hover, Color back_active, float radius)
{
if (collapsed) PaintItemMini(g, it, fore, fore_active, fore_enabled, back_hover, back_active, radius);
else PaintItem(g, it, fore, fore_active, fore_enabled, back_hover, back_active, radius);
}
void PaintItemMini(Graphics g, MenuItem it, Color fore, Color fore_active, Color fore_enabled, Color back_hover, Color back_active, float radius)
{
if (it.Enabled)
{
if (Config.IsDark || theme == TAMode.Dark)
{
if (it.Select)
{
PaintBack(g, back_active, it.rect, radius);
PaintIcon(g, it, fore_active);
}
else
{
if (it.AnimationHover)
{
PaintIcon(g, it, fore);
PaintIcon(g, it, Helper.ToColorN(it.AnimationHoverValue, back_hover));
}
else if (it.Hover) PaintIcon(g, it, back_hover);
else PaintIcon(g, it, fore);
}
}
else
{
if (it.Select)
{
PaintBack(g, back_active, it.rect, radius);
PaintIcon(g, it, fore_active);
}
else
{
if (it.AnimationHover) PaintBack(g, Helper.ToColorN(it.AnimationHoverValue, back_hover), it.rect, radius);
else if (it.Hover) PaintBack(g, back_hover, it.rect, radius);
PaintIcon(g, it, fore);
}
}
}
else
{
if (it.Select) PaintBack(g, back_active, it.rect, radius);
PaintIcon(g, it, fore_enabled);
}
}
void PaintItem(Graphics g, MenuItem it, Color fore, Color fore_active, Color fore_enabled, Color back_hover, Color back_active, float radius)
{
if (it.Enabled)
{
if (Config.IsDark || theme == TAMode.Dark)
{
if (it.Select)
{
if (it.CanExpand) PaintTextIconExpand(g, it, fore_active);
else
{
PaintBack(g, back_active, it.rect, radius);
PaintTextIcon(g, it, fore_active);
}
}
else
{
if (it.AnimationHover)
{
PaintTextIconExpand(g, it, fore);
PaintTextIconExpand(g, it, Helper.ToColorN(it.AnimationHoverValue, back_hover));
}
else if (it.Hover) PaintTextIconExpand(g, it, back_hover);
else PaintTextIconExpand(g, it, fore);
}
}
else
{
if (it.Select)
{
if (it.CanExpand) PaintTextIconExpand(g, it, fore_active);
else
{
PaintBack(g, back_active, it.rect, radius);
PaintTextIcon(g, it, fore_active);
}
}
else
{
if (it.AnimationHover) PaintBack(g, Helper.ToColorN(it.AnimationHoverValue, back_hover), it.rect, radius);
else if (it.Hover) PaintBack(g, back_hover, it.rect, radius);
PaintTextIconExpand(g, it, fore);
}
}
}
else
{
if (it.Select)
{
if (it.CanExpand)
{
using (var pen = new Pen(fore_active, 2F))
{
pen.StartCap = pen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
g.DrawLines(pen, it.arr_rect.TriangleLines(it.ArrowProg, .4F));
}
}
else PaintBack(g, back_active, it.rect, radius);
}
else if (it.CanExpand)
{
using (var pen = new Pen(fore_enabled, 2F))
{
pen.StartCap = pen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
g.DrawLines(pen, it.arr_rect.TriangleLines(it.ArrowProg, .4F));
}
}
PaintTextIcon(g, it, fore_enabled);
}
}
readonly StringFormat SL = Helper.SF_ALL(lr: StringAlignment.Near);
void PaintTextIcon(Graphics g, MenuItem it, Color fore)
{
using (var brush = new SolidBrush(fore))
{
g.DrawStr(it.Text, it.Font ?? Font, brush, it.txt_rect, SL);
}
PaintIcon(g, it, fore);
}
void PaintTextIconExpand(Graphics g, MenuItem it, Color fore)
{
if (it.CanExpand)
{
if (mode == TMenuMode.Inline)
{
using (var pen = new Pen(fore, 2F))
{
pen.StartCap = pen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
g.DrawLines(pen, it.arr_rect.TriangleLines(it.ArrowProg, .4F));
}
}
else if (mode == TMenuMode.Vertical)
{
using (var pen = new Pen(fore, 2F))
{
pen.StartCap = pen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
g.DrawLines(pen, TAlignMini.Right.TriangleLines(it.arr_rect, .4F));
}
}
}
using (var brush = new SolidBrush(fore))
{
g.DrawStr(it.Text, it.Font ?? Font, brush, it.txt_rect, SL);
}
PaintIcon(g, it, fore);
}
void PaintIcon(Graphics g, MenuItem it, Color fore)
{
if (it.Icon != null) g.DrawImage(it.Icon, it.ico_rect);
else if (it.IconSvg != null) g.GetImgExtend(it.IconSvg, it.ico_rect, fore);
}
void PaintBack(Graphics g, Color color, Rectangle rect, float radius)
{
using (var brush = new SolidBrush(color))
{
if (Round || radius > 0)
{
using (var path = rect.RoundPath(radius, Round))
{
g.FillPath(brush, path);
}
}
else g.FillRectangle(brush, rect);
}
}
#endregion
#region 鼠标
MenuItem? MDown = null;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (ScrollBar.MouseDown(e.Location))
{
if (items == null || items.Count == 0) return;
OnTouchDown(e.X, e.Y);
foreach (var it in items)
{
if (IMouseDown(items, it, e.Location)) return;
}
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (ScrollBar.MouseUp() && OnTouchUp())
{
if (items == null || items.Count == 0 || MDown == null) return;
foreach (var it in items)
{
var list = new List