// 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.Drawing; using System.Drawing.Drawing2D; using System.Threading; using System.Windows.Forms; namespace AntdUI { internal class LayeredFormContextMenuStrip : ILayeredFormOpacity, SubLayeredForm { ContextMenuStrip.Config config; public override bool MessageEnable => true; public override bool MessageCloseSub => true; Font FontSub; float radius = 0; public LayeredFormContextMenuStrip(ContextMenuStrip.Config _config) { if (_config.TopMost) { PARENT = this; Helper.SetTopMost(Handle); MessageCloseMouseLeave = true; } else _config.Control.SetTopMost(Handle); var point = _config.Location ?? MousePosition; maxalpha = 250; config = _config; Font = config.Font ?? config.Control.Font; FontSub = Font; rectsContent = Init(config.Items); scrollY = new ScrollY(this); switch (config.Align) { case TAlign.BL: case TAlign.LB: point.X -= TargetRect.Width; point.Offset(10, -10); break; case TAlign.TL: case TAlign.LT: point.X -= TargetRect.Width; point.Y -= TargetRect.Height; point.Offset(10, 10); break; case TAlign.Left: point.X -= TargetRect.Width; point.Y -= TargetRect.Height / 2; point.Offset(10, 0); break; case TAlign.Right: point.Y -= TargetRect.Height / 2; point.Offset(-10, 0); break; case TAlign.Top: point.X -= TargetRect.Width / 2; point.Y -= TargetRect.Height; point.Offset(0, 10); break; case TAlign.Bottom: point.X -= TargetRect.Width / 2; point.Offset(0, -10); break; case TAlign.TR: case TAlign.RT: point.Y -= TargetRect.Height; point.Offset(-10, 10); break; case TAlign.BR: case TAlign.RB: default: point.Offset(-10, -10); break; } Init(point); KeyCall = keys => { if (keys == Keys.Escape) { IClose(); return true; } if (keys == Keys.Enter) { if (select_index > -1) { var it = rectsContent[select_index]; if (ClickItem(it)) return true; } } else if (keys == Keys.Up) { select_index--; if (select_index < 0) select_index = rectsContent.Length - 1; while (rectsContent[select_index].Tag == null) { select_index--; if (select_index < 0) select_index = rectsContent.Length - 1; } foreach (var it in rectsContent) it.Hover = false; FocusItem(rectsContent[select_index]); return true; } else if (keys == Keys.Down) { if (select_index == -1) select_index = 0; else { select_index++; if (select_index > rectsContent.Length - 1) select_index = 0; } while (rectsContent[select_index].Tag == null) { select_index++; if (select_index > rectsContent.Length - 1) select_index = 0; } foreach (var it in rectsContent) it.Hover = false; FocusItem(rectsContent[select_index]); return true; } else if (keys == Keys.Left) { IClose(); return true; } else if (keys == Keys.Right) { if (select_index > -1) { var it = rectsContent[select_index]; if (it.Tag != null && it.Tag.Sub != null && it.Tag.Sub.Length > 0) { if (subForm == null) { subForm = new LayeredFormContextMenuStrip(config, this, new Point(TargetRect.X + (it.Rect.X + it.Rect.Width) - 20, TargetRect.Y + it.Rect.Y - 20 - (scrollY.Show ? (int)scrollY.Value : 0)), it.Tag.Sub); subForm.Show(this); } else { subForm?.IClose(); subForm = null; } return true; } } return true; } return false; }; } ScrollY scrollY; public LayeredFormContextMenuStrip(ContextMenuStrip.Config _config, LayeredFormContextMenuStrip parent, Point point, IContextMenuStripItem[] subs) { PARENT = parent; MessageCloseMouseLeave = true; maxalpha = 250; config = _config; Font = config.Font ?? config.Control.Font; FontSub = Font; if (_config.TopMost) Helper.SetTopMost(Handle); else _config.Control.SetTopMost(Handle); rectsContent = Init(subs); scrollY = new ScrollY(this); Init(point); } void Init(Point point) { var screen = Screen.FromPoint(point).WorkingArea; if (point.X < screen.X) point.X = screen.X; else if (point.X > (screen.X + screen.Width) - TargetRect.Width) point.X = screen.X + screen.Width - TargetRect.Width; if (TargetRect.Height > screen.Height) { int gap_y = rectsContent[0].y / 2 / 2, vr = TargetRect.Height, height = screen.Height; scrollY.Rect = new Rectangle(TargetRect.Width - gap_y - scrollY.SIZE, 10 + gap_y, scrollY.SIZE, height - 20 - gap_y * 2); scrollY.Show = true; scrollY.SetVrSize(vr, height); SetSizeH(height); } if (point.Y < screen.Y) point.Y = screen.Y; else if (point.Y > (screen.Y + screen.Height) - TargetRect.Height) point.Y = screen.Y + screen.Height - TargetRect.Height; SetLocation(point); } bool has_subtext = false; InRect[] Init(IContextMenuStripItem[] Items) { return Helper.GDI(g => { var dpi = Config.Dpi; radius = (int)(config.Radius * dpi); int split = (int)Math.Round(1F * dpi), sp = (int)Math.Round(8F * dpi), spm = sp / 2, padding = (int)Math.Round(16 * dpi), padding2 = padding * 2; Padding = new Padding(padding); var _rectsContent = new List(Items.Length); int usew = 0, useh = 0, has_icon = 0, has_checked = 0, has_sub = 0; foreach (var it in Items) { if (it is ContextMenuStripItem item) { if (!has_subtext && item.SubText != null) has_subtext = true; var size = g.MeasureString(item.Text + item.SubText, Font); int w = (int)Math.Ceiling(size.Width), hc = (int)Math.Ceiling(size.Height), h = hc + sp; if (has_sub == 0 && (item.Sub != null && item.Sub.Length > 0)) has_sub = hc; if (has_icon == 0 && (item.Icon != null || item.IconSvg != null)) has_icon = (int)(hc * 0.68F); if (has_checked == 0 && item.Checked) has_checked = (int)(hc * 0.8F); if (w > usew) usew = w; _rectsContent.Add(new InRect(item, padding + useh, h)); useh += h + spm; } else if (it is ContextMenuStripItemDivider divider) { _rectsContent.Add(new InRect(padding + useh, sp)); useh += sp; } } if (has_subtext) FontSub = new Font(FontSub.FontFamily, FontSub.Size * 0.8F); int use_r; if (has_icon > 0 || has_checked > 0) { if (has_icon > 0 && has_checked > 0) use_r = has_icon + has_checked + spm * 3; else if (has_icon > 0) use_r = has_icon + spm; else use_r = has_checked + spm; } else use_r = 0; int sp2 = sp * 2, x = padding + use_r; usew += use_r; int readw = usew + has_sub + padding2 + sp2; foreach (var it in _rectsContent) { if (it.Tag == null) it.Rect = new Rectangle(10, it.y + (it.h - split) / 2, readw - 20, split); else { it.Rect = new Rectangle(padding, it.y, usew + has_sub + sp2, it.h); if (it.Tag.Sub != null && it.Tag.Sub.Length > 0) it.RectSub = new Rectangle(it.Rect.Right - spm - has_sub, it.y + (it.h - has_sub) / 2, has_sub, has_sub); int usex = padding + spm; if (has_checked > 0) { if (it.Tag.Checked) it.RectCheck = new Rectangle(usex + spm, it.y + (it.h - has_checked) / 2, has_checked, has_checked); usex += has_checked + sp; it.RectT = new Rectangle(x + sp, it.y, usew - use_r - spm, it.h); } else it.RectT = new Rectangle(x + sp, it.y, usew - use_r, it.h); if (has_icon > 0 && it.Tag.Icon != null || it.Tag.IconSvg != null) it.RectIcon = new Rectangle(usex + spm, it.y + (it.h - has_icon) / 2, has_icon, has_icon); } } SetSize(readw, useh - spm + padding2); return _rectsContent.ToArray(); }); } protected override void OnDeactivate(EventArgs e) { IClose(); subForm?.IClose(); subForm = null; base.OnDeactivate(e); } protected override void Dispose(bool disposing) { if (has_subtext) FontSub.Dispose(); subForm?.IClose(); subForm = null; resetEvent?.Set(); resetEvent?.Dispose(); resetEvent = null; base.Dispose(disposing); } InRect[] rectsContent; #region 渲染 readonly StringFormat stringLeft = Helper.SF_Ellipsis(lr: StringAlignment.Near); readonly StringFormat stringRight = Helper.SF_Ellipsis(lr: StringAlignment.Far); public override Bitmap PrintBit() { var rect = TargetRectXY; var rect_read = new Rectangle(10, 10, rect.Width - 20, rect.Height - 20); Bitmap original_bmp = new Bitmap(rect.Width, rect.Height); using (var g = Graphics.FromImage(original_bmp).High()) { using (var path_sh = DrawShadow(g, rect, rect_read)) { using (var brush = new SolidBrush(Style.Db.BgElevated)) { g.FillPath(brush, path_sh); } using (var brush = new SolidBrush(Style.Db.Text)) using (var brushSplit = new SolidBrush(Style.Db.Split)) using (var brushSecondary = new SolidBrush(Style.Db.TextSecondary)) using (var brushEnabled = new SolidBrush(Style.Db.TextQuaternary)) { if (scrollY.Show) { g.SetClip(path_sh); g.TranslateTransform(0, -scrollY.Value); } foreach (var it in rectsContent) { if (it.Tag == null) g.FillRectangle(brushSplit, it.Rect); else { if (it.Hover) { using (var path = Helper.RoundPath(it.Rect, radius)) { using (var brush_hover = new SolidBrush(Style.Db.PrimaryBg)) { g.FillPath(brush_hover, path); } } } if (it.Tag.Enabled) { g.DrawStr(it.Tag.SubText, FontSub, brushSecondary, it.RectT, stringRight); if (it.Tag.Fore.HasValue) { using (var brush_fore = new SolidBrush(it.Tag.Fore.Value)) { g.DrawStr(it.Tag.Text, Font, brush_fore, it.RectT, stringLeft); } } else g.DrawStr(it.Tag.Text, Font, brush, it.RectT, stringLeft); if (it.Tag.Sub != null && it.Tag.Sub.Length > 0) { using (var pen = new Pen(Style.Db.TextSecondary, 2F * Config.Dpi)) { pen.StartCap = pen.EndCap = LineCap.Round; g.DrawLines(pen, TAlignMini.Right.TriangleLines(it.RectSub)); } } if (it.Tag.Checked) { using (var pen = new Pen(Style.Db.Primary, 3F * Config.Dpi)) { g.DrawLines(pen, PaintArrow(it.RectCheck)); } } if (it.Tag.IconSvg != null) { using (var bmp = it.Tag.IconSvg.SvgToBmp(it.RectIcon.Width, it.RectIcon.Height, it.Tag.Fore ?? brush.Color)) { if (bmp != null) g.DrawImage(bmp, it.RectIcon); } } else if (it.Tag.Icon != null) g.DrawImage(it.Tag.Icon, it.RectIcon); } else { g.DrawStr(it.Tag.SubText, FontSub, brushEnabled, it.RectT, stringRight); g.DrawStr(it.Tag.Text, Font, brushEnabled, it.RectT, stringLeft); if (it.Tag.Sub != null && it.Tag.Sub.Length > 0) { using (var pen = new Pen(Style.Db.TextQuaternary, 2F * Config.Dpi)) { pen.StartCap = pen.EndCap = LineCap.Round; g.DrawLines(pen, TAlignMini.Right.TriangleLines(it.RectSub)); } } if (it.Tag.Checked) { using (var pen = new Pen(Style.Db.Primary, 3F * Config.Dpi)) { g.DrawLines(pen, PaintArrow(it.RectCheck)); } } if (it.Tag.IconSvg != null) { using (var bmp = it.Tag.IconSvg.SvgToBmp(it.RectIcon.Width, it.RectIcon.Height, brushEnabled.Color)) { if (bmp != null) g.DrawImage(bmp, it.RectIcon); } } else if (it.Tag.Icon != null) g.DrawImage(it.Tag.Icon, it.RectIcon); } } } g.ResetTransform(); g.ResetClip(); scrollY.Paint(g); } } } return original_bmp; } internal PointF[] PaintArrow(Rectangle rect) { float size = rect.Height * 0.15F, size2 = rect.Height * 0.2F, size3 = rect.Height * 0.26F; return new PointF[] { new PointF(rect.X+size,rect.Y+rect.Height/2), new PointF(rect.X+rect.Width*0.4F,rect.Y+(rect.Height-size3)), new PointF(rect.X+rect.Width-size2,rect.Y+size2), }; } Bitmap? shadow_temp = null; /// /// 绘制阴影 /// /// GDI /// 客户区域 /// 真实区域 GraphicsPath DrawShadow(Graphics g, Rectangle rect_client, Rectangle rect_read) { var path = rect_read.RoundPath(radius); if (Config.ShadowEnabled) { if (shadow_temp == null || (shadow_temp.Width != rect_client.Width || shadow_temp.Height != rect_client.Height)) { shadow_temp?.Dispose(); shadow_temp = path.PaintShadow(rect_client.Width, rect_client.Height); } g.DrawImage(shadow_temp, rect_client, 0.2F); } return path; } #endregion #region 鼠标 int select_index = -1; protected override void OnMouseDown(MouseEventArgs e) { if (scrollY.MouseDown(e.Location)) { OnTouchDown(e.X, e.Y); select_index = -1; if (e.Button == MouseButtons.Left) { int y = scrollY.Show ? (int)scrollY.Value : 0; for (int i = 0; i < rectsContent.Length; i++) { var it = rectsContent[i]; if (it.Tag != null && it.Tag.Enabled && it.Rect.Contains(e.X, e.Y + y)) { select_index = i; it.Down = true; base.OnMouseDown(e); return; } } } base.OnMouseDown(e); } } protected override void OnMouseUp(MouseEventArgs e) { if (scrollY.MouseUp(e.Location) && OnTouchUp()) { int y = scrollY.Show ? (int)scrollY.Value : 0; foreach (var it in rectsContent) { if (it.Down) { if (it.Rect.Contains(e.X, e.Y + y)) { if (it.Tag != null) { if (it.Tag.Sub == null || it.Tag.Sub.Length == 0) { IClose(); LayeredFormContextMenuStrip item = this; while (item.PARENT is LayeredFormContextMenuStrip form && item.PARENT != form) { form.IClose(); item = form; } resetEvent = new ManualResetEvent(false); ITask.Run(() => { if (Config.Animation && resetEvent.Wait()) return; if (config.CallSleep > 0) Thread.Sleep(config.CallSleep); config.Control.BeginInvoke(new Action(() => { config.Call(it.Tag); })); }); } } } it.Down = false; return; } } } base.OnMouseUp(e); } bool ClickItem(InRect it) { if (it.Tag != null) { if (it.Tag.Sub == null || it.Tag.Sub.Length == 0) { IClose(); LayeredFormContextMenuStrip item = this; while (item.PARENT is LayeredFormContextMenuStrip form) { form.IClose(); item = form; } resetEvent = new ManualResetEvent(false); ITask.Run(() => { if (resetEvent.Wait()) return; if (config.CallSleep > 0) Thread.Sleep(config.CallSleep); config.Control.BeginInvoke(new Action(() => { config.Call(it.Tag); })); }); } else { if (subForm == null) { subForm = new LayeredFormContextMenuStrip(config, this, new Point(TargetRect.X + (it.Rect.X + it.Rect.Width) - 20, TargetRect.Y + it.Rect.Y - 20 - (scrollY.Show ? (int)scrollY.Value : 0)), it.Tag.Sub); subForm.Show(this); } else { subForm?.IClose(); subForm = null; } } return true; } return false; } void FocusItem(InRect it) { if (it.SetHover(true)) { if (scrollY.Show) scrollY.Value = it.Rect.Y - it.Rect.Height; Print(); } } int oldSub = -1; protected override void OnMouseMove(MouseEventArgs e) { if (scrollY.MouseMove(e.Location) && OnTouchMove(e.X, e.Y)) { int count = 0, hand = -1; int y = scrollY.Show ? (int)scrollY.Value : 0; for (int i = 0; i < rectsContent.Length; i++) { var it = rectsContent[i]; if (it.Tag == null) continue; if (it.Tag.Enabled) { bool hover = it.Rect.Contains(e.X, e.Y + y); if (hover) hand = i; if (it.Hover != hover) { it.Hover = hover; count++; } } else { if (it.Hover != false) { it.Hover = false; count++; } } } SetCursor(hand > 0); if (count > 0) Print(); select_index = hand; if (hand > -1) { SetCursor(true); if (oldSub == hand) return; var it = rectsContent[hand]; oldSub = hand; subForm?.IClose(); subForm = null; if (it.Tag != null && it.Tag.Sub != null && it.Tag.Sub.Length > 0) { subForm = new LayeredFormContextMenuStrip(config, this, new Point(TargetRect.X + (it.Rect.X + it.Rect.Width) - 20, TargetRect.Y + it.Rect.Y - 20 - (scrollY.Show ? (int)scrollY.Value : 0)), it.Tag.Sub); subForm.Show(this); } } else { oldSub = -1; subForm?.IClose(); subForm = null; SetCursor(false); } } base.OnMouseMove(e); } protected override void OnMouseWheel(MouseEventArgs e) { scrollY.MouseWheel(e.Delta); base.OnMouseWheel(e); } protected override bool OnTouchScrollY(int value) => scrollY.MouseWheel(value); ManualResetEvent? resetEvent; LayeredFormContextMenuStrip? subForm = null; ILayeredForm? SubLayeredForm.SubForm() => subForm; #endregion class InRect { public InRect(ContextMenuStripItem tag, int _y, int _h) { Tag = tag; y = _y; h = _h; } public InRect(int _y, int _h) { y = _y; h = _h; } public ContextMenuStripItem? Tag { get; set; } public bool Hover { get; set; } public bool Down { get; set; } public Rectangle RectT { get; set; } public Rectangle RectIcon { get; set; } public Rectangle RectCheck { get; set; } public Rectangle RectSub { get; set; } public Rectangle Rect { get; set; } public int y { get; set; } public int h { get; set; } internal bool SetHover(bool val) { bool change = false; if (val) { if (!Hover) change = true; Hover = true; } else { if (Hover) change = true; Hover = false; } return change; } } } }