Commit beca36c3 authored by 段廷瑞's avatar 段廷瑞

fix: 点击空白弹出预览问题

parent 67a4bda7
No preview for this file type
...@@ -2875,6 +2875,12 @@ ...@@ -2875,6 +2875,12 @@
<summary>缩略图点击映射(v1.3.5):渲染序列里成功内联成 \pict 的图片绝对路径(渲染顺序), <summary>缩略图点击映射(v1.3.5):渲染序列里成功内联成 \pict 的图片绝对路径(渲染顺序),
来源 ChatRtfCache.LastImageSequence,与命中位置表按下标一一对应</summary> 来源 ChatRtfCache.LastImageSequence,与命中位置表按下标一一对应</summary>
</member> </member>
<member name="F:Tools.MyAgent.wMyAgent.ThumbHitRadius">
<summary>缩略图字符命中半径(v1.3.7):超出即视为未命中(空白点击防线;见 NearestPosition)</summary>
</member>
<member name="F:Tools.MyAgent.wMyAgent.ThumbHeight">
<summary>缩略图显示高(px,与 MarkdownRenderer.ThumbHeight 对齐;用于图片行行盒校验)</summary>
</member>
<member name="F:Tools.MyAgent.wMyAgent._thumbMarkers"> <member name="F:Tools.MyAgent.wMyAgent._thumbMarkers">
<summary>主命中通道(v1.3.5b):Text 流里零宽标记 U+200B 的位置(与 \pict 一一配对,随 RebuildAnswerRefs 重建)</summary> <summary>主命中通道(v1.3.5b):Text 流里零宽标记 U+200B 的位置(与 \pict 一一配对,随 RebuildAnswerRefs 重建)</summary>
</member> </member>
...@@ -2958,8 +2964,27 @@ ...@@ -2958,8 +2964,27 @@
主通道为零宽标记序数;标记缺失时回退 IRichEditOle 对象表序数。 主通道为零宽标记序数;标记缺失时回退 IRichEditOle 对象表序数。
</summary> </summary>
</member> </member>
<member name="M:Tools.MyAgent.wMyAgent.IsClickOnImageBlock(System.Drawing.Point,System.Int32)">
<summary>
几何校验(v1.3.7):点击点是否真的落在图片块的行盒内。
<para>为什么需要:<c>\pict</c> 内联对象在字符流里只占一个零宽槽位,字符距离(<see cref="M:Tools.MyAgent.wMyAgent.NearestPosition(System.Collections.Generic.List{System.Int32},System.Int32,System.Int32@)"/>)
无法区分「点在图上」与「点在图片行右侧空白」—— 后者也会被归到该行行尾字符上。
故在字符命中通过后,再用像素几何确认一次:行盒(Y 在图片带内)+ 行尾右界(X 不越过行尾)。</para>
<para>X 右界取「该行最后一个字符的右缘」:图片行内容就是若干缩略图,最右一张的右缘之后即空白。
无法取到行尾坐标时**放行**(宁可少一次误拦,也不要让图片变成点不开)。</para>
</summary>
</member>
<member name="M:Tools.MyAgent.wMyAgent.GetLineIndexAt(System.Int32)">
<summary>charIdx 所在行号(0 基);失败返回 0</summary>
</member>
<member name="M:Tools.MyAgent.wMyAgent.NearestPosition(System.Collections.Generic.List{System.Int32},System.Int32,System.Int32@)"> <member name="M:Tools.MyAgent.wMyAgent.NearestPosition(System.Collections.Generic.List{System.Int32},System.Int32,System.Int32@)">
<summary>位置表中距 charIdx 最近者(半径 ±2),命中返回其下标</summary> <summary>
位置表中距 charIdx 最近者(半径 <see cref="F:Tools.MyAgent.wMyAgent.ThumbHitRadius"/>),命中返回其下标。
<para>⚠ 必须设距离上界:<b>超出半径一律返回 false</b>。本通道位于 <c>IsClickOnChar</c> 闸门**之前**
(见 OnChatMouseUp 注释),因此它是空白点击的第一道也是唯一一道防线 —— 一旦退化为「取全局最近的一个」,
点在图片块右侧空白处(GetCharIndexFromPosition 归给行尾字符索引,而行尾恰是最后一张图的标记位)
就会被吸附到**最右边那张**缩略图上并弹出预览。</para>
</summary>
</member> </member>
<member name="M:Tools.MyAgent.wMyAgent.OpenAnswerImage(System.String)"> <member name="M:Tools.MyAgent.wMyAgent.OpenAnswerImage(System.String)">
<summary>打开缩略图对应的大图(v1.3.5):直接走内置图片查看器</summary> <summary>打开缩略图对应的大图(v1.3.5):直接走内置图片查看器</summary>
......
...@@ -119,6 +119,12 @@ namespace Tools.MyAgent ...@@ -119,6 +119,12 @@ namespace Tools.MyAgent
/// 来源 ChatRtfCache.LastImageSequence,与命中位置表按下标一一对应</summary> /// 来源 ChatRtfCache.LastImageSequence,与命中位置表按下标一一对应</summary>
private List<string> _answerImageSeq = new List<string>(); private List<string> _answerImageSeq = new List<string>();
/// <summary>缩略图字符命中半径(v1.3.7):超出即视为未命中(空白点击防线;见 NearestPosition)</summary>
private const int ThumbHitRadius = 2;
/// <summary>缩略图显示高(px,与 MarkdownRenderer.ThumbHeight 对齐;用于图片行行盒校验)</summary>
private const int ThumbHeight = 72;
/// <summary>主命中通道(v1.3.5b):Text 流里零宽标记 U+200B 的位置(与 \pict 一一配对,随 RebuildAnswerRefs 重建)</summary> /// <summary>主命中通道(v1.3.5b):Text 流里零宽标记 U+200B 的位置(与 \pict 一一配对,随 RebuildAnswerRefs 重建)</summary>
private List<int> _thumbMarkers = new List<int>(); private List<int> _thumbMarkers = new List<int>();
...@@ -678,18 +684,69 @@ namespace Tools.MyAgent ...@@ -678,18 +684,69 @@ namespace Tools.MyAgent
return false; return false;
} }
/// <summary>位置表中距 charIdx 最近者(半径 ±2),命中返回其下标</summary> /// <summary>
/// 几何校验(v1.3.7):点击点是否真的落在图片块的行盒内。
/// <para>为什么需要:<c>\pict</c> 内联对象在字符流里只占一个零宽槽位,字符距离(<see cref="NearestPosition"/>)
/// 无法区分「点在图上」与「点在图片行右侧空白」—— 后者也会被归到该行行尾字符上。
/// 故在字符命中通过后,再用像素几何确认一次:行盒(Y 在图片带内)+ 行尾右界(X 不越过行尾)。</para>
/// <para>X 右界取「该行最后一个字符的右缘」:图片行内容就是若干缩略图,最右一张的右缘之后即空白。
/// 无法取到行尾坐标时**放行**(宁可少一次误拦,也不要让图片变成点不开)。</para>
/// </summary>
private bool IsClickOnImageBlock(Point click, int charIdx)
{
try
{
if (_rtfFallback == null) return true;
int lineEnd = _rtfFallback.GetFirstCharIndexFromLine(GetLineIndexAt(charIdx));
if (lineEnd < 0) return true;
Point pStart = _rtfFallback.GetPositionFromCharIndex(lineEnd);
if (pStart.X < 0 || pStart.Y < 0) return true;
// 行盒:以该行行首字符的行高为垂直带(图片与同行文字共享基线行)
int lineHeight = Math.Max(_rtfFallback.Font.Height, ThumbHeight);
const int bandPad = 6; // 图片内联后行高可能大于字体行高,给一点宽容
if (Math.Abs(click.Y - pStart.Y) > lineHeight + bandPad) return false;
// 行尾右界:该行最后一个字符(图片标记)的右缘 + 宽容
int lastChar = _rtfFallback.GetFirstCharIndexFromLine(GetLineIndexAt(charIdx) + 1);
int endIdx = (lastChar > 0 ? lastChar - 1 : _rtfFallback.TextLength - 1);
if (endIdx < 0) return true;
Point pEnd = _rtfFallback.GetPositionFromCharIndex(endIdx);
if (pEnd.X < 0 || pEnd.Y < 0 || pEnd.Y != pStart.Y) return true; // 取不到行尾坐标 → 放行
return click.X <= pEnd.X + _rtfFallback.Font.Height; // 零宽标记无宽度 ⇒ 用字高近似其右缘
}
catch { return true; }
}
/// <summary>charIdx 所在行号(0 基);失败返回 0</summary>
private int GetLineIndexAt(int charIdx)
{
try
{
if (_rtfFallback == null || charIdx < 0) return 0;
return _rtfFallback.GetLineFromCharIndex(charIdx);
}
catch { return 0; }
}
/// <summary>
/// 位置表中距 charIdx 最近者(半径 <see cref="ThumbHitRadius"/>),命中返回其下标。
/// <para>⚠ 必须设距离上界:<b>超出半径一律返回 false</b>。本通道位于 <c>IsClickOnChar</c> 闸门**之前**
/// (见 OnChatMouseUp 注释),因此它是空白点击的第一道也是唯一一道防线 —— 一旦退化为「取全局最近的一个」,
/// 点在图片块右侧空白处(GetCharIndexFromPosition 归给行尾字符索引,而行尾恰是最后一张图的标记位)
/// 就会被吸附到**最右边那张**缩略图上并弹出预览。</para>
/// </summary>
private static bool NearestPosition(List<int> positions, int charIdx, out int index) private static bool NearestPosition(List<int> positions, int charIdx, out int index)
{ {
index = -1; index = -1;
if (positions == null || positions.Count == 0) return false; if (positions == null || positions.Count == 0) return false;
int best = -1, bestDist = 3; int best = -1, bestDist = ThumbHitRadius + 1;
for (int i = 0; i < positions.Count; i++) for (int i = 0; i < positions.Count; i++)
{ {
int d = Math.Abs(positions[i] - charIdx); int d = Math.Abs(positions[i] - charIdx);
if (d < bestDist) { bestDist = d; best = i; } if (d < bestDist) { bestDist = d; best = i; }
} }
if (best < 0) return false; if (best < 0 || bestDist > ThumbHitRadius) return false; // ← 超出半径不算命中(空白点击防线)
index = best; index = best;
return true; return true;
} }
...@@ -1487,18 +1544,12 @@ namespace Tools.MyAgent ...@@ -1487,18 +1544,12 @@ namespace Tools.MyAgent
return; return;
int charIdx = _rtfFallback.GetCharIndexFromPosition(e.Location); int charIdx = _rtfFallback.GetCharIndexFromPosition(e.Location);
// 诊断(v1.3.5b 临时):Ctrl+点击 → 上报缩略图命中链路三要素(序列数/标记数/对象数/命中位置)
// 注意:MouseEventArgs 无 Control 属性(KeyEventArgs 才有),鼠标事件取修饰键用静态 Control.ModifierKeys
if ((Control.ModifierKeys & Keys.Control) != 0)
{
AgentUtils.Toast(string.Format("[诊断] seq={0} 标记={1} 对象={2} charIdx={3}",
_answerImageSeq.Count, _thumbMarkers.Count, _pictPositions.Count, charIdx), 8000);
return;
}
// 缩略图(v1.3.5):\pict 内联对象直接点击放大 —— 须在 IsClickOnChar 之前判定: // 缩略图(v1.3.5):\pict 内联对象直接点击放大 —— 须在 IsClickOnChar 之前判定:
// 对象字符锚点在图片左上角,点在图片中下部时 IsClickOnChar 的窄字符判定会把它当"空白点击"漏掉 // 对象字符锚点在图片左上角,点在图片中下部时 IsClickOnChar 的窄字符判定会把它当"空白点击"漏掉。
// v1.3.7:因提前于闸门,本通道自带两道防线 —— ① NearestPosition 的距离上界;
// ② IsClickOnImageBlock 的像素几何(拦截「图片行右侧空白」,那里 charIdx 会被归到行尾字符)。
string thumbImage; string thumbImage;
if (TryGetAnswerImageAt(charIdx, out thumbImage)) if (TryGetAnswerImageAt(charIdx, out thumbImage) && IsClickOnImageBlock(e.Location, charIdx))
{ {
OpenAnswerImage(thumbImage); OpenAnswerImage(thumbImage);
return; return;
...@@ -1532,9 +1583,10 @@ namespace Tools.MyAgent ...@@ -1532,9 +1583,10 @@ namespace Tools.MyAgent
if (_mouseButtonDown) return; // 拖拽选择中,不切换悬停效果 if (_mouseButtonDown) return; // 拖拽选择中,不切换悬停效果
int charIdx = _rtfFallback.GetCharIndexFromPosition(e.Location); int charIdx = _rtfFallback.GetCharIndexFromPosition(e.Location);
// 缩略图悬停(v1.3.5):与点击同因,须在 IsClickOnChar 闸门之前判定(对象锚点会窄字符误判为空白) // 缩略图悬停(v1.3.5):与点击同因,须在 IsClickOnChar 闸门之前判定(对象锚点会窄字符误判为空白);
// v1.3.7:几何校验与点击同一口径(图片行右侧空白不再显示手型光标)
string hoverImg; string hoverImg;
if (TryGetAnswerImageAt(charIdx, out hoverImg)) if (TryGetAnswerImageAt(charIdx, out hoverImg) && IsClickOnImageBlock(e.Location, charIdx))
{ {
if (charIdx != _lastHoverChar) if (charIdx != _lastHoverChar)
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment