信号 X 按钮移至行尾并统一抬起响应,光标读数补秒单位,图例悬停高亮信号,去除筛选框内圈
1. 信号条目的「移除」X 按钮移到最右端
- 与分组行关闭 X 同尺寸(18)、同右边距(同一列)、同行内垂直居中,描边宽度也统一。
- 网格编号 GX 徽标让位到 X 左侧,二者恒在列表最右侧;名称可用宽度改按实际徽标是否显示计算,
不再白留一段空间。
2. 所有 X 关闭按钮由"按下即响应"改为"左键抬起才响应"
- 与常见的可取消按钮一致:按下只记录目标,按住后移开鼠标再松开视为放弃。
- 覆盖文件分组 X、信号 X、筛选框清空 X、标签页 X 与实验性功能面板 X。
- 筛选框的清空 X 在编辑态下走的是宿主的 EDIT_FIELD 分支(OnEditMouseDown/OnEditMouseUp),
非编辑态才走面板的按下/抬起分支,两条路径都已改造。
- 标签页 X 原先只要求"抬起时仍在同一标签内",现收紧为"仍在 X 上"。
- PlotView::OnLButtonUp 增加横坐标参数以便判定抬起位置。
3. 时间光标读数补上 s 后缀
- 新增 FormatCursorTime,标签宽度测量与文字绘制同时使用,避免文字被裁掉;
内联编辑仍使用不带单位的秒值。
4. 鼠标悬停网格图例时高亮对应信号
- 除图例条目的半高亮外,对命中信号绘制与"鼠标贴近曲线"相同的光晕,
便于确认该条目对应哪条曲线。
5. 筛选信号输入框编辑态不再出现内圈
- 把输入框绘制拆分为"底板+边框"与"仅内容"两部分;筛选框外框已自绘,
编辑态改用仅内容版本,空内容时不再套一层多余的圆角内框;
有输入时的清空 X 不受影响。
This commit is contained in:
1 parent
a1e30985b8
commit
d3c83237f1
8 files changed
+252
-72
No files matched your search
@@ -1846,7 +1846,7 @@ namespace jdi
|
||||
m_activeTarget = Target::NONE;
|
||||
m_draggingSplitter = false;
|
||||
m_panel.OnLButtonUp(0, 0);
|
||||
m_plot.OnLButtonUp(0);
|
||||
m_plot.OnLButtonUp(0, 0);
|
||||
}
|
||||
|
||||
switch (message)
|
||||
@@ -1971,7 +1971,7 @@ namespace jdi
|
||||
|
||||
// 拾色器接管输入后,取消底层组件可能仍在进行的拖拽。
|
||||
m_panel.OnLButtonUp(0, 0);
|
||||
m_plot.OnLButtonUp(0);
|
||||
m_plot.OnLButtonUp(0, 0);
|
||||
m_activeTarget = Target::NONE;
|
||||
m_draggingSplitter = false;
|
||||
|
||||
@@ -2305,7 +2305,7 @@ namespace jdi
|
||||
{
|
||||
// 结束输入框内的拖拽框选(选区保留)。
|
||||
bool changed = m_tabBar.OnEditMouseUp();
|
||||
changed = m_panel.OnEditMouseUp() || changed;
|
||||
changed = m_panel.OnEditMouseUp(x, y) || changed;
|
||||
changed = m_plot.OnEditMouseUp() || changed;
|
||||
changed = m_colorPicker.OnEditMouseUp() || changed;
|
||||
m_activeTarget = Target::NONE;
|
||||
@@ -2340,7 +2340,7 @@ namespace jdi
|
||||
m_panel.OnLButtonUp(x, y);
|
||||
break;
|
||||
case Target::PLOT:
|
||||
m_plot.OnLButtonUp(y);
|
||||
m_plot.OnLButtonUp(x, y);
|
||||
break;
|
||||
case Target::NONE:
|
||||
default:
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace jdi
|
||||
* @param [IN] y 客户区纵坐标。
|
||||
* @return 需要重绘返回 true。
|
||||
*/
|
||||
bool OnLButtonUp(int y);
|
||||
bool OnLButtonUp(int x, int y);
|
||||
|
||||
/**
|
||||
* @brief 处理鼠标中键按下:与左键在常规模式下一致,进入自由平移(水平平移时间轴 +
|
||||
@@ -1224,6 +1224,16 @@ namespace jdi
|
||||
int m_pendingMeanFilterLane = -1; /**< 待构建的均值滤波信号所属网格下标(-1 表示无请求)。 */
|
||||
int m_pendingMeanFilterRadius = 0; /**< 待构建的均值滤波半径 N。 */
|
||||
|
||||
/**
|
||||
* @brief 实验性功能浮动面板的 ✕ 处于"已按下、等待抬起确认"状态。
|
||||
*
|
||||
* 关闭类 ✕ 统一改为左键**抬起**时生效:按下只记录目标,抬起时指针仍在该 ✕ 上才真正
|
||||
* 关闭对应功能;按住后移开再松开视为放弃。
|
||||
*/
|
||||
bool m_pendingPanelClose = false; /**< 是否有待确认的面板关闭请求。 */
|
||||
int m_pendingPanelCloseLane = -1; /**< 待关闭面板所属网格下标。 */
|
||||
EdgePanelKind m_pendingPanelCloseKind = EdgePanelKind::COUNT; /**< 待关闭面板的功能类型。 */
|
||||
|
||||
int m_hoverLane = -1; /**< 当前悬停网格下标。 */
|
||||
int m_mouseX = 0; /**< 最近一次鼠标横坐标(用于悬停提示定位)。 */
|
||||
int m_mouseY = 0; /**< 最近一次鼠标纵坐标(用于悬停提示定位)。 */
|
||||
|
||||
@@ -364,6 +364,20 @@ namespace jdi
|
||||
return L"Δt:" + FormatDurationCompound(seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 时间光标的读数文本("数值 + s 后缀")。
|
||||
*
|
||||
* 时间轴本身就是秒,但与幅值读数放在一起时没有单位容易误读,
|
||||
* 因此读数恒带 "s" 后缀;内联编辑仍使用不带单位的纯数值(见 BeginEdit 调用处)。
|
||||
*
|
||||
* @param [IN] seconds 光标所在时刻(秒)。
|
||||
* @return 形如 "1.25s" 的文本。
|
||||
*/
|
||||
std::wstring FormatCursorTime(double seconds)
|
||||
{
|
||||
return FormatValue(seconds) + L"s";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 把时间间隔格式化为"秒"数值文本(不含单位),用于内联编辑。
|
||||
*
|
||||
@@ -1313,7 +1327,7 @@ namespace jdi
|
||||
const auto rawRect = [this](int index) -> gd::RectF
|
||||
{
|
||||
const double time = (index == 2) ? Tab().CursorTimeAlt() : Tab().CursorTime();
|
||||
const std::wstring text = FormatValue(time);
|
||||
const std::wstring text = FormatCursorTime(time);
|
||||
const float width =
|
||||
std::max(64.0f * m_scale, UiMeasureTextWidth(m_fonts->monoSmall, text) + 16.0f * m_scale);
|
||||
const float height = 20.0f * m_scale;
|
||||
@@ -3338,6 +3352,9 @@ namespace jdi
|
||||
m_cursorGrabOffset = 0;
|
||||
// 新的交互开始:清掉上一次留下的提示(拖动过程中会重新设置粘滞提示)。
|
||||
SetHint(std::wstring());
|
||||
// 上一轮尚未抬起确认的面板关闭请求一律作废。
|
||||
m_pendingPanelClose = false;
|
||||
m_pendingPanelCloseLane = -1;
|
||||
|
||||
if (m_scrollBarVisible &&
|
||||
UiPointInRect(m_scrollBarRect, static_cast<float>(x), static_cast<float>(y)))
|
||||
@@ -3416,22 +3433,11 @@ namespace jdi
|
||||
|
||||
if (UiPointInRect(layout.close, fx, fy))
|
||||
{
|
||||
// ✕ 关闭面板的同时关闭该网格对应的实验性功能。
|
||||
switch (kind)
|
||||
{
|
||||
case EdgePanelKind::CENTER:
|
||||
SetLaneEdgeCenterCount(index, false);
|
||||
break;
|
||||
case EdgePanelKind::MEAN:
|
||||
SetLaneMeanFilter(index, false);
|
||||
break;
|
||||
case EdgePanelKind::COUNT:
|
||||
default:
|
||||
SetLaneEdgeCount(index, false);
|
||||
break;
|
||||
}
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
// ✕:**抬起**时才关闭该网格对应的实验性功能,这里只记录目标;
|
||||
// 按住后移开再松开视为放弃(与面板/标签栏的关闭 ✕ 语义一致)。
|
||||
m_pendingPanelClose = true;
|
||||
m_pendingPanelCloseLane = index;
|
||||
m_pendingPanelCloseKind = kind;
|
||||
}
|
||||
|
||||
if ((kind == EdgePanelKind::CENTER || kind == EdgePanelKind::MEAN) &&
|
||||
@@ -3584,10 +3590,42 @@ namespace jdi
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool PlotView::OnLButtonUp(int y)
|
||||
bool PlotView::OnLButtonUp(int x, int y)
|
||||
{
|
||||
bool changed = (m_dragMode != PlotDragMode::NONE);
|
||||
|
||||
// 实验性功能面板的 ✕:只有"按下后仍在同一个 ✕ 上抬起"才真正关闭该功能。
|
||||
if (m_pendingPanelClose)
|
||||
{
|
||||
const int lane = m_pendingPanelCloseLane;
|
||||
const EdgePanelKind kind = m_pendingPanelCloseKind;
|
||||
m_pendingPanelClose = false;
|
||||
m_pendingPanelCloseLane = -1;
|
||||
|
||||
if (lane >= 0 && lane < static_cast<int>(m_lanes.size()))
|
||||
{
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(lane, kind);
|
||||
if (layout.close.Width > 0.0f &&
|
||||
UiPointInRect(layout.close, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case EdgePanelKind::CENTER:
|
||||
SetLaneEdgeCenterCount(lane, false);
|
||||
break;
|
||||
case EdgePanelKind::MEAN:
|
||||
SetLaneMeanFilter(lane, false);
|
||||
break;
|
||||
case EdgePanelKind::COUNT:
|
||||
default:
|
||||
SetLaneEdgeCount(lane, false);
|
||||
break;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (m_dragMode)
|
||||
{
|
||||
case PlotDragMode::RUBBER_SELECT:
|
||||
@@ -4370,6 +4408,19 @@ namespace jdi
|
||||
|
||||
const int saveState = g.Save();
|
||||
g.SetClip(m_area, gd::CombineModeIntersect);
|
||||
|
||||
// 除了图例条目本身,还要把**对应的信号曲线**一并高亮:用户在网格图例上移动时
|
||||
// 一眼就能确认这条条目对应哪条曲线(与"鼠标贴近曲线"时的光晕提示一致)。
|
||||
const ResolvedSignal resolved = ResolveSignal(m_hoverLegend);
|
||||
if (resolved.data != nullptr && resolved.style != nullptr && resolved.visible)
|
||||
{
|
||||
const LaneVisual& lane = m_lanes[static_cast<std::size_t>(laneIndex)];
|
||||
if (const PolylineCacheEntry* cached = AcquirePolyline(m_hoverLegend, resolved, lane))
|
||||
{
|
||||
PaintSignalHalo(g, resolved, cached->points, cached->breaks);
|
||||
}
|
||||
}
|
||||
|
||||
for (const LegendHit& hit : m_legendHits)
|
||||
{
|
||||
if (hit.ref == m_hoverLegend)
|
||||
@@ -5656,7 +5707,7 @@ namespace jdi
|
||||
highlighted ? color : WithAlpha(color, 0xE0));
|
||||
const bool dark = IsDarkColor(color);
|
||||
DrawLabelOutline(g, labelRect, 3.0f * m_scale, dark);
|
||||
UiText(g, FormatValue(which == 2 ? Tab().CursorTimeAlt() : Tab().CursorTime()),
|
||||
UiText(g, FormatCursorTime(which == 2 ? Tab().CursorTimeAlt() : Tab().CursorTime()),
|
||||
m_fonts->monoSmall, labelRect,
|
||||
dark ? 0xFFFDF6E3u : 0xFF073642u, TextAlign::CENTER);
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace jdi
|
||||
* @brief 编辑态下松开鼠标左键:结束拖拽框选。
|
||||
* @return 之前处于拖拽中返回 true。
|
||||
*/
|
||||
bool OnEditMouseUp();
|
||||
bool OnEditMouseUp(int x, int y);
|
||||
|
||||
/**
|
||||
* @brief 获取指定信号的显示外观(只读,全局共享)。
|
||||
@@ -405,10 +405,10 @@ namespace jdi
|
||||
gd::RectF SignalBadgeRect(int row) const;
|
||||
|
||||
/**
|
||||
* @brief 计算信号行左侧「移除该信号」✕ 按钮的矩形。
|
||||
* @brief 计算信号行右侧「移除该信号」✕ 按钮的矩形。
|
||||
*
|
||||
* 位置就信号行空着的折叠三角列,并与所属分组行的复选框**同一列**(左边界对齐),
|
||||
* 因此始终位于复选框前方且与其垂直对齐。
|
||||
* 位于信号条目的**最右端**,尺寸、样式与右边距与分组行的关闭 ✕ 完全一致,
|
||||
* 并与它们在同一行内垂直居中(因此视觉上垂直对齐);网格徽标会让位到它的左侧。
|
||||
*
|
||||
* @param [IN] row 行下标。
|
||||
* @return 按钮矩形;非信号行时返回空矩形。
|
||||
@@ -641,6 +641,16 @@ namespace jdi
|
||||
SignalRef m_pendingLaneRef; /**< 待弹出菜单的目标信号。 */
|
||||
bool m_pendingColorPicker = false; /**< 左键抬起时是否弹出拾色器。 */
|
||||
|
||||
/**
|
||||
* @brief 待确认的「关闭类」按钮(✕)。
|
||||
*
|
||||
* 关闭类按钮统一改为**左键抬起**时生效:按下只记录目标,抬起时若指针仍在同一个
|
||||
* ✕ 上才真正执行;按住后移开再松开视为放弃(与常见"可取消按钮"交互一致)。
|
||||
*/
|
||||
RowPart m_pendingClosePart = RowPart::NONE; /**< 待确认的 ✕ 所在行内区域(CLOSE / REMOVE_SIGNAL)。 */
|
||||
int m_pendingCloseRow = -1; /**< 待确认的 ✕ 所在行下标。 */
|
||||
bool m_pendingFilterClear = false; /**< 待确认的筛选框清空 ✕。 */
|
||||
|
||||
bool m_layoutDirty = false; /**< 是否需要重新布局。 */
|
||||
|
||||
SignalRef m_menuTargetSignal; /**< 菜单操作的目标信号。 */
|
||||
|
||||
@@ -23,13 +23,13 @@ namespace jdi
|
||||
const float kDisclosureWidth = 16.0f; /**< 折叠三角占位宽度。 */
|
||||
const float kIndent = 14.0f; /**< 信号行相对文件行的缩进。 */
|
||||
const float kScrollBarThickness = 8.0f; /**< 滚动条宽度。 */
|
||||
const float kCloseButtonSize = 18.0f; /**< 文件行关闭按钮尺寸。 */
|
||||
const float kCloseButtonSize = 18.0f; /**< 行尾关闭(✕)按钮尺寸(分组行与信号行共用)。 */
|
||||
const float kGroupSeparatorGap = 7.0f; /**< 分组标签与分隔线/名称的间距。 */
|
||||
const float kGroupSeparatorWidth = 1.0f; /**< 分组标签右侧分隔线宽度。 */
|
||||
const float kFilterHeight = 26.0f; /**< 「筛选信号」输入框高度。 */
|
||||
const float kFilterPadV = 5.0f; /**< 筛选框上下留白。 */
|
||||
const float kFilterTextPad = 4.0f; /**< 筛选框内文字/输入框相对外框的内缩(四边一致)。 */
|
||||
const float kRemoveButtonSize = 14.0f; /**< 信号行「移除该信号」✕ 按钮尺寸。 */
|
||||
const float kRowTailGap = 6.0f; /**< 网格徽标与行尾 ✕ 之间的间距。 */
|
||||
|
||||
/**
|
||||
* @brief 大小写无关的通配符匹配(`*` 匹配任意长度的任意字符序列)。
|
||||
@@ -181,6 +181,9 @@ namespace jdi
|
||||
m_editField = StyleEditField::NONE;
|
||||
m_pendingLaneMenu = false;
|
||||
m_pendingColorPicker = false;
|
||||
m_pendingClosePart = RowPart::NONE;
|
||||
m_pendingCloseRow = -1;
|
||||
m_pendingFilterClear = false;
|
||||
}
|
||||
|
||||
void SignalPanel::SetScale(float scale)
|
||||
@@ -461,8 +464,10 @@ namespace jdi
|
||||
const gd::RectF rect = RowRect(row);
|
||||
const float width = kBadgeWidth * m_scale;
|
||||
const float height = kBadgeHeight * m_scale;
|
||||
return gd::RectF(rect.X + rect.Width - kPadding * m_scale - width,
|
||||
rect.Y + (rect.Height - height) * 0.5f, width, height);
|
||||
// 行尾现在被「移除该信号」✕ 占用(与分组 ✕ 同尺寸、同列),网格徽标因此整体前移;
|
||||
// 两者都恒在列表最右侧,且始终保留固定的间隔,不会互相挤压。
|
||||
const float right = SignalRemoveRect(row).X - kRowTailGap * m_scale;
|
||||
return gd::RectF(right - width, rect.Y + (rect.Height - height) * 0.5f, width, height);
|
||||
}
|
||||
|
||||
gd::RectF SignalPanel::SignalRemoveRect(int row) const
|
||||
@@ -473,13 +478,12 @@ namespace jdi
|
||||
return gd::RectF();
|
||||
}
|
||||
|
||||
// 分组行的复选框矩形为 [rect.X + kPadding + kDisclosureWidth, +kCheckSize],
|
||||
// 信号行的折叠三角列是空着的,正好用来放 ✕:左边界与分组复选框对齐
|
||||
// (垂直方向同列),右边界仍留在三角列内,不会压到信号自己的复选框。
|
||||
// 与分组行的关闭 ✕ **完全一致**(同尺寸、同右边距、同行内垂直居中),
|
||||
// 只是作用对象是本行信号:位于信号条目的最右端。
|
||||
const gd::RectF rect = RowRect(row);
|
||||
const float left = rect.X + (kPadding + kDisclosureWidth) * m_scale;
|
||||
const float size = kRemoveButtonSize * m_scale;
|
||||
return gd::RectF(left, rect.Y + (rect.Height - size) * 0.5f, size, size);
|
||||
const float size = kCloseButtonSize * m_scale;
|
||||
return gd::RectF(rect.X + rect.Width - kPadding * m_scale - size,
|
||||
rect.Y + (rect.Height - size) * 0.5f, size, size);
|
||||
}
|
||||
|
||||
int SignalPanel::HitRow(int x, int y, RowPart& part)
|
||||
@@ -790,9 +794,12 @@ namespace jdi
|
||||
|
||||
if (m_filterEditing)
|
||||
{
|
||||
// 外框(m_filterRect)已经画好了底板与强调色边框,这里只叠加文本内容。
|
||||
// 若改用 UiDrawEditField,会在外框内部再套一个带边框的圆角框,
|
||||
// 空内容时只剩一圈内框十分突兀(用户明确要求去掉这层"内圈")。
|
||||
gd::RectF fieldRect = FilterFieldRect();
|
||||
fieldRect.Width = std::max(8.0f, textWidth + pad);
|
||||
UiDrawEditField(g, fieldRect, m_filterEdit, m_fonts->uiSmall, m_scale);
|
||||
UiDrawEditFieldContent(g, fieldRect, m_filterEdit, m_fonts->uiSmall, m_scale);
|
||||
}
|
||||
else if (m_filterText.empty())
|
||||
{
|
||||
@@ -1010,18 +1017,6 @@ namespace jdi
|
||||
const bool visible = m_tab->IsVisible(ref);
|
||||
UiDrawCheckBox(g, checkRect, visible ? 1 : 0, 3.0f * m_scale);
|
||||
|
||||
// 「移除该信号」✕:位于复选框前方(分组复选框所在列),悬停时高亮为危险色。
|
||||
{
|
||||
const gd::RectF removeRect = SignalRemoveRect(row);
|
||||
const bool removeHot = hovered && (m_hoverPart == RowPart::REMOVE_SIGNAL);
|
||||
if (removeHot)
|
||||
{
|
||||
UiFillRoundRect(g, removeRect, 3.0f * m_scale, m_theme->pressed);
|
||||
}
|
||||
UiDrawCrossIcon(g, removeRect, removeHot ? m_theme->danger : m_theme->textMuted,
|
||||
1.4f * m_scale);
|
||||
}
|
||||
|
||||
// 颜色块:叠加不透明度,透明度过低时自动补描边(与样式区色块规则一致)。
|
||||
const gd::RectF swatchRect(checkRect.X + checkRect.Width + 6.0f * m_scale,
|
||||
centerY - kSwatchSize * m_scale * 0.5f, kSwatchSize * m_scale,
|
||||
@@ -1029,12 +1024,13 @@ namespace jdi
|
||||
UiDrawSwatch(g, swatchRect, style->color, visible ? style->opacity : 0.3f, 2.5f * m_scale,
|
||||
m_scale);
|
||||
|
||||
// 网格徽标(可点击,用于调整所属网格)。
|
||||
// 网格徽标(可点击,用于调整所属网格)。行尾 ✕ 恒在最右侧,徽标整体前移让位。
|
||||
const int laneIndex = m_tab->LaneOfSignal(ref);
|
||||
const bool badgeVisible = (laneIndex >= 0) && visible;
|
||||
const gd::RectF badgeRect = SignalBadgeRect(row);
|
||||
const gd::RectF removeRect = SignalRemoveRect(row);
|
||||
|
||||
float nameRightLimit = rect.X + rect.Width - kPadding * m_scale;
|
||||
float nameRightLimit = removeRect.X - 6.0f * m_scale;
|
||||
if (badgeVisible)
|
||||
{
|
||||
nameRightLimit = badgeRect.X - 6.0f * m_scale;
|
||||
@@ -1064,6 +1060,18 @@ namespace jdi
|
||||
UiText(g, L"G" + std::to_wstring(laneIndex + 1), m_fonts->monoSmall, badgeRect,
|
||||
badgeHot ? m_theme->textStrong : m_theme->text, TextAlign::CENTER);
|
||||
}
|
||||
|
||||
// 「移除该信号」✕:位于信号条目最右端,尺寸/样式/右边距与分组行的关闭 ✕ 完全一致。
|
||||
// 最后绘制,确保它永远压在最上层(与分组行 ✕ 的绘制顺序保持一致)。
|
||||
{
|
||||
const bool removeHot = hovered && (m_hoverPart == RowPart::REMOVE_SIGNAL);
|
||||
if (removeHot)
|
||||
{
|
||||
UiFillRoundRect(g, removeRect, 3.0f * m_scale, m_theme->pressed);
|
||||
}
|
||||
UiDrawCrossIcon(g, removeRect, removeHot ? m_theme->danger : m_theme->textMuted,
|
||||
1.5f * m_scale);
|
||||
}
|
||||
}
|
||||
|
||||
gd::RectF SignalPanel::ScrollThumbRect() const
|
||||
@@ -1304,7 +1312,12 @@ namespace jdi
|
||||
else
|
||||
{
|
||||
left = checkRight + 6.0f * m_scale + kSwatchSize * m_scale + 6.0f * m_scale;
|
||||
right = SignalBadgeRect(row).X - 6.0f * m_scale;
|
||||
// 行尾的可用右界:有网格徽标时让位给徽标,否则可直接延伸到行尾 ✕ 之前。
|
||||
const SignalRef ref{m_rows[static_cast<std::size_t>(row)].fileIndex,
|
||||
m_rows[static_cast<std::size_t>(row)].signalIndex};
|
||||
const bool badgeVisible = (m_tab != nullptr) && m_tab->IsVisible(ref) &&
|
||||
(m_tab->LaneOfSignal(ref) >= 0);
|
||||
right = (badgeVisible ? SignalBadgeRect(row).X : SignalRemoveRect(row).X) - 6.0f * m_scale;
|
||||
}
|
||||
|
||||
if (right <= left)
|
||||
@@ -1721,11 +1734,12 @@ namespace jdi
|
||||
{
|
||||
// 编辑态下 ✕ 位于输入框内容区之内,必须优先于插入符定位处理,
|
||||
// 否则点击会被当成"把光标移到此处"而清空不了内容。
|
||||
// 与其它 ✕ 一致:按下只记录目标,抬起时仍在 ✕ 上才真正清空。
|
||||
const gd::RectF clearRect = FilterClearRect();
|
||||
if (clearRect.Width > 0.0f &&
|
||||
UiPointInRect(clearRect, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
ClearFilter();
|
||||
m_pendingFilterClear = true;
|
||||
return true;
|
||||
}
|
||||
return UiEditFieldMouseDown(m_fonts->uiSmall, FilterFieldRect(), m_filterEdit, m_scale, x, y);
|
||||
@@ -1750,10 +1764,24 @@ namespace jdi
|
||||
return UiEditFieldMouseMove(m_fonts->ui, EditFieldRect(), m_edit, m_scale, x);
|
||||
}
|
||||
|
||||
bool SignalPanel::OnEditMouseUp()
|
||||
bool SignalPanel::OnEditMouseUp(int x, int y)
|
||||
{
|
||||
if (m_filterEditing)
|
||||
{
|
||||
// 筛选框右端的清空 ✕ 在编辑态下的**按下**走 OnEditMouseDown(优先于插入符定位),
|
||||
// 抬起同样由宿主送进 EDIT_FIELD 分支,因此确认逻辑要在这里结算:
|
||||
// 只有抬起时指针仍在 ✕ 上才真正清空,移开后松开视为放弃。
|
||||
if (m_pendingFilterClear)
|
||||
{
|
||||
m_pendingFilterClear = false;
|
||||
const gd::RectF clearRect = FilterClearRect();
|
||||
if (clearRect.Width > 0.0f &&
|
||||
UiPointInRect(clearRect, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
ClearFilter();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!m_filterEdit.Dragging())
|
||||
{
|
||||
return false;
|
||||
@@ -1863,6 +1891,10 @@ namespace jdi
|
||||
m_colorPicker->Close();
|
||||
changed = true;
|
||||
}
|
||||
// 面板外点击:取消尚未抬起确认的关闭操作。
|
||||
m_pendingClosePart = RowPart::NONE;
|
||||
m_pendingCloseRow = -1;
|
||||
m_pendingFilterClear = false;
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -1979,13 +2011,19 @@ namespace jdi
|
||||
const float fx = static_cast<float>(x);
|
||||
const float fy = static_cast<float>(y);
|
||||
|
||||
// 新的按下开始:上一轮留下的"待确认关闭"一律作废。
|
||||
m_pendingClosePart = RowPart::NONE;
|
||||
m_pendingCloseRow = -1;
|
||||
m_pendingFilterClear = false;
|
||||
|
||||
// 「筛选信号」输入框:单击进入编辑;点击其它位置则先结束筛选编辑(文本已实时生效)。
|
||||
if (IsOnFilterBox(x, y))
|
||||
{
|
||||
// 右侧 ✕:清空筛选内容(不退出编辑态,便于继续输入)。
|
||||
// 右侧 ✕:**抬起**时才清空(不退出编辑态,便于继续输入);
|
||||
// 按下后移开再松开视为放弃。
|
||||
if (UiPointInRect(FilterClearRect(), fx, fy))
|
||||
{
|
||||
ClearFilter();
|
||||
m_pendingFilterClear = true;
|
||||
return true;
|
||||
}
|
||||
BeginFilterEdit();
|
||||
@@ -2111,11 +2149,10 @@ namespace jdi
|
||||
}
|
||||
if (part == RowPart::CLOSE)
|
||||
{
|
||||
// 关闭文件属于数据层操作,交由宿主窗口处理。
|
||||
if (m_host != nullptr)
|
||||
{
|
||||
PostMessageW(m_host, WM_APP + 1, static_cast<WPARAM>(item.fileIndex), 0);
|
||||
}
|
||||
// 关闭文件属于数据层操作,交由宿主窗口处理;但**抬起时才生效**,
|
||||
// 这里只记录目标(按住后移开再松开视为放弃)。
|
||||
m_pendingClosePart = RowPart::CLOSE;
|
||||
m_pendingCloseRow = row;
|
||||
return true;
|
||||
}
|
||||
m_tab->ToggleCollapsed(item.fileIndex);
|
||||
@@ -2126,15 +2163,12 @@ namespace jdi
|
||||
const SignalRef ref{item.fileIndex, item.signalIndex};
|
||||
|
||||
// ✕:用户明确表示不再需要该信号 → 交由宿主从数据层移除并释放其采样内存。
|
||||
// 必须在 SelectSignal 之前处理,避免选中一条即将被删除的信号。
|
||||
// 必须在 SelectSignal 之前处理,避免选中一条即将被删除的信号;
|
||||
// 且与其它 ✕ 一致,**抬起时才真正删除**(按住后移开再松开视为放弃)。
|
||||
if (part == RowPart::REMOVE_SIGNAL)
|
||||
{
|
||||
if (m_host != nullptr)
|
||||
{
|
||||
// wParam = 文件下标,lParam = 文件内信号下标(见 Application 的消息处理)。
|
||||
PostMessageW(m_host, WM_APP + 2, static_cast<WPARAM>(item.fileIndex),
|
||||
static_cast<LPARAM>(item.signalIndex));
|
||||
}
|
||||
m_pendingClosePart = RowPart::REMOVE_SIGNAL;
|
||||
m_pendingCloseRow = row;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2159,6 +2193,48 @@ namespace jdi
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
// ---- 关闭类按钮(✕):按下时记录目标,抬起时若指针仍在同一个 ✕ 上才执行 ----
|
||||
if (m_pendingFilterClear)
|
||||
{
|
||||
m_pendingFilterClear = false;
|
||||
const float fx = static_cast<float>(x);
|
||||
const float fy = static_cast<float>(y);
|
||||
const gd::RectF clearRect = FilterClearRect();
|
||||
if (clearRect.Width > 0.0f && UiPointInRect(clearRect, fx, fy))
|
||||
{
|
||||
ClearFilter();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (m_pendingClosePart != RowPart::NONE)
|
||||
{
|
||||
const RowPart pendingPart = m_pendingClosePart;
|
||||
const int pendingRow = m_pendingCloseRow;
|
||||
m_pendingClosePart = RowPart::NONE;
|
||||
m_pendingCloseRow = -1;
|
||||
|
||||
RowPart part = RowPart::NONE;
|
||||
const int row = HitRow(x, y, part);
|
||||
if (row == pendingRow && part == pendingPart && row >= 0)
|
||||
{
|
||||
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
|
||||
if (m_host != nullptr)
|
||||
{
|
||||
if (pendingPart == RowPart::CLOSE)
|
||||
{
|
||||
PostMessageW(m_host, WM_APP + 1, static_cast<WPARAM>(item.fileIndex), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// wParam = 文件下标,lParam = 文件内信号下标(见 Application 的消息处理)。
|
||||
PostMessageW(m_host, WM_APP + 2, static_cast<WPARAM>(item.fileIndex),
|
||||
static_cast<LPARAM>(item.signalIndex));
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_draggingOpacity)
|
||||
{
|
||||
m_draggingOpacity = false;
|
||||
|
||||
@@ -380,8 +380,14 @@ namespace jdi
|
||||
|
||||
if (wasClose)
|
||||
{
|
||||
result.type = TabBarResult::Type::CLOSE;
|
||||
result.index = index;
|
||||
// 关闭 ✕ 只在"按下后仍在 ✕ 上抬起"时生效:按住后移开再松开视为放弃
|
||||
// (与信号面板的文件/信号 ✕ 保持一致的交互语义)。
|
||||
if (UiPointInRect(m_tabs[static_cast<std::size_t>(index)].closeRect,
|
||||
static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
result.type = TabBarResult::Type::CLOSE;
|
||||
result.index = index;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -278,6 +278,21 @@ namespace jdi
|
||||
void UiDrawEditField(gd::Graphics& g, const gd::RectF& rect, const TextEdit& edit, HFONT font,
|
||||
float scale);
|
||||
|
||||
/**
|
||||
* @brief 只绘制输入框的**内容**(选区高亮 / 文本 / 插入符),不绘制底板与边框。
|
||||
*
|
||||
* 用于"宿主已经画好了外框"的场景(例如信号面板的「筛选信号」输入框):若直接调用
|
||||
* UiDrawEditField,会在外框内部再套一个带边框的圆角框,形成一圈多余的内圈。
|
||||
*
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] rect 输入框矩形(内容区,几何与 UiDrawEditField 完全一致)。
|
||||
* @param [IN] edit 输入框编辑模型。
|
||||
* @param [IN] font 文本字体。
|
||||
* @param [IN] scale DPI 缩放比例。
|
||||
*/
|
||||
void UiDrawEditFieldContent(gd::Graphics& g, const gd::RectF& rect, const TextEdit& edit, HFONT font,
|
||||
float scale);
|
||||
|
||||
/**
|
||||
* @brief 绘制一个带边框的复选框。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
|
||||
@@ -550,6 +550,18 @@ namespace jdi
|
||||
UiFillRoundRect(g, rect, corner, theme.cardBg);
|
||||
UiDrawRoundRect(g, rect, corner, theme.accent, 1.6f);
|
||||
|
||||
UiDrawEditFieldContent(g, rect, edit, font, scale);
|
||||
}
|
||||
|
||||
void UiDrawEditFieldContent(gd::Graphics& g, const gd::RectF& rect, const TextEdit& edit, HFONT font,
|
||||
float scale)
|
||||
{
|
||||
if (rect.Width <= 0.0f || rect.Height <= 0.0f || font == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const Theme& theme = SolarizedLight();
|
||||
const std::wstring& text = edit.Text();
|
||||
const float innerLeft = rect.X + 6.0f * scale;
|
||||
|
||||
|
||||
Reference in new issue
Block a user