自由标记体验重做 + 全部实验性面板可折起
- 网格内固定点:取值胶囊按已放置矩形做纵向避让(不再互相遮挡);行尾 ✕ 改为与 胶囊同高的正方形;移除固定点的纵向半透明竖线 - 新增「标记透明度」(0–100%),作用于本网格全部固定点的圆点/胶囊/描边/文字/✕; 半透明时文字改走 GDI+(GDI DrawTextW 忽略 alpha),100% 时像素零变化; ≤2% 时不绘制且不登记 ✕ 命中矩形(看不见的按钮不可点) - 面板按信号分组:信号色托底分组栏(展开三角 + 信号名 + N 标记点 + ✕,文字/三角/✕ 一律用自适应对比色),点击分组栏收起/展开;条目五列(值/枚举/增量/时刻/间隔), 首项增列与 Δt 显示 N/A,整个面板没有枚举文本时枚举列整列缺省;行尾仍有 ✕ - 面板内容超高时右侧出现滚动条(分组栏与条目一起滚动),滚轮在内容区翻页; 底部按钮与「标记透明度」滑块固定在底部、不参与滚动 - 底部按钮改为「光标选取模式」切换类按钮(外观与工具栏切换按钮一致,激活高亮), 激活时其上方出现「标记透明度」滑动条(与信号样式区的透明度滑块同款) - 所有实验性功能浮动面板支持展开/折起:标题栏 GX 徽标左侧新增「三角 + 横线」指示器, 单击切换、双击标题栏切换;折起的面板只有标题栏 - 数据层:LanePanelState.collapsed、Lane.freeMarkerOpacity/freeMarkerScroll/ freeMarkerCollapsedGroups,LaneCarry 与 SplitLane 一并继承 - 验证:新增离屏 .preflight/perf/mark2check 69/69(含两组 A/B);回归 datacheck 69/69、 verify 49/49、panelanim 158/158、errcheck 19/19;make release/debug 零警告
This commit is contained in:
1 parent
067128f46c
commit
3400ac7c29
9 files changed
+1225
-202
No files matched your search
@@ -941,6 +941,12 @@ namespace jdi
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
// 「展开 / 折起」三角指示器:可点击控件 ⇒ 手型光标。
|
||||
if (layout.disclosure.Width > 0.0f &&
|
||||
UiPointInRect(layout.disclosure, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (UiPointInRect(layout.title, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEALL);
|
||||
@@ -953,15 +959,27 @@ namespace jdi
|
||||
// 「构建为新信号」按钮:手型光标作为可点击提示。
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
// 「自由标记」面板:行尾 ✕ 与信号名右侧 ✕ 都是可点击控件(手型)。
|
||||
// 「自由标记」面板:行尾 ✕ / 分组栏 ✕ 与三角都是可点击控件(手型),
|
||||
// 滚动条与「标记透明度」滑块则是拖动类控件(上下 / 左右箭头)。
|
||||
if (static_cast<EdgePanelKind>(panelKind) == EdgePanelKind::MARK)
|
||||
{
|
||||
// 底部「启用光标模式 / 关闭光标模式」切换按钮。
|
||||
// 底部「光标选取模式」切换按钮。
|
||||
if (layout.button.Width > 0.0f &&
|
||||
UiPointInRect(layout.button, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (layout.opacityTrack.Width > 0.0f &&
|
||||
WeakPeakSliderHit(layout.opacityTrack, m_scale, static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||
}
|
||||
if (layout.scrollTrack.Width > 0.0f &&
|
||||
UiPointInRect(layout.scrollTrack, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZENS);
|
||||
}
|
||||
for (const EdgePanelLayout::MarkerRow& row : layout.markerRows)
|
||||
{
|
||||
if (row.close.Width > 0.0f &&
|
||||
@@ -970,13 +988,18 @@ namespace jdi
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
}
|
||||
for (const gd::RectF& rect : layout.rowNameCloses)
|
||||
for (const EdgePanelLayout::MarkerGroup& group : layout.markerGroups)
|
||||
{
|
||||
if (rect.Width > 0.0f &&
|
||||
UiPointInRect(rect, static_cast<float>(x), static_cast<float>(y)))
|
||||
if (group.close.Width > 0.0f &&
|
||||
UiPointInRect(group.close, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (UiPointInRect(group.header, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
// 分组栏本体(含三角)单击 = 展开 / 收起。
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 「弱峰调整」的逐信号滑块:轨道 → 左右箭头;迭代次数右侧数值 → 手型(可点击改上限)。
|
||||
@@ -1439,6 +1462,57 @@ namespace jdi
|
||||
m_dragMoved = true;
|
||||
return true;
|
||||
}
|
||||
case PlotDragMode::MARKER_OPACITY:
|
||||
{
|
||||
// 「标记透明度」滑块:拖动时把滑块位置换算成 0–1 的不透明度。
|
||||
if (m_tab == nullptr || m_markerOpacityDragLane < 0 ||
|
||||
m_markerOpacityDragLane >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
const EdgePanelLayout layout =
|
||||
EdgePanelLayoutFor(m_markerOpacityDragLane, EdgePanelKind::MARK);
|
||||
if (layout.opacityTrack.Width <= 0.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
const double ratio = UiSliderRatioFromX(layout.opacityTrack, static_cast<float>(x));
|
||||
if (SetLaneFreeMarkerOpacity(m_markerOpacityDragLane, static_cast<float>(ratio)))
|
||||
{
|
||||
m_dragMoved = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case PlotDragMode::MARKER_SCROLL:
|
||||
{
|
||||
// 面板内容滚动条:按滑块在轨道里的位置换算滚动偏移。
|
||||
if (m_tab == nullptr || m_markerScrollDragLane < 0 ||
|
||||
m_markerScrollDragLane >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
const EdgePanelLayout layout =
|
||||
EdgePanelLayoutFor(m_markerScrollDragLane, EdgePanelKind::MARK);
|
||||
const float travel = layout.scrollTrack.Height - layout.scrollThumb.Height;
|
||||
const float maxScroll =
|
||||
std::max(0.0f, layout.markerContentHeight - layout.content.Height);
|
||||
if (layout.scrollTrack.Width <= 0.0f || travel <= 0.0f || maxScroll <= 0.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
const float thumbTop =
|
||||
std::max(layout.scrollTrack.Y,
|
||||
std::min(static_cast<float>(y) - m_markerScrollGrabOffset,
|
||||
layout.scrollTrack.Y + travel));
|
||||
const float ratio = (thumbTop - layout.scrollTrack.Y) / travel;
|
||||
if (SetLaneFreeMarkerScroll(m_markerScrollDragLane, ratio * maxScroll))
|
||||
{
|
||||
m_dragMoved = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case PlotDragMode::NONE:
|
||||
default:
|
||||
break;
|
||||
@@ -1500,11 +1574,13 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// 面板内 ✕ 的悬停(信号名 ✕ 与固定点行 ✕)。
|
||||
// 面板内 ✕ 的悬停(分组栏 ✕ 与固定点行 ✕)与分组栏三角的悬停。
|
||||
{
|
||||
int hoverLane = -1;
|
||||
std::size_t hoverItem = static_cast<std::size_t>(-1);
|
||||
SignalRef hoverSignal;
|
||||
int triLane = -1;
|
||||
SignalRef triSignal;
|
||||
int markLane = -1;
|
||||
int markKind = 0;
|
||||
if (m_tab != nullptr &&
|
||||
@@ -1512,10 +1588,14 @@ namespace jdi
|
||||
static_cast<EdgePanelKind>(markKind) == EdgePanelKind::MARK)
|
||||
{
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(markLane, EdgePanelKind::MARK);
|
||||
// 只认**内容区**内的命中:被滚动到内容区之外的条目(它们的矩形可能落在
|
||||
// 底部按钮 / 滑块区域)不得抢走那些控件的点击。
|
||||
const bool insideContent =
|
||||
UiPointInRect(layout.content, static_cast<float>(x), static_cast<float>(y));
|
||||
// 固定点行 ✕ 优先(更具体)。
|
||||
for (const EdgePanelLayout::MarkerRow& row : layout.markerRows)
|
||||
{
|
||||
if (row.close.Width > 0.0f &&
|
||||
if (insideContent && row.close.Width > 0.0f &&
|
||||
UiPointInRect(row.close, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
hoverLane = markLane;
|
||||
@@ -1525,18 +1605,22 @@ namespace jdi
|
||||
}
|
||||
if (hoverLane < 0)
|
||||
{
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowNameCloses.size(); ++rowIndex)
|
||||
for (const EdgePanelLayout::MarkerGroup& group : layout.markerGroups)
|
||||
{
|
||||
const gd::RectF& rect = layout.rowNameCloses[rowIndex];
|
||||
if (rect.Width > 0.0f &&
|
||||
UiPointInRect(rect, static_cast<float>(x), static_cast<float>(y)) &&
|
||||
rowIndex < layout.rowRefs.size())
|
||||
if (insideContent && group.close.Width > 0.0f &&
|
||||
UiPointInRect(group.close, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
hoverLane = markLane;
|
||||
hoverItem = static_cast<std::size_t>(-1);
|
||||
hoverSignal = layout.rowRefs[rowIndex];
|
||||
hoverSignal = group.ref;
|
||||
break;
|
||||
}
|
||||
if (insideContent &&
|
||||
UiPointInRect(group.triangle, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
triLane = markLane;
|
||||
triSignal = group.ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1548,6 +1632,13 @@ namespace jdi
|
||||
m_freeMarkerPanelHoverSignal = hoverSignal;
|
||||
changed = true;
|
||||
}
|
||||
if (triLane != m_freeMarkerTriangleHoverLane ||
|
||||
!(triSignal == m_freeMarkerTriangleHoverSignal))
|
||||
{
|
||||
m_freeMarkerTriangleHoverLane = triLane;
|
||||
m_freeMarkerTriangleHoverSignal = triSignal;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标压在实验性功能面板上时不再触发信号悬停提示(面板本来就盖住波形),
|
||||
@@ -1911,6 +2002,15 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
// 标题栏左侧的「展开 / 折起」三角指示器:单击切换面板的折起状态。
|
||||
// 折起后面板只剩标题栏(再次点击三角、或双击标题栏可展开)。
|
||||
if (layout.disclosure.Width > 0.0f && UiPointInRect(layout.disclosure, fx, fy))
|
||||
{
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
ToggleLanePanelCollapsed(index, kind);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 「中心轴边沿计数」/「均值滤波」/「弱峰调整」面板:逐信号条目
|
||||
// (前两者是「名称 + 复选框 + 两个参数输入框」,弱峰调整是「名称 + 复选框 + 两行滑块」)。
|
||||
if ((kind == EdgePanelKind::MEAN || kind == EdgePanelKind::CENTER ||
|
||||
@@ -2154,37 +2254,80 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// 「自由标记」面板:行尾 ✕ 移除该固定点;信号名右侧 ✕ 清除该信号的全部固定点。
|
||||
// 「自由标记」面板:底部的切换按钮 / 「标记透明度」滑块 / 内容滚动条,
|
||||
// 随后是分组栏(单击展开收起、右侧 ✕ 清空该信号)与固定点行 ✕。
|
||||
// 与其它逐行 ✕ / 复位控件同一语义:**按下即生效**(不进拖拽、不穿透)。
|
||||
if (kind == EdgePanelKind::MARK && m_tab != nullptr)
|
||||
{
|
||||
Lane& tabLane = m_tab->Lanes()[static_cast<std::size_t>(index)];
|
||||
// 底部切换按钮「启用光标模式 / 关闭光标模式」(默认关闭)。
|
||||
// 底部切换按钮「光标选取模式」(默认关闭)。
|
||||
if (layout.button.Width > 0.0f && UiPointInRect(layout.button, fx, fy))
|
||||
{
|
||||
ToggleLaneFreeMarkerCursor(index);
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
for (const EdgePanelLayout::MarkerRow& row : layout.markerRows)
|
||||
// 「标记透明度」滑块:按下即写入该处数值并进入拖动。
|
||||
if (layout.opacityTrack.Width > 0.0f &&
|
||||
WeakPeakSliderHit(layout.opacityTrack, m_scale, fx, fy))
|
||||
{
|
||||
if (row.close.Width > 0.0f && UiPointInRect(row.close, fx, fy))
|
||||
SetLaneFreeMarkerOpacity(index,
|
||||
static_cast<float>(UiSliderRatioFromX(
|
||||
layout.opacityTrack, fx)));
|
||||
m_dragMode = PlotDragMode::MARKER_OPACITY;
|
||||
m_markerOpacityDragLane = index;
|
||||
return true;
|
||||
}
|
||||
// 内容滚动条:按在滑块上就带上抓取偏移,按在轨道上则直接跳转。
|
||||
if (layout.scrollTrack.Width > 0.0f &&
|
||||
UiPointInRect(layout.scrollTrack, fx, fy))
|
||||
{
|
||||
const float travel = layout.scrollTrack.Height - layout.scrollThumb.Height;
|
||||
const float maxScroll =
|
||||
std::max(0.0f, layout.markerContentHeight - layout.content.Height);
|
||||
if (travel > 0.0f && maxScroll > 0.0f)
|
||||
{
|
||||
tabLane.RemoveFreeMarkerAt(row.itemIndex);
|
||||
m_dragMode = PlotDragMode::MARKER_SCROLL;
|
||||
m_markerScrollDragLane = index;
|
||||
const bool onThumb = UiPointInRect(layout.scrollThumb, fx, fy);
|
||||
m_markerScrollGrabOffset =
|
||||
onThumb ? (fy - layout.scrollThumb.Y)
|
||||
: (layout.scrollThumb.Height * 0.5f);
|
||||
const float thumbTop =
|
||||
std::max(layout.scrollTrack.Y,
|
||||
std::min(fy - m_markerScrollGrabOffset,
|
||||
layout.scrollTrack.Y + travel));
|
||||
SetLaneFreeMarkerScroll(
|
||||
index, ((thumbTop - layout.scrollTrack.Y) / travel) * maxScroll);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 分组栏:✕ → 清空该信号全部固定点;其它位置(含三角)→ 展开 / 收起。
|
||||
// ⚠️ 只认内容区内的命中:滚动出去的条目不得抢到底部按钮 / 滑块的点击。
|
||||
const bool insideContent = UiPointInRect(layout.content, fx, fy);
|
||||
for (const EdgePanelLayout::MarkerGroup& group : layout.markerGroups)
|
||||
{
|
||||
if (insideContent && group.close.Width > 0.0f && UiPointInRect(group.close, fx, fy))
|
||||
{
|
||||
tabLane.RemoveFreeMarkersOf(group.ref);
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
m_freeMarkerPanelHoverLane = -1;
|
||||
m_freeMarkerPanelHoverItem = static_cast<std::size_t>(-1);
|
||||
m_freeMarkerPanelHoverSignal = SignalRef();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowNameCloses.size(); ++rowIndex)
|
||||
{
|
||||
const gd::RectF& rect = layout.rowNameCloses[rowIndex];
|
||||
if (rect.Width > 0.0f && UiPointInRect(rect, fx, fy) &&
|
||||
rowIndex < layout.rowRefs.size())
|
||||
if (insideContent && UiPointInRect(group.header, fx, fy))
|
||||
{
|
||||
tabLane.RemoveFreeMarkersOf(layout.rowRefs[rowIndex]);
|
||||
ToggleLaneFreeMarkerGroup(index, group.ref);
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const EdgePanelLayout::MarkerRow& row : layout.markerRows)
|
||||
{
|
||||
if (insideContent && row.close.Width > 0.0f && UiPointInRect(row.close, fx, fy))
|
||||
{
|
||||
tabLane.RemoveFreeMarkerAt(row.itemIndex);
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
m_freeMarkerPanelHoverLane = -1;
|
||||
m_freeMarkerPanelHoverItem = static_cast<std::size_t>(-1);
|
||||
@@ -2490,6 +2633,10 @@ namespace jdi
|
||||
// 弱峰滑块拖拽结束:清掉目标(避免留下上一轮的信号引用)。
|
||||
m_peakDragLane = -1;
|
||||
m_peakDragRef = SignalRef();
|
||||
// 「标记透明度」滑块 / 面板内容滚动条拖拽结束。
|
||||
m_markerOpacityDragLane = -1;
|
||||
m_markerScrollDragLane = -1;
|
||||
m_markerScrollGrabOffset = 0.0f;
|
||||
// 松开左键:粘滞提示结束计时,开始"停留后淡出"。
|
||||
ReleaseHint();
|
||||
return changed;
|
||||
@@ -2504,6 +2651,37 @@ namespace jdi
|
||||
|
||||
const double notches = static_cast<double>(delta) / 120.0;
|
||||
|
||||
// 鼠标位于「自由标记」面板的可滚动内容区上:滚轮翻页该面板的内容(不滚动网格)。
|
||||
// ⚠️ 只认内容区:底部的切换按钮与「标记透明度」滑块不参与滚动,
|
||||
// 因此鼠标压在它们上面时仍然滚动网格(与“它们不属于内容”的语义一致)。
|
||||
if (m_tab != nullptr)
|
||||
{
|
||||
int panelLane = -1;
|
||||
int panelKind = 0;
|
||||
if (TopPanelAtPoint(static_cast<float>(m_mouseX), static_cast<float>(m_mouseY),
|
||||
panelLane, panelKind) &&
|
||||
static_cast<EdgePanelKind>(panelKind) == EdgePanelKind::MARK)
|
||||
{
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(panelLane, EdgePanelKind::MARK);
|
||||
if (layout.content.Width > 0.0f &&
|
||||
UiPointInRect(layout.content, static_cast<float>(m_mouseX),
|
||||
static_cast<float>(m_mouseY)))
|
||||
{
|
||||
const float maxScroll =
|
||||
std::max(0.0f, layout.markerContentHeight - layout.content.Height);
|
||||
if (maxScroll > 0.0f)
|
||||
{
|
||||
const float next = static_cast<float>(Tab().Lanes()[static_cast<std::size_t>(
|
||||
panelLane)]
|
||||
.freeMarkerScroll -
|
||||
notches * 40.0 * m_scale);
|
||||
SetLaneFreeMarkerScroll(panelLane, std::max(0.0f, std::min(next, maxScroll)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 滚轮不再承担缩放:默认纵向滚动网格,按住 Shift 时水平平移时间轴。
|
||||
if (shift)
|
||||
{
|
||||
@@ -2542,6 +2720,27 @@ namespace jdi
|
||||
int panelKind = 0;
|
||||
if (TopPanelAtPoint(static_cast<float>(x), static_cast<float>(y), panelLane, panelKind))
|
||||
{
|
||||
// 标题栏双击 = 展开 / 折起该面板(折起的面板只显示标题栏)。
|
||||
// ⚠️ 折起状态下标题栏是唯一可见区域,因此这里也是“双击重新展开”的入口。
|
||||
{
|
||||
const EdgePanelLayout titleLayout =
|
||||
EdgePanelLayoutFor(panelLane, static_cast<EdgePanelKind>(panelKind));
|
||||
if (titleLayout.title.Width > 0.0f &&
|
||||
UiPointInRect(titleLayout.title, static_cast<float>(x), static_cast<float>(y)) &&
|
||||
!(titleLayout.badge.Width > 0.0f &&
|
||||
UiPointInRect(titleLayout.badge, static_cast<float>(x),
|
||||
static_cast<float>(y))) &&
|
||||
!(titleLayout.close.Width > 0.0f &&
|
||||
UiPointInRect(titleLayout.close, static_cast<float>(x),
|
||||
static_cast<float>(y))) &&
|
||||
!(titleLayout.disclosure.Width > 0.0f &&
|
||||
UiPointInRect(titleLayout.disclosure, static_cast<float>(x),
|
||||
static_cast<float>(y))))
|
||||
{
|
||||
ToggleLanePanelCollapsed(panelLane, static_cast<EdgePanelKind>(panelKind));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 「自由标记」面板:双击某一固定点行 → 编辑它的时间(左侧是自身时刻、
|
||||
// 右侧是相对前一固定点的间隔,对应行内两列的位置)。
|
||||
if (static_cast<EdgePanelKind>(panelKind) == EdgePanelKind::MARK && m_tab != nullptr &&
|
||||
@@ -2553,6 +2752,7 @@ namespace jdi
|
||||
for (const EdgePanelLayout::MarkerRow& row : layout.markerRows)
|
||||
{
|
||||
if (row.rect.Width <= 0.0f ||
|
||||
!UiPointInRect(layout.content, static_cast<float>(x), static_cast<float>(y)) ||
|
||||
!UiPointInRect(row.rect, static_cast<float>(x), static_cast<float>(y)) ||
|
||||
row.itemIndex >= tabLane.freeMarkerItems.size())
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user