// 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
{
///
/// Breadcrumb 面包屑
///
/// 显示当前页面在系统层级结构中的位置,并能向上返回。
[Description("Breadcrumb 面包屑")]
[ToolboxItem(true)]
[DefaultProperty("Items")]
[DefaultEvent("ItemClick")]
public class Breadcrumb : IControl
{
#region 属性
int gap = 12;
[Description("间距"), Category("外观"), DefaultValue(12)]
public int Gap
{
get => gap;
set
{
if (gap == value) return;
gap = value;
ChangeItems();
Invalidate();
}
}
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 = 4;
///
/// 圆角
///
[Description("圆角"), Category("外观"), DefaultValue(4)]
public int Radius
{
get => radius;
set
{
if (radius == value) return;
radius = value;
Invalidate();
}
}
BreadcrumbItemCollection? items;
///
/// 获取列表中所有列表项的集合
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Description("集合"), Category("数据"), DefaultValue(null)]
public BreadcrumbItemCollection Items
{
get
{
items ??= new BreadcrumbItemCollection(this);
return items;
}
set => items = value.BindData(this);
}
///
/// 点击项时发生
///
[Description("点击项时发生"), Category("行为")]
public event BreadcrumbItemEventHandler? ItemClick = null;
#region Change
protected override void OnSizeChanged(EventArgs e)
{
ChangeItems();
base.OnSizeChanged(e);
}
protected override void OnMarginChanged(EventArgs e)
{
ChangeItems();
base.OnMarginChanged(e);
}
protected override void OnPaddingChanged(EventArgs e)
{
ChangeItems();
base.OnPaddingChanged(e);
}
protected override void OnFontChanged(EventArgs e)
{
ChangeItems();
base.OnFontChanged(e);
}
#endregion
#region 布局
bool pauseLayout = false;
[Browsable(false), Description("暂停布局"), Category("行为"), DefaultValue(false)]
public bool PauseLayout
{
get => pauseLayout;
set
{
if (pauseLayout == value) return;
pauseLayout = value;
if (!value)
{
ChangeItems();
Invalidate();
}
}
}
Rectangle[] hs = new Rectangle[0];
internal void ChangeItems()
{
if (items == null || items.Count == 0) return;
if (pauseLayout) return;
var _rect = ClientRectangle.PaddingRect(Padding);
if (_rect.Width == 0 || _rect.Height == 0) return;
var rect = _rect.PaddingRect(Margin);
hs = Helper.GDI(g =>
{
var hs = new List(items.Count);
var size_t = g.MeasureString(Config.NullText, Font).Size();
int sp = (int)(4 * Config.Dpi), sp2 = sp * 2, imgsize = (int)(size_t.Height * .8F), h = size_t.Height + sp, y = rect.Y + (rect.Height - h) / 2, y_img = rect.Y + (rect.Height - imgsize) / 2, _gap = (int)(gap * Config.Dpi);
int x = 0, tmpx = 0;
foreach (BreadcrumbItem it in items)
{
it.PARENT = this;
if (it.Text == null || string.IsNullOrEmpty(it.Text))
{
var Rect = new Rectangle(rect.X + x, y, imgsize + sp2, h);
if (it.HasIcon)
{
it.Rect = it.RectText = Rect;
it.RectImg = new Rectangle(Rect.X + sp, y_img, imgsize, imgsize);
}
else it.Rect = it.RectText = Rect;
}
else
{
var size = g.MeasureString(it.Text, Font).Size();
if (it.HasIcon)
{
int imgw = imgsize + sp2;
var Rect = new Rectangle(rect.X + x, y, size.Width + sp + imgsize + sp2, h);
it.Rect = Rect;
it.RectImg = new Rectangle(Rect.X + sp, y_img, imgsize, imgsize);
it.RectText = new Rectangle(it.RectImg.Right + sp, Rect.Y, Rect.Width - imgw - sp2, Rect.Height);
}
else it.Rect = it.RectText = new Rectangle(rect.X + x, y, size.Width + sp2, h);
}
x += it.Rect.Width + _gap;
if (tmpx > 0)
hs.Add(new Rectangle(tmpx - _gap + sp, y, _gap, h));
tmpx = x;
}
return hs.ToArray();
});
}
#endregion
#endregion
#region 渲染
readonly StringFormat s_f = Helper.SF_ALL();
protected override void OnPaint(PaintEventArgs e)
{
if (items == null || items.Count == 0) return;
var g = e.Graphics.High();
float _radius = radius * Config.Dpi;
using (var brush = new SolidBrush(fore ?? Style.Db.TextSecondary))
using (var brush_active = new SolidBrush(ForeActive ?? Style.Db.Text))
{
foreach (var it in hs) g.DrawStr("/", Font, brush, it, s_f);
for (int i = 0; i < items.Count; i++)
{
var it = items[i];
if (it == null) continue;
if (i == items.Count - 1)
{
//最后一个
PaintImg(g, it, brush_active.Color, it.IconSvg, it.Icon);
g.DrawStr(it.Text, Font, brush_active, it.RectText, s_f);
}
else
{
if (it.Hover)
{
using (var path = it.Rect.RoundPath(_radius))
{
using (var brush_hover = new SolidBrush(Style.Db.FillSecondary))
{
g.FillPath(brush_hover, path);
}
}
PaintImg(g, it, brush_active.Color, it.IconSvg, it.Icon);
g.DrawStr(it.Text, Font, brush_active, it.RectText, s_f);
}
else
{
PaintImg(g, it, brush.Color, it.IconSvg, it.Icon);
g.DrawStr(it.Text, Font, brush, it.RectText, s_f);
}
}
}
}
this.PaintBadge(g);
base.OnPaint(e);
}
bool PaintImg(Graphics g, BreadcrumbItem it, Color color, string? svg, Image? bmp)
{
if (svg != null)
{
if (g.GetImgExtend(svg, it.RectImg, color)) return false;
}
else if (bmp != null) { g.DrawImage(bmp, it.RectImg); return false; }
return true;
}
#endregion
#region 鼠标
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (items == null || items.Count == 0) return;
int hand = 0, change = 0;
foreach (BreadcrumbItem it in items)
{
bool hover = it.Rect.Contains(e.Location);
if (it.Hover != hover)
{
it.Hover = hover;
change++;
}
if (it.Hover) hand++;
}
SetCursor(hand > 0);
if (change > 0) Invalidate();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
SetCursor(false);
if (items == null || items.Count == 0) return;
int change = 0;
foreach (BreadcrumbItem it in items)
{
if (it.Hover)
{
it.Hover = false;
change++;
}
}
if (change > 0) Invalidate();
}
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
SetCursor(false);
if (items == null || items.Count == 0) return;
int change = 0;
foreach (BreadcrumbItem it in items)
{
if (it.Hover)
{
it.Hover = false;
change++;
}
}
if (change > 0) Invalidate();
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
if (items == null || items.Count == 0) return;
for (int i = 0; i < items.Count; i++)
{
var it = items[i];
if (it != null && it.Rect.Contains(e.Location))
{
ItemClick?.Invoke(this, new BreadcrumbItemEventArgs(it, e));
return;
}
}
}
#endregion
}
public class BreadcrumbItemCollection : iCollection
{
public BreadcrumbItemCollection(Breadcrumb it)
{
BindData(it);
}
internal BreadcrumbItemCollection BindData(Breadcrumb it)
{
action = render =>
{
if (render) it.ChangeItems();
it.Invalidate();
};
return this;
}
}
public class BreadcrumbItem
{
///
/// ID
///
public string? ID { get; set; }
Image? icon = null;
///
/// 图标
///
[Description("图标"), Category("外观"), DefaultValue(null)]
public Image? Icon
{
get => icon;
set
{
if (icon == value) return;
icon = value;
Invalidates();
}
}
string? iconsvg = null;
///
/// 图标SVG
///
[Description("图标SVG"), Category("外观"), DefaultValue(null)]
public string? IconSvg
{
get => iconsvg;
set
{
if (iconsvg == value) return;
iconsvg = value;
Invalidates();
}
}
///
/// 是否包含图标
///
public bool HasIcon
{
get => IconSvg != null || Icon != null;
}
string? text = null;
///
/// 文本
///
[Description("文本"), Category("外观"), DefaultValue(null)]
public string? Text
{
get => text;
set
{
if (text == value) return;
text = value;
Invalidates();
}
}
///
/// 用户定义数据
///
[Description("用户定义数据"), Category("数据"), DefaultValue(null)]
public object? Tag { get; set; }
internal bool Hover { get; set; }
internal Rectangle Rect { get; set; }
internal Rectangle RectImg { get; set; }
internal Rectangle RectText { get; set; }
internal Breadcrumb? PARENT { get; set; }
void Invalidates()
{
if (PARENT == null) return;
PARENT.ChangeItems();
PARENT.Invalidate();
}
public override string? ToString() => text;
}
}