fix: 统一输入框编辑行为、外观跨视图同步并优化标尺与网格可读性
输入框(TextEdit,全组件共用): - 修复退格一次删除两个字符:WM_CHAR 与 WM_KEYDOWN 重复处理了退格/删除, 现统一由 OnEditKey 处理,WM_CHAR 忽略全部控制字符。 - 进入编辑(整段选中)后按右键:取消全选并把插入符移到文本最右侧。 - 存在选区时松开 Shift 再按左右方向键,插入符收拢到选区的左/右端。 - 修复整段选中后按 Home/End 不收拢选区的问题。 标签页: - 按加粗字体测量宽度,避免激活页长名称被提前省略(预览过短)。 - 重命名过程中按正在输入的文本测量并加宽上限(320px),标签随输入增长, 解决长内容绘制错误;输入溢出时整体等比收缩,保证「+」始终可见。 信号显示外观跨标签页同步: - 外观(颜色/自定义色记忆/透明度/线宽/样式)从 TabConfig 上移到 DataSet 的 SignalData,成为信号的全局属性;可见性仍按标签页独立保存。 - 因此任一视图中修改某信号的颜色/线宽/透明度/样式,其它视图自动一致。 工具栏: - 移除「全选信号」按钮;「清空勾选」改为「清空网格」, 「拆分信号网格」改为「拆分网格」。 自适应: - 无可见信号(未勾选/全部取消/清空网格)时复位 ViewInitialized, 下次勾选第一个信号会重新自适应,而不是停留在旧视图范围。 绘图区标尺与网格: - 幅值刻度密度按网格高度自适应(3-8 档),各网格刻度数量一致; 网格较矮时同步收紧标签行距,提高可读密度。 - 网格线与五等分估读线加深,与网格边界(蓝灰)保持明显区分。 - 时间轴列名补上单位,显示为 time (s)。
This commit is contained in:
1 parent
53768b8b25
commit
e3744f3eff
16 files changed
+462
-340
No files matched your search
@@ -389,7 +389,13 @@ namespace jdi
|
||||
m_cachedLaneCount = laneCount;
|
||||
|
||||
// 标签页首次出现可见信号时,把时间轴对齐到可见数据的范围。
|
||||
if (anyVisible && !tab.ViewInitialized())
|
||||
// 没有任何可见信号时(未勾选 / 全部取消勾选 / 清空网格)主动复位自适应标记,
|
||||
// 这样用户下一次勾选信号时会重新做一次自适应,而不是停留在上次的视图范围。
|
||||
if (!anyVisible)
|
||||
{
|
||||
tab.ViewInitialized() = false;
|
||||
}
|
||||
else if (!tab.ViewInitialized())
|
||||
{
|
||||
m_plot.FitAll();
|
||||
}
|
||||
@@ -804,20 +810,12 @@ namespace jdi
|
||||
SetStatusMessage(L"当前标签页已切换为「每信号一格」。", false);
|
||||
}
|
||||
break;
|
||||
case ToolbarAction::SELECT_ALL:
|
||||
if (!m_dataSet.Empty())
|
||||
{
|
||||
ActiveTab().SetAllVisible(m_dataSet, true);
|
||||
m_panel.InvalidateRows();
|
||||
SetStatusMessage(L"已勾选当前标签页全部信号。", false);
|
||||
}
|
||||
break;
|
||||
case ToolbarAction::CLEAR_ALL:
|
||||
if (!m_dataSet.Empty())
|
||||
{
|
||||
ActiveTab().SetAllVisible(m_dataSet, false);
|
||||
m_panel.InvalidateRows();
|
||||
SetStatusMessage(L"已清空当前标签页的信号勾选。", false);
|
||||
SetStatusMessage(L"已清空当前标签页的全部网格。", false);
|
||||
}
|
||||
break;
|
||||
case ToolbarAction::NONE:
|
||||
@@ -1604,6 +1602,11 @@ namespace jdi
|
||||
{
|
||||
SetFocus(hwnd);
|
||||
changed = m_tabBar.OnDoubleClick(x, y);
|
||||
if (m_tabBar.IsEditing())
|
||||
{
|
||||
// 进入重命名后该标签会加宽,需要重新排布标签栏。
|
||||
Relayout();
|
||||
}
|
||||
}
|
||||
else if (m_panel.HitTest(x, y))
|
||||
{
|
||||
@@ -1624,6 +1627,21 @@ namespace jdi
|
||||
const int x = GET_X_LPARAM(lParam);
|
||||
const int y = GET_Y_LPARAM(lParam);
|
||||
|
||||
// 编辑态下右键 = 取消选区并把插入符移到文本末尾(常规输入框行为),
|
||||
// 其它操作(弹菜单、拖拽)一并被吞掉。
|
||||
if (m_tabBar.IsEditing() || m_panel.IsEditing() || m_plot.IsEditing() || m_colorPicker.IsEditing())
|
||||
{
|
||||
bool changed = m_tabBar.CollapseEditSelectionToEnd();
|
||||
changed = m_panel.CollapseEditSelectionToEnd() || changed;
|
||||
changed = m_plot.CollapseEditSelectionToEnd() || changed;
|
||||
changed = m_colorPicker.CollapseEditSelectionToEnd() || changed;
|
||||
if (changed)
|
||||
{
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
POINT cursor = {x, y};
|
||||
ClientToScreen(hwnd, &cursor);
|
||||
|
||||
@@ -1727,6 +1745,11 @@ namespace jdi
|
||||
TabBarResult tabResult;
|
||||
m_tabBar.OnEditKey(wParam, tabResult);
|
||||
OnTabBarResult(tabResult);
|
||||
if (!m_tabBar.IsEditing())
|
||||
{
|
||||
// 退出重命名后标签宽度恢复,需要重新排布。
|
||||
Relayout();
|
||||
}
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
return 0;
|
||||
}
|
||||
@@ -1758,6 +1781,11 @@ namespace jdi
|
||||
{
|
||||
if (m_tabBar.OnEditChar(static_cast<wchar_t>(wParam)))
|
||||
{
|
||||
// 标签宽度随输入内容增长(重命名时上限更宽),需重新排布才能看到完整名称。
|
||||
if (m_tabBar.IsEditing())
|
||||
{
|
||||
Relayout();
|
||||
}
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -318,6 +318,12 @@ namespace jdi
|
||||
*/
|
||||
bool OnEditKey(WPARAM key);
|
||||
|
||||
/**
|
||||
* @brief 编辑态下按下鼠标右键:取消选区并把插入符移到文本末尾。
|
||||
* @return 发生改变返回 true;不在编辑态返回 false。
|
||||
*/
|
||||
bool CollapseEditSelectionToEnd();
|
||||
|
||||
/**
|
||||
* @brief 获取当前视图左边界时间。
|
||||
* @return 视图左边界时间。
|
||||
@@ -351,9 +357,10 @@ namespace jdi
|
||||
*/
|
||||
struct ResolvedSignal
|
||||
{
|
||||
const SignalData* data = nullptr; /**< 信号数据。 */
|
||||
const SignalStyle* style = nullptr; /**< 显示配置。 */
|
||||
const SignalData* data = nullptr; /**< 信号数据(含全局共享的显示外观)。 */
|
||||
const SignalStyle* style = nullptr; /**< 显示外观。 */
|
||||
const DataFile* file = nullptr; /**< 所属文件。 */
|
||||
bool visible = false; /**< 在当前标签页中是否可见。 */
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,8 +12,13 @@ namespace jdi
|
||||
namespace
|
||||
{
|
||||
const int kTimeTickTarget = 10; /**< 时间轴期望刻度数。 */
|
||||
const int kYTickTarget = 6; /**< 幅值轴期望刻度数。 */
|
||||
const int kYTickTarget = 6; /**< 幅值轴期望刻度数(网格较高时)。 */
|
||||
const int kYTickTargetMin = 3; /**< 幅值轴最少刻度数(网格很矮时也要保证可读数)。 */
|
||||
const int kYTickTargetMax = 8; /**< 幅值轴最多刻度数。 */
|
||||
const float kYTickSpacing = 28.0f; /**< 幅值刻度之间的理想间距(逻辑像素)。 */
|
||||
const float kYLabelMinGap = 15.0f; /**< 幅值标签的最小行距(逻辑像素)。 */
|
||||
const int kMinorDivisions = 5; /**< 相邻主刻度之间的等分数(用于估读)。 */
|
||||
const unsigned int kMinorLineAlpha = 0x9Au; /**< 辅助等分线的透明度(越高越清晰)。 */
|
||||
const float kScrollBarThickness = 9.0f; /**< 滚动条宽度(逻辑像素)。 */
|
||||
const float kLanePadding = 8.0f; /**< 网格内边距。 */
|
||||
const size_t kMaxPolylinePoints = 20000; /**< 单条折线最大顶点数。 */
|
||||
@@ -162,10 +167,8 @@ namespace jdi
|
||||
}
|
||||
result.data = m_dataSet->Resolve(ref);
|
||||
result.file = m_dataSet->FileAt(ref.fileIndex);
|
||||
if (m_tab != nullptr)
|
||||
{
|
||||
result.style = m_tab->Style(ref);
|
||||
}
|
||||
result.style = m_dataSet->Style(ref);
|
||||
result.visible = (m_tab != nullptr) && m_tab->IsVisible(ref);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -229,8 +232,8 @@ namespace jdi
|
||||
int firstFile = -1;
|
||||
for (const SignalRef& ref : m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].signals)
|
||||
{
|
||||
const SignalStyle* style = m_tab->Style(ref);
|
||||
if (style == nullptr || !style->visible)
|
||||
const SignalStyle* style = m_dataSet->Style(ref);
|
||||
if (style == nullptr || !m_tab->IsVisible(ref))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -888,7 +891,7 @@ namespace jdi
|
||||
for (const SignalRef& ref : m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.style->visible)
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1157,21 +1160,23 @@ namespace jdi
|
||||
{
|
||||
return EndEdit(true);
|
||||
}
|
||||
if (character == L'\b')
|
||||
{
|
||||
return m_edit.Backspace();
|
||||
}
|
||||
if (character == 0x7F)
|
||||
{
|
||||
return m_edit.DeleteForward();
|
||||
}
|
||||
if (character < 0x20)
|
||||
// 退格/删除已由 WM_KEYDOWN 的 OnEditKey 处理,此处忽略控制字符避免重复删除。
|
||||
if (character < 0x20 || character == 0x7F)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_edit.Insert(character);
|
||||
}
|
||||
|
||||
bool PlotView::CollapseEditSelectionToEnd()
|
||||
{
|
||||
if (m_editTarget == PlotEditTarget::NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_edit.DeselectToEnd();
|
||||
}
|
||||
|
||||
bool PlotView::OnEditKey(WPARAM key)
|
||||
{
|
||||
if (m_editTarget == PlotEditTarget::NONE)
|
||||
@@ -1893,7 +1898,11 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 幅值网格线(先画线,标尺稍后作为覆盖层绘制,避免被波形遮挡)。
|
||||
const double yStep = NiceStep(lane.yMax - lane.yMin, kYTickTarget);
|
||||
// 刻度密度随网格高度自适应:所有网格高度相同 → 各网格的刻度数量一致,
|
||||
// 不会出现“有的网格只有 1 条标签、有的有 3 条”的不一致观感。
|
||||
int yTickTarget = static_cast<int>(std::lround(lane.rect.Height / (kYTickSpacing * m_scale)));
|
||||
yTickTarget = std::max(kYTickTargetMin, std::min(yTickTarget, kYTickTargetMax));
|
||||
const double yStep = NiceStep(lane.yMax - lane.yMin, yTickTarget);
|
||||
std::vector<double> yTicks;
|
||||
BuildTicks(lane.yMin, lane.yMax, yStep, yTicks);
|
||||
|
||||
@@ -1916,7 +1925,7 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
UiDrawLine(g, lane.rect.X, y, lane.rect.X + lane.rect.Width, y,
|
||||
WithAlpha(m_theme->gridWeak, 0x70), 1.0f);
|
||||
WithAlpha(m_theme->gridWeak, kMinorLineAlpha), 1.0f);
|
||||
}
|
||||
|
||||
for (const double tick : yTicks)
|
||||
@@ -1952,7 +1961,7 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
UiDrawLine(g, x, lane.rect.Y, x, lane.rect.Y + lane.rect.Height,
|
||||
WithAlpha(m_theme->gridWeak, 0x70), 1.0f);
|
||||
WithAlpha(m_theme->gridWeak, kMinorLineAlpha), 1.0f);
|
||||
}
|
||||
|
||||
for (const double tick : timeTicks)
|
||||
@@ -1983,7 +1992,7 @@ namespace jdi
|
||||
for (const SignalRef& ref : laneSignals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.style->visible)
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2074,6 +2083,11 @@ namespace jdi
|
||||
const bool showLabels = (lane.rect.Height >= 34.0f * m_scale);
|
||||
float lastLabelY = -1.0e9f;
|
||||
|
||||
// 网格较矮时同步收紧标签行距,让密度随可用高度提升而不是整片被过滤掉。
|
||||
const float labelMinGap =
|
||||
std::min(kYLabelMinGap * m_scale,
|
||||
lane.rect.Height / static_cast<float>(yTickTarget + 1));
|
||||
|
||||
for (const double tick : yTicks)
|
||||
{
|
||||
if (!showLabels)
|
||||
@@ -2086,7 +2100,7 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (std::fabs(y - lastLabelY) < 15.0f * m_scale)
|
||||
if (std::fabs(y - lastLabelY) < labelMinGap)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2150,7 +2164,7 @@ namespace jdi
|
||||
for (const SignalRef& ref : m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.style->visible)
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2223,7 +2237,7 @@ namespace jdi
|
||||
for (const SignalRef& ref : laneSignals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.style->visible)
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2266,7 +2280,7 @@ namespace jdi
|
||||
for (const SignalRef& ref : laneSignals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.style->visible)
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2370,7 +2384,7 @@ namespace jdi
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || resolved.file == nullptr ||
|
||||
!resolved.style->visible)
|
||||
!resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2455,6 +2469,12 @@ namespace jdi
|
||||
if (file != nullptr && !file->timeColumnName.empty())
|
||||
{
|
||||
axisName = file->timeColumnName;
|
||||
// CSV 中该列通常直接叫 "time",这里补上时间单位秒便于阅读;
|
||||
// 若列名自带单位描述(以右括号结尾)则不重复追加。
|
||||
if (axisName.back() != L')')
|
||||
{
|
||||
axisName += L" (s)";
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2532,7 +2552,7 @@ namespace jdi
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || resolved.file == nullptr ||
|
||||
!resolved.style->visible)
|
||||
!resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -182,6 +182,26 @@ namespace jdi
|
||||
*/
|
||||
bool OnEditKey(WPARAM key);
|
||||
|
||||
/**
|
||||
* @brief 编辑态下按下鼠标右键:取消选区并把插入符移到文本末尾。
|
||||
* @return 发生改变返回 true;不在编辑态返回 false。
|
||||
*/
|
||||
bool CollapseEditSelectionToEnd();
|
||||
|
||||
/**
|
||||
* @brief 获取指定信号的显示外观(只读,全局共享)。
|
||||
* @param [IN] ref 信号引用。
|
||||
* @return 外观指针,引用无效时返回 nullptr。
|
||||
*/
|
||||
const SignalStyle* StyleOf(const SignalRef& ref) const;
|
||||
|
||||
/**
|
||||
* @brief 获取指定信号的显示外观(可写,全局共享)。
|
||||
* @param [IN] ref 信号引用。
|
||||
* @return 外观指针,引用无效时返回 nullptr。
|
||||
*/
|
||||
SignalStyle* StyleOf(const SignalRef& ref);
|
||||
|
||||
/**
|
||||
* @brief 将浮动拾色器的取色结果应用到当前选中信号。
|
||||
* @param [IN] color 0xAARRGGBB 颜色值。
|
||||
|
||||
@@ -708,7 +708,7 @@ namespace jdi
|
||||
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
|
||||
const SignalRef ref{item.fileIndex, item.signalIndex};
|
||||
const SignalData* signal = m_dataSet->Resolve(ref);
|
||||
const SignalStyle* style = m_tab->Style(ref);
|
||||
const SignalStyle* style = StyleOf(ref);
|
||||
if (signal == nullptr || style == nullptr)
|
||||
{
|
||||
return;
|
||||
@@ -734,18 +734,20 @@ namespace jdi
|
||||
// 复选框。
|
||||
const gd::RectF checkRect(baseX + kDisclosureWidth * m_scale, centerY - kCheckSize * m_scale * 0.5f,
|
||||
kCheckSize * m_scale, kCheckSize * m_scale);
|
||||
UiDrawCheckBox(g, checkRect, style->visible ? 1 : 0, 3.0f * m_scale);
|
||||
// 可见性属于当前标签页,与全局共享的显示外观分开读取。
|
||||
const bool visible = m_tab->IsVisible(ref);
|
||||
UiDrawCheckBox(g, checkRect, visible ? 1 : 0, 3.0f * m_scale);
|
||||
|
||||
// 颜色块:叠加不透明度,透明度过低时自动补描边(与样式区色块规则一致)。
|
||||
const gd::RectF swatchRect(checkRect.X + checkRect.Width + 6.0f * m_scale,
|
||||
centerY - kSwatchSize * m_scale * 0.5f, kSwatchSize * m_scale,
|
||||
kSwatchSize * m_scale);
|
||||
UiDrawSwatch(g, swatchRect, style->color, style->visible ? style->opacity : 0.3f, 2.5f * m_scale,
|
||||
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) && style->visible;
|
||||
const bool badgeVisible = (laneIndex >= 0) && visible;
|
||||
const gd::RectF badgeRect = SignalBadgeRect(row);
|
||||
|
||||
float nameRightLimit = rect.X + rect.Width - kPadding * m_scale;
|
||||
@@ -763,7 +765,7 @@ namespace jdi
|
||||
else
|
||||
{
|
||||
const float nameWidth = std::max(16.0f, nameRightLimit - nameLeft);
|
||||
const std::uint32_t nameColor = style->visible ? m_theme->text : m_theme->textMuted;
|
||||
const std::uint32_t nameColor = visible ? m_theme->text : m_theme->textMuted;
|
||||
UiText(g, UiEllipsize(g, signal->Title(), m_fonts->ui, nameWidth), m_fonts->ui,
|
||||
gd::RectF(nameLeft, rect.Y, nameWidth, rect.Height), nameColor, TextAlign::LEFT);
|
||||
}
|
||||
@@ -816,7 +818,7 @@ namespace jdi
|
||||
|
||||
UiFillRect(g, m_styleRect, m_theme->panelBg);
|
||||
|
||||
const SignalStyle* style = (m_tab == nullptr) ? nullptr : m_tab->Style(m_tab->Selected());
|
||||
const SignalStyle* style = (m_tab == nullptr) ? nullptr : StyleOf(m_tab->Selected());
|
||||
if (style == nullptr || m_tab->Selected().IsValid() == false)
|
||||
{
|
||||
UiText(g, L"请选择信号以编辑样式", m_fonts->uiSmall, m_styleRect, m_theme->textMuted,
|
||||
@@ -962,7 +964,7 @@ namespace jdi
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SignalStyle* style = m_tab->Style(m_tab->Selected());
|
||||
SignalStyle* style = StyleOf(m_tab->Selected());
|
||||
if (style == nullptr)
|
||||
{
|
||||
return false;
|
||||
@@ -1041,7 +1043,7 @@ namespace jdi
|
||||
{
|
||||
case StyleEditField::WIDTH:
|
||||
{
|
||||
const SignalStyle* style = m_tab->Style(m_tab->Selected());
|
||||
const SignalStyle* style = StyleOf(m_tab->Selected());
|
||||
if (style == nullptr)
|
||||
{
|
||||
return false;
|
||||
@@ -1051,7 +1053,7 @@ namespace jdi
|
||||
}
|
||||
case StyleEditField::OPACITY:
|
||||
{
|
||||
const SignalStyle* style = m_tab->Style(m_tab->Selected());
|
||||
const SignalStyle* style = StyleOf(m_tab->Selected());
|
||||
if (style == nullptr)
|
||||
{
|
||||
return false;
|
||||
@@ -1138,7 +1140,7 @@ namespace jdi
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (SignalStyle* style = m_tab->Style(m_tab->Selected()))
|
||||
if (SignalStyle* style = StyleOf(m_tab->Selected()))
|
||||
{
|
||||
const float value = ClampFloat(static_cast<float>(parsed), kWidthMin, kWidthMax);
|
||||
style->lineWidth = std::round(value * 10.0f) / 10.0f;
|
||||
@@ -1157,7 +1159,7 @@ namespace jdi
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (SignalStyle* style = m_tab->Style(m_tab->Selected()))
|
||||
if (SignalStyle* style = StyleOf(m_tab->Selected()))
|
||||
{
|
||||
style->opacity = ClampFloat(static_cast<float>(parsed) / 100.0f, 0.0f, 1.0f);
|
||||
}
|
||||
@@ -1233,20 +1235,14 @@ namespace jdi
|
||||
{
|
||||
return EndEdit(true);
|
||||
}
|
||||
if (character == L'\b')
|
||||
{
|
||||
return m_edit.Backspace();
|
||||
}
|
||||
if (character == 0x7F)
|
||||
{
|
||||
return m_edit.DeleteForward();
|
||||
}
|
||||
// 百分比字段允许直接输入 % 但会被忽略,其余控制字符一律丢弃。
|
||||
if (m_editField == StyleEditField::OPACITY && character == L'%')
|
||||
// 退格/删除已由 WM_KEYDOWN 的 OnEditKey 处理:这里必须忽略控制字符,
|
||||
// 否则同一次按键会被处理两次(一次删除两个字符)。
|
||||
if (character < 0x20 || character == 0x7F)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (character < 0x20)
|
||||
// 百分比字段允许直接输入 % 但会被忽略,其余控制字符一律丢弃。
|
||||
if (m_editField == StyleEditField::OPACITY && character == L'%')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1294,13 +1290,32 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SignalPanel::CollapseEditSelectionToEnd()
|
||||
{
|
||||
if (m_editField == StyleEditField::NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_edit.DeselectToEnd();
|
||||
}
|
||||
|
||||
const SignalStyle* SignalPanel::StyleOf(const SignalRef& ref) const
|
||||
{
|
||||
return (m_dataSet != nullptr) ? m_dataSet->Style(ref) : nullptr;
|
||||
}
|
||||
|
||||
SignalStyle* SignalPanel::StyleOf(const SignalRef& ref)
|
||||
{
|
||||
return (m_dataSetWritable != nullptr) ? m_dataSetWritable->Style(ref) : nullptr;
|
||||
}
|
||||
|
||||
bool SignalPanel::ApplyColor(std::uint32_t color)
|
||||
{
|
||||
if (m_tab == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SignalStyle* style = m_tab->Style(m_tab->Selected());
|
||||
SignalStyle* style = StyleOf(m_tab->Selected());
|
||||
if (style == nullptr)
|
||||
{
|
||||
return false;
|
||||
@@ -1319,7 +1334,7 @@ namespace jdi
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SignalStyle* style = m_tab->Style(m_tab->Selected());
|
||||
SignalStyle* style = StyleOf(m_tab->Selected());
|
||||
if (style == nullptr)
|
||||
{
|
||||
return false;
|
||||
@@ -1400,7 +1415,7 @@ namespace jdi
|
||||
{
|
||||
hoverStyle = StyleHit::TOGGLE_ALL;
|
||||
}
|
||||
else if (m_tab != nullptr && m_tab->Style(m_tab->Selected()) != nullptr)
|
||||
else if (m_tab != nullptr && StyleOf(m_tab->Selected()) != nullptr)
|
||||
{
|
||||
for (int index = 0; index < m_colorCount; ++index)
|
||||
{
|
||||
@@ -1513,7 +1528,7 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 样式编辑区。
|
||||
if (SignalStyle* style = m_tab->Style(m_tab->Selected()))
|
||||
if (SignalStyle* style = StyleOf(m_tab->Selected()))
|
||||
{
|
||||
int paletteCount = 0;
|
||||
const std::uint32_t* palette = SignalPalette(paletteCount);
|
||||
@@ -1641,7 +1656,7 @@ namespace jdi
|
||||
const float fy = static_cast<float>(y);
|
||||
if (UiPointInRect(m_customChipRect, fx, fy) && m_tab != nullptr)
|
||||
{
|
||||
if (const SignalStyle* style = m_tab->Style(m_tab->Selected()))
|
||||
if (const SignalStyle* style = StyleOf(m_tab->Selected()))
|
||||
{
|
||||
changed = OpenColorPickerPopup(*style) || changed;
|
||||
}
|
||||
@@ -1789,7 +1804,7 @@ namespace jdi
|
||||
return;
|
||||
}
|
||||
|
||||
const SignalStyle* style = m_tab->Style(ref);
|
||||
const SignalStyle* style = StyleOf(ref);
|
||||
const SignalData* signal = m_dataSet->Resolve(ref);
|
||||
if (style == nullptr || signal == nullptr)
|
||||
{
|
||||
@@ -1807,7 +1822,7 @@ namespace jdi
|
||||
items.push_back(header);
|
||||
|
||||
MenuItem toggle;
|
||||
toggle.label = style->visible ? L"隐藏该信号" : L"显示该信号";
|
||||
toggle.label = m_tab->IsVisible(ref) ? L"隐藏该信号" : L"显示该信号";
|
||||
toggle.id = CMD_SIGNAL_TOGGLE;
|
||||
items.push_back(toggle);
|
||||
|
||||
@@ -2038,7 +2053,7 @@ namespace jdi
|
||||
}
|
||||
else if (result.id == CMD_COLOR_BASE && result.swatchIndex >= 0)
|
||||
{
|
||||
if (SignalStyle* style = m_tab->Style(m_menuTargetSignal))
|
||||
if (SignalStyle* style = StyleOf(m_menuTargetSignal))
|
||||
{
|
||||
int paletteCount = 0;
|
||||
const std::uint32_t* palette = SignalPalette(paletteCount);
|
||||
@@ -2047,7 +2062,7 @@ namespace jdi
|
||||
}
|
||||
else if (result.id >= CMD_MODE_BASE && result.id < CMD_MODE_BASE + kModeOptionCount)
|
||||
{
|
||||
if (SignalStyle* style = m_tab->Style(m_menuTargetSignal))
|
||||
if (SignalStyle* style = StyleOf(m_menuTargetSignal))
|
||||
{
|
||||
style->mode = kModeOptions[result.id - CMD_MODE_BASE];
|
||||
}
|
||||
|
||||
@@ -157,6 +157,12 @@ namespace jdi
|
||||
*/
|
||||
bool OnEditKey(WPARAM key, TabBarResult& result);
|
||||
|
||||
/**
|
||||
* @brief 编辑态下按下鼠标右键:取消选区并把插入符移到文本末尾。
|
||||
* @return 发生改变返回 true;不在编辑态返回 false。
|
||||
*/
|
||||
bool CollapseEditSelectionToEnd();
|
||||
|
||||
/**
|
||||
* @brief 查询拖拽排序时应当插入的位置。
|
||||
* @param [IN] x 客户区横坐标。
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace jdi
|
||||
const float kAddGap = 6.0f; /**< 新建按钮与末个标签的间距。 */
|
||||
const float kCorner = 5.0f; /**< 标签圆角半径。 */
|
||||
const float kMaxTabWidth = 200.0f; /**< 单个标签最大宽度(逻辑像素)。 */
|
||||
const float kEditingTabWidth = 320.0f; /**< 重命名输入时标签的最大宽度(便于看清长名称)。 */
|
||||
const float kMinTabWidth = 70.0f; /**< 单个标签最小宽度(逻辑像素)。 */
|
||||
}
|
||||
|
||||
TabBar::TabBar() = default;
|
||||
@@ -72,16 +74,50 @@ namespace jdi
|
||||
const float tabHeight = kTabHeight * m_scale;
|
||||
const float top = area.Y + kTopMargin * m_scale;
|
||||
const float closeSize = kCloseSize * m_scale;
|
||||
const float maxWidth = kMaxTabWidth * m_scale;
|
||||
const float minWidth = 70.0f * m_scale;
|
||||
const float minWidth = kMinTabWidth * m_scale;
|
||||
const float addSize = kAddSize * m_scale;
|
||||
|
||||
float cursorX = area.X + kLeftPadding * m_scale;
|
||||
|
||||
for (TabItem& tab : m_tabs)
|
||||
// 标签宽度按「加粗字体」测量:激活页用 uiBold 绘制,
|
||||
// 若按常规字体测量,加粗后的长名称会在激活时被省略号截断(预览过短)。
|
||||
std::vector<float> desired;
|
||||
desired.reserve(m_tabs.size());
|
||||
float total = kLeftPadding * m_scale;
|
||||
for (std::size_t index = 0; index < m_tabs.size(); ++index)
|
||||
{
|
||||
const float textWidth = UiMeasureTextWidth(m_fonts->ui, tab.name);
|
||||
const bool editing = (static_cast<int>(index) == m_editIndex);
|
||||
|
||||
// 重命名过程中按「正在输入的文本」测量,标签才能随输入逐步变宽;
|
||||
// 否则始终按已提交的旧名称算宽度,长名称会被挤成很短的一截(预览过短)。
|
||||
const std::wstring& measureText = editing ? m_edit.Text() : m_tabs[index].name;
|
||||
const float boldWidth = UiMeasureTextWidth(m_fonts->uiBold, measureText);
|
||||
const float regularWidth = UiMeasureTextWidth(m_fonts->ui, measureText);
|
||||
const float textWidth = std::max(boldWidth, regularWidth);
|
||||
|
||||
const float maxWidth = (editing ? kEditingTabWidth : kMaxTabWidth) * m_scale;
|
||||
|
||||
float width = kTabPadding * 2.0f * m_scale + textWidth + kCloseGap * m_scale + closeSize;
|
||||
width = std::max(minWidth, std::min(width, maxWidth));
|
||||
desired.push_back(width);
|
||||
total += width + kAddGap * m_scale;
|
||||
}
|
||||
total += addSize;
|
||||
|
||||
// 标签总宽超出可用宽度时整体等比收缩,保证「+」按钮始终可见。
|
||||
const float available = area.Width - kLeftPadding * m_scale;
|
||||
float shrink = 1.0f;
|
||||
if (total > available && total > 0.0f)
|
||||
{
|
||||
shrink = available / total;
|
||||
}
|
||||
|
||||
float cursorX = area.X + kLeftPadding * m_scale;
|
||||
for (std::size_t index = 0; index < m_tabs.size(); ++index)
|
||||
{
|
||||
TabItem& tab = m_tabs[index];
|
||||
float width = std::max(minWidth * shrink, desired[index] * shrink);
|
||||
|
||||
// 收缩后仍要保证「文本 + 关闭按钮」有最小可用空间。
|
||||
width = std::max(width, closeSize + kTabPadding * m_scale + 8.0f * m_scale);
|
||||
|
||||
tab.rect = gd::RectF(cursorX, top, width, tabHeight);
|
||||
tab.closeRect = gd::RectF(cursorX + width - kTabPadding * m_scale - closeSize,
|
||||
@@ -90,8 +126,8 @@ namespace jdi
|
||||
cursorX += width + kAddGap * m_scale;
|
||||
}
|
||||
|
||||
const float addSize = kAddSize * m_scale;
|
||||
m_addRect = gd::RectF(cursorX, top + (tabHeight - addSize) * 0.5f, addSize, addSize);
|
||||
m_addRect = gd::RectF(std::min(cursorX, area.X + area.Width - addSize - 2.0f * m_scale),
|
||||
top + (tabHeight - addSize) * 0.5f, addSize, addSize);
|
||||
}
|
||||
|
||||
void TabBar::Paint(gd::Graphics& g)
|
||||
@@ -421,15 +457,8 @@ namespace jdi
|
||||
OnEditKey(VK_RETURN, result);
|
||||
return true;
|
||||
}
|
||||
if (character == L'\b')
|
||||
{
|
||||
return m_edit.Backspace();
|
||||
}
|
||||
if (character == 0x7F)
|
||||
{
|
||||
return m_edit.DeleteForward();
|
||||
}
|
||||
if (character < L' ')
|
||||
// 退格/删除已由 WM_KEYDOWN 的 OnEditKey 处理,此处忽略全部控制字符。
|
||||
if (character < L' ' || character == 0x7F)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -491,6 +520,15 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TabBar::CollapseEditSelectionToEnd()
|
||||
{
|
||||
if (m_editIndex < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_edit.DeselectToEnd();
|
||||
}
|
||||
|
||||
bool TabBar::OnRButtonDown(int x, int y, int screenX, int screenY, TabBarResult& result)
|
||||
{
|
||||
result = TabBarResult();
|
||||
|
||||
@@ -27,8 +27,7 @@ namespace jdi
|
||||
CURSOR, /**< 切换时间光标。 */
|
||||
MERGE_LANES, /**< 合并为单一网格。 */
|
||||
SPLIT_LANES, /**< 按信号拆分为多网格。 */
|
||||
SELECT_ALL, /**< 勾选当前标签页全部信号。 */
|
||||
CLEAR_ALL /**< 取消当前标签页全部信号勾选。 */
|
||||
CLEAR_ALL /**< 清空当前标签页的全部网格与信号勾选。 */
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,9 +72,8 @@ namespace jdi
|
||||
{ToolbarAction::ZOOM_VALUE, L"Y 轴缩放", false, true, true},
|
||||
{ToolbarAction::CURSOR, L"光标", false, true, true},
|
||||
{ToolbarAction::MERGE_LANES, L"合并网格", false, true, false},
|
||||
{ToolbarAction::SPLIT_LANES, L"拆分信号网格", false, true, false},
|
||||
{ToolbarAction::SELECT_ALL, L"全选信号", false, true, false},
|
||||
{ToolbarAction::CLEAR_ALL, L"清空勾选", false, true, false},
|
||||
{ToolbarAction::SPLIT_LANES, L"拆分网格", false, true, false},
|
||||
{ToolbarAction::CLEAR_ALL, L"清空网格", false, true, false},
|
||||
};
|
||||
const std::size_t specCount = sizeof(specs) / sizeof(specs[0]);
|
||||
|
||||
|
||||
Reference in new issue
Block a user