refactor: 移除 CueBanner 类及相关按钮,优化界面布局
- 删除 CueBanner.cs 文件,移除输入框的占位提示功能。 - 更新 MainForm.cs 和 MainFormUi.cs,删除与 Browse 按钮相关的代码,简化用户界面。 - 调整控件布局以提升整体用户体验,确保界面更加简洁。
This commit is contained in:
+269
-11
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TfsExcelImporter
|
||||
@@ -149,10 +151,64 @@ namespace TfsExcelImporter
|
||||
return Card;
|
||||
}
|
||||
|
||||
public static int TextWidth(string text, Font font) {
|
||||
private static readonly Dictionary<int, SolidBrush> _eraseBrushes = new Dictionary<int, SolidBrush>();
|
||||
|
||||
/// <summary>
|
||||
/// WM_ERASEBKGND 的快速擦除:直接用 GDI 把客户区铺成底色,不经过双缓冲、不建 GraphicsPath。
|
||||
/// 自绘控件为了不闪都不擦背景,代价是窗口被重新暴露(还原、切前台)时那块像素保持系统的黑色,
|
||||
/// 要等完整首帧(实测整窗 100ms+)才盖掉——这就是「黑色界面闪现」的来源。
|
||||
/// 这里先用一次 FillRect 换掉黑底:即使完整首帧还没来,用户看到的也只是和界面同色的浅底。
|
||||
/// </summary>
|
||||
public static bool FastErase(IntPtr hwnd, IntPtr hdc, Color color)
|
||||
{
|
||||
RECT rc;
|
||||
if (color.A == 0 || !GetClientRect(hwnd, out rc) || rc.right <= 0 || rc.bottom <= 0) return false;
|
||||
int key = color.ToArgb();
|
||||
SolidBrush brush;
|
||||
if (!_eraseBrushes.TryGetValue(key, out brush))
|
||||
{
|
||||
brush = new SolidBrush(color);
|
||||
_eraseBrushes[key] = brush;
|
||||
}
|
||||
var g = Graphics.FromHdc(hdc);
|
||||
try { g.FillRectangle(brush, 0, 0, rc.right, rc.bottom); }
|
||||
finally { g.Dispose(); }
|
||||
return true;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct RECT { public int left, top, right, bottom; }
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetClientRect(IntPtr hWnd, out RECT rc);
|
||||
|
||||
private static readonly Dictionary<string, int> _textWidths = new Dictionary<string, int>();
|
||||
|
||||
public static int TextWidth(string text, Font font)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return 0;
|
||||
return TextRenderer.MeasureText(text, font, Size.Empty,
|
||||
// 排版会反复量同一批文案(拖拽缩放每帧都要全量量一遍),而 MeasureText 每次都要建 DC、选字体,
|
||||
// 和查一次字典差两个数量级。字体是静态单例,键里带字体名/字号/字形就足以区分。
|
||||
string key = font.Name + "|" + font.SizeInPoints.ToString("F1") + "|" + (int)font.Style + "|" + text;
|
||||
int w;
|
||||
if (_textWidths.TryGetValue(key, out w)) return w;
|
||||
w = TextRenderer.MeasureText(text, font, Size.Empty,
|
||||
TextFormatFlags.NoPadding | TextFormatFlags.SingleLine).Width;
|
||||
if (_textWidths.Count > 800) _textWidths.Clear(); // 日志摘要/路径是动态文案,别让它无限涨
|
||||
_textWidths[key] = w;
|
||||
return w;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 位置尺寸都没变就不动。每次 SetBounds 都是一次 WM_WINDOWPOSCHANGED 加一次重绘,
|
||||
/// 而重排里绝大多数控件其实待在原地——打字、状态刷新这类重排尤其如此,
|
||||
/// 拦掉空转会把这些场景的 70~100ms 直接抹平。
|
||||
/// </summary>
|
||||
public static void Place(Control c, int x, int y, int w, int h)
|
||||
{
|
||||
var b = c.Bounds;
|
||||
if (b.X == x && b.Y == y && b.Width == w && b.Height == h) return;
|
||||
c.SetBounds(x, y, w, h);
|
||||
}
|
||||
|
||||
public static Color ChipInk(ChipKind kind)
|
||||
@@ -393,6 +449,8 @@ namespace TfsExcelImporter
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
var r = new RectangleF(0.5f, 0.5f, Width - 1f, Height - 1f);
|
||||
using (var b = new SolidBrush(fill)) g.FillEllipse(b, r);
|
||||
if (Kind == BadgeKind.Pending)
|
||||
using (var p = new Pen(Theme.BorderStrong, Theme.Hair)) g.DrawEllipse(p, r);
|
||||
g.SmoothingMode = old;
|
||||
// 勾画在圆内缩 4px(14px 盒子 / 22px 圆),铺满整圆会贴边看着像被裁掉
|
||||
if (Kind == BadgeKind.Done) Icons.Draw(g, IconKind.Check, Icons.BadgeGlyph(ClientRectangle), ink);
|
||||
@@ -423,6 +481,16 @@ namespace TfsExcelImporter
|
||||
TabStop = false;
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
if (m.Msg == 0x0014 && Theme.FastErase(m.HWnd, m.WParam, BackColor))
|
||||
{
|
||||
m.Result = (IntPtr)1;
|
||||
return;
|
||||
}
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
Theme.FillPanel(e.Graphics, new Rectangle(0, 0, Width, Height), _face, LineColor,
|
||||
@@ -446,6 +514,8 @@ namespace TfsExcelImporter
|
||||
public bool ShowBadge = true;
|
||||
/// <summary>卡片头部右侧留给按钮的宽度,摘要文字不越过它。</summary>
|
||||
public int HeaderReserve = 0;
|
||||
/// <summary>头部摘要按「圆点 + 短语」分段绘制(状态摘要样式);默认纯文本。</summary>
|
||||
public bool SummaryDots = false;
|
||||
public int HeaderH = 44;
|
||||
public int BodyH = 0;
|
||||
public Action<bool> OnToggle;
|
||||
@@ -461,6 +531,17 @@ namespace TfsExcelImporter
|
||||
HeaderH = Theme.Px(44);
|
||||
}
|
||||
|
||||
// 卡片占了大半客户区,窗口重新暴露时它不擦背景就是一大片黑——和 MainForm 一样先铺面色。
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
if (m.Msg == 0x0014 && Theme.FastErase(m.HWnd, m.WParam, BackColor))
|
||||
{
|
||||
m.Result = (IntPtr)1;
|
||||
return;
|
||||
}
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
public Rectangle BodyRect { get { return new Rectangle(0, HeaderH, Width, BodyH); } }
|
||||
|
||||
private Rectangle HeaderRect { get { return new Rectangle(0, 0, Width, HeaderH); } }
|
||||
@@ -570,6 +651,10 @@ namespace TfsExcelImporter
|
||||
var old = g.SmoothingMode;
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
using (var b = new SolidBrush(badgeFill)) g.FillEllipse(b, badge);
|
||||
// 未激活徽章加一圈浅描边:纯灰底在白卡上存在感太弱,描边后与「激活/完成」的实心徽章同属一套语义
|
||||
if (Badge == BadgeKind.Pending)
|
||||
using (var p = new Pen(Theme.BorderStrong, Theme.Hair))
|
||||
g.DrawEllipse(p, badge.X + 0.5f, badge.Y + 0.5f, badge.Width - 1f, badge.Height - 1f);
|
||||
g.SmoothingMode = old;
|
||||
if (Badge == BadgeKind.Done) Icons.Draw(g, IconKind.Check, Icons.BadgeGlyph(badge), badgeInk);
|
||||
else
|
||||
@@ -588,11 +673,15 @@ namespace TfsExcelImporter
|
||||
| TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
|
||||
|
||||
int sx = titleRect.Right + Theme.Px(12);
|
||||
var sumRect = new Rectangle(sx, 0, Math.Max(0, Width - chevW - sx - Theme.Px(12)), HeaderH);
|
||||
TextRenderer.DrawText(g, Summary, Theme.Caption, sumRect,
|
||||
Invalid ? Theme.Danger : Theme.Ink2,
|
||||
TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding
|
||||
| TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
|
||||
int sumW = Math.Max(0, Width - chevW - sx - Theme.Px(12));
|
||||
if (SummaryDots)
|
||||
DrawDotSummary(g, Summary, sx, sumW);
|
||||
else
|
||||
TextRenderer.DrawText(g, Summary, Theme.Caption,
|
||||
new Rectangle(sx, 0, sumW, HeaderH),
|
||||
Invalid ? Theme.Danger : Theme.Ink2,
|
||||
TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding
|
||||
| TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
|
||||
|
||||
if (Collapsible)
|
||||
{
|
||||
@@ -605,6 +694,40 @@ namespace TfsExcelImporter
|
||||
using (var p = new Pen(Theme.Border))
|
||||
g.DrawLine(p, Theme.S16, HeaderH, Width - Theme.S16, HeaderH);
|
||||
}
|
||||
|
||||
/// <summary>「圆点 + 短语」状态摘要:按 " · " 分段,每段一枚圆点;放不下的段整段省略。</summary>
|
||||
private void DrawDotSummary(Graphics g, string summary, int x, int availW)
|
||||
{
|
||||
if (string.IsNullOrEmpty(summary) || availW <= 0) return;
|
||||
var segments = summary.Split(new[] { " · " }, StringSplitOptions.None);
|
||||
int dot = Theme.Px(6);
|
||||
int dotGap = Theme.Px(6);
|
||||
int segGap = Theme.Px(14);
|
||||
int cursor = x;
|
||||
int limit = x + availW;
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (cursor + dot + dotGap > limit) break;
|
||||
var oldSmoothing = g.SmoothingMode;
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
using (var b = new SolidBrush(Invalid ? Theme.Danger : Theme.Ink3))
|
||||
g.FillEllipse(b, cursor, (HeaderH - dot) / 2, dot, dot);
|
||||
g.SmoothingMode = oldSmoothing;
|
||||
cursor += dot + dotGap;
|
||||
int remain = limit - cursor;
|
||||
if (remain <= 0) break;
|
||||
int tw = Theme.TextWidth(segment, Theme.Caption);
|
||||
bool truncated = tw > remain;
|
||||
var flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding
|
||||
| TextFormatFlags.SingleLine;
|
||||
if (truncated) flags |= TextFormatFlags.EndEllipsis;
|
||||
TextRenderer.DrawText(g, segment, Theme.Caption,
|
||||
new Rectangle(cursor, 0, Math.Min(tw + Theme.Px(2), remain), HeaderH),
|
||||
Invalid ? Theme.Danger : Theme.Ink2, flags);
|
||||
cursor += Math.Min(tw, remain) + segGap;
|
||||
if (truncated) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal enum BtnKind { Secondary, Primary, Ghost }
|
||||
@@ -831,14 +954,25 @@ namespace TfsExcelImporter
|
||||
private bool _invalid;
|
||||
private ComboBox _combo;
|
||||
private Chevron _chevron;
|
||||
private SuffixIcon _suffix;
|
||||
private Control _content; // 主内容控件(TextBox/ComboBox),占位层据此判定显隐
|
||||
private CueLabel _cue; // 自绘占位提示:原生 EM_SETCUEBANNER 控制不了颜色/字号/对齐
|
||||
// 内容容器:整体内缩 2px 并按圆角 Region 裁剪——恰好让开聚焦态 2px 描边带,
|
||||
// 否则白底会切掉描边的内半圈,只剩外圈 AA 残边(视觉上就是一圈毛刺)。
|
||||
// 子控件(尤其被挤出宿主藏箭头的 ComboBox)再怎么越界,也只会被裁在描边圈以内。
|
||||
private Panel _box;
|
||||
/// <summary>上一次 LayoutInner 时的宿主尺寸:尺寸没变就不用重摆子控件、更不用重建圆角 Region。</summary>
|
||||
private Size _innerSize = new Size(-1, -1);
|
||||
|
||||
/// <summary>宿主右侧留给自绘箭头的区域,同时也是点击展开下拉的热区。</summary>
|
||||
private int ArrowZone { get { return Theme.Px(26); } }
|
||||
|
||||
/// <summary>内容右侧的固定预留:组合框是箭头热区,文本框是尾部图标按钮(没有则 0)。</summary>
|
||||
private int RightReserve
|
||||
{
|
||||
get { return _chevron != null ? ArrowZone : (_suffix != null ? Theme.Px(26) : 0); }
|
||||
}
|
||||
|
||||
/// <summary>内容左缘相对 _box 的缩进。TextBox 就摆在 Px(7),组合框闭合态文字必须共用同一基准,
|
||||
/// 否则同一排的输入框文字会错开一条竖线。</summary>
|
||||
private static int TextInsetX { get { return Theme.Px(7); } }
|
||||
@@ -896,16 +1030,56 @@ namespace TfsExcelImporter
|
||||
_chevron.MouseDown += (s, e) => { if (e.Button == MouseButtons.Left) ToggleComboDropDown(); };
|
||||
_box.Controls.Add(_chevron);
|
||||
}
|
||||
_content = inner;
|
||||
inner.BackColor = Theme.Card;
|
||||
inner.Font = mono ? Theme.Mono : Theme.Body;
|
||||
inner.ForeColor = Theme.Ink;
|
||||
_box.Controls.Add(inner);
|
||||
inner.Enter += (s, e) => Invalidate();
|
||||
inner.Leave += (s, e) => Invalidate();
|
||||
LayoutInner();
|
||||
LayoutInner(true);
|
||||
return inner;
|
||||
}
|
||||
|
||||
/// <summary>文本框尾部图标按钮(如 Excel 字段的「浏览」):点击执行回调,悬停着色提示可点。</summary>
|
||||
public void AddSuffix(IconKind glyph, Action onClick)
|
||||
{
|
||||
if (_suffix != null) return;
|
||||
_suffix = new SuffixIcon { Glyph = glyph };
|
||||
_suffix.Click += (s, e) => { if (onClick != null) onClick(); };
|
||||
_box.Controls.Add(_suffix);
|
||||
LayoutInner(true);
|
||||
}
|
||||
|
||||
/// <summary>忙等时独立禁用尾部按钮,不影响输入框本身的编辑。</summary>
|
||||
public void SetSuffixEnabled(bool enabled)
|
||||
{
|
||||
if (_suffix != null) _suffix.Enabled = enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自绘占位提示:比原生 EM_SETCUEBANNER 多出「浅灰、小字号、水平居中」三件事。
|
||||
/// 鼠标经 HTTRANSPARENT 穿透到输入框,文字随内容输入即时显隐,与 Web placeholder 手感一致。
|
||||
/// </summary>
|
||||
public void SetCue(string text)
|
||||
{
|
||||
if (_cue == null)
|
||||
{
|
||||
_cue = new CueLabel();
|
||||
_box.Controls.Add(_cue);
|
||||
}
|
||||
_cue.Text = text ?? "";
|
||||
if (_content != null) _content.TextChanged += (s, e) => UpdateCueVisibility();
|
||||
UpdateCueVisibility();
|
||||
LayoutInner(true);
|
||||
}
|
||||
|
||||
private void UpdateCueVisibility()
|
||||
{
|
||||
if (_cue != null && _content != null)
|
||||
_cue.Visible = string.IsNullOrEmpty(_content.Text);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); LayoutInner(); }
|
||||
|
||||
private void DrawComboItem(object sender, DrawItemEventArgs e)
|
||||
@@ -953,14 +1127,19 @@ namespace TfsExcelImporter
|
||||
| TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
|
||||
}
|
||||
|
||||
private void LayoutInner()
|
||||
private void LayoutInner(bool force = false)
|
||||
{
|
||||
if (_box == null) return; // 构造中途触发 OnSizeChanged 时容器可能还没建好
|
||||
// 尺寸没变 → 子控件位置也不会变,直接跳过:这里一次要重摆 3~5 个真实子窗并重建 Region,
|
||||
// 单次 3~4ms,重排里被反复调用的正是这种空转。
|
||||
var hostSize = new Size(Width, Height);
|
||||
if (!force && hostSize == _innerSize) return;
|
||||
_innerSize = hostSize;
|
||||
_box.SetBounds(2, 2, Width - 4, Height - 4);
|
||||
SetBoxRegion();
|
||||
foreach (Control c in _box.Controls)
|
||||
{
|
||||
if (c is Chevron) continue;
|
||||
if (c is Chevron || c is SuffixIcon || c is CueLabel) continue;
|
||||
var tb = c as TextBox;
|
||||
var cb = c as ComboBox;
|
||||
if (cb != null)
|
||||
@@ -974,7 +1153,13 @@ namespace TfsExcelImporter
|
||||
}
|
||||
int natural = tb != null ? tb.PreferredHeight : Theme.Px(19);
|
||||
int th = Math.Min(Math.Max(natural, Theme.Px(18)), _box.Height - Theme.Px(4));
|
||||
c.SetBounds(Theme.Px(7), Math.Max(Theme.Px(2), (_box.Height - th) / 2), _box.Width - Theme.Px(14), th);
|
||||
// Edit 自绘时把文字画在客户区上沿往下 Px(2) 处,而 PreferredHeight 底部还含着约
|
||||
// Px(4) 的边框留白(BorderStyle=None 也不减)。直接按 PreferredHeight 居中,等于把
|
||||
// 「上留白 2 + 文字 + 下留白 4」整体居中——文字就偏上 Px(2),肉眼一眼能看出来。
|
||||
// 把这段底部留白的一半补回来,文字才落在框内视觉中线上(Consolas / 雅黑都适用)。
|
||||
int textTopFix = tb != null ? Theme.Px(2) : 0;
|
||||
c.SetBounds(Theme.Px(7), Math.Max(Theme.Px(2), (_box.Height - th) / 2) + textTopFix,
|
||||
_box.Width - Theme.Px(14) - RightReserve, th);
|
||||
}
|
||||
if (_chevron != null)
|
||||
{
|
||||
@@ -982,6 +1167,21 @@ namespace TfsExcelImporter
|
||||
_chevron.SetBounds(_box.Width - Theme.Px(7) - size, Theme.Px(2), size, _box.Height - Theme.Px(4));
|
||||
_chevron.BringToFront(); // 后加的兄弟控件在底层,必须提到组合框上方才看得见
|
||||
}
|
||||
if (_suffix != null)
|
||||
{
|
||||
int size = Theme.Px(16);
|
||||
_suffix.SetBounds(_box.Width - Theme.Px(8) - size, Theme.Px(2), size, _box.Height - Theme.Px(4));
|
||||
}
|
||||
if (_cue != null)
|
||||
{
|
||||
// 与内容文字同一缩进基准,宽度让开尾部箭头/图标区;文字左对齐(不再居中)。
|
||||
// 起点就用 TextInsetX:实测(150% 截图)空态占位与已填内容的第一个墨迹列
|
||||
// 相对左边框都在 +13px,多让或让少都会在「敲字瞬间」看到文字横跳。
|
||||
_cue.SetBounds(TextInsetX, Theme.Px(2),
|
||||
Math.Max(Theme.Px(24), _box.Width - TextInsetX - Theme.Px(7) - RightReserve),
|
||||
_box.Height - Theme.Px(4));
|
||||
_cue.BringToFront();
|
||||
}
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
@@ -1018,6 +1218,64 @@ namespace TfsExcelImporter
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>输入框尾部图标按钮:白底上悬停变主色,点击交给外部回调。</summary>
|
||||
private sealed class SuffixIcon : Control
|
||||
{
|
||||
public IconKind Glyph = IconKind.None;
|
||||
private bool _hover;
|
||||
|
||||
public SuffixIcon()
|
||||
{
|
||||
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint
|
||||
| ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw
|
||||
| ControlStyles.SupportsTransparentBackColor, true);
|
||||
BackColor = Theme.Card;
|
||||
Cursor = Cursors.Hand;
|
||||
TabStop = false;
|
||||
}
|
||||
|
||||
protected override void OnMouseEnter(EventArgs e) { base.OnMouseEnter(e); _hover = true; Invalidate(); }
|
||||
protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); _hover = false; Invalidate(); }
|
||||
protected override void OnEnabledChanged(EventArgs e) { base.OnEnabledChanged(e); Invalidate(); }
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
int size = Theme.Px(16);
|
||||
Icons.Draw(e.Graphics, Glyph,
|
||||
new Rectangle((Width - size) / 2, (Height - size) / 2, size, size),
|
||||
_hover && Enabled ? Theme.Primary : Theme.Ink2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>占位提示层:小一号字号、置灰、左对齐并贴住真实文字的左缘;鼠标事件穿透,不挡输入框聚焦与编辑。</summary>
|
||||
private sealed class CueLabel : Control
|
||||
{
|
||||
public CueLabel()
|
||||
{
|
||||
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint
|
||||
| ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
|
||||
BackColor = Theme.Card;
|
||||
TabStop = false;
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
const int WM_NCHITTEST = 0x84;
|
||||
if (m.Msg == WM_NCHITTEST) { m.Result = (IntPtr)(-1); return; } // HTTRANSPARENT:点击穿透
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
// 左对齐:占位文字必须从真实文字的起点起头,用户一敲字两个位置才不跳动。
|
||||
// NoPadding 去掉 DrawText 自带的 2px 文字边距,起点完全由外层给的 x 决定。
|
||||
TextRenderer.DrawText(e.Graphics, Text, Theme.Caption,
|
||||
new Rectangle(0, 0, Width, Height), Theme.Ink3,
|
||||
TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding
|
||||
| TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseDown(e);
|
||||
|
||||
Reference in New Issue
Block a user