Files
Just-DataInspector/Utilities/Inc/ColorPickerPopup.hpp
T
DeepSeek V4.1 Flash bc8db72f18 修复色域拖拽回弹、网格 GX 拖拽重排、均值滤波多阶与逐信号条目
1. 自定义颜色面板:拖动时鼠标移出控件不再"弹回去"
   - 根因是选点位置每帧都从 RGB 反推:色相 1 与 0 是同一个颜色(红),拖到色相条底部
     下一帧反推回 0,指示条弹到顶部;拖到色域方块右下角之外时明度为 0(黑色),
     黑色的饱和度无定义,反推得 0,选点弹到最左边。
   - 现在把"选点位置(饱和度/明度)与色相"作为独立状态保存,只由对应控件与外部改色维护;
     越界一律夹取到最近边界,因此鼠标离开控件后选点停在边界上。
   - 统一色域方块、色相条、预览色块与色号框的圆角;RGB 滑块适当加粗;
     色域选点去掉外圈深色描边(只保留白色描边),色相指示器改为白色描边的圆角长方条、
     底色取该位置的颜色;色相条改用分段线性渐变绘制。

2. 网格 GX 编号支持拖拽重排
   - 按住网格图例左侧的 GX 徽标即可上下拖动,整块网格(连同波形与标尺)跟随鼠标移动;
     途经的网格保持自身大小、整体让出一格,目标槽位有吸附高亮提示。
   - 松开后真正重排并从下到上/从上到下重新编号,信号列表徽标、图例徽标、菜单与浮动面板
     中所有 GX 编号同步更新;手动排布后切换为自定义划分,后续重建网格不会打乱次序。
   - 拖动期间复用静态场景缓存,按网格条带平移贴图,因此大文件下依然流畅。

3. 均值滤波升级为"均值点数 X + 阶数 N"
   - 原"均值点数 N"更名"均值点数 X",新增"阶数 N":预览曲线以 X 点均值滤波的结果为蓝本,
     再重复同样的滤波共 N 阶(1 阶即与原来的单次滤波逐位一致)。
   - 浮动面板改为逐信号条目:第一排是信号名与复选框,第二排是均值点数与阶数输入;
     只有勾选的信号才绘制预览曲线,未勾选时不显示第二排;启用功能时默认只勾选第一个信号。
   - 参数按信号保存(同一网格内不同信号可用不同参数),「构建为新信号」按各自参数构建。
2026-10-10 15:33:45 +08:00

314 lines
9.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef __COLOR_PICKER_POPUP_HPP__
#define __COLOR_PICKER_POPUP_HPP__
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include <array>
#include <cstdint>
#include <memory>
#include "PaintUtil.hpp"
#include "Theme.hpp"
namespace jdi
{
/**
* @brief 浮动式 RGB 自定义颜色拾取器。
*
* 与弹出菜单一致,不创建独立窗口,而是作为宿主绘制的最后一步直接画到后备缓冲区,
* 以浮动面板的形式出现在触发控件附近;取值变化实时生效,点击面板外部即收起。
*/
class ColorPickerPopup
{
public:
ColorPickerPopup();
/**
* @brief 绑定字体与主题资源。
* @param [IN] fonts 字体集合,需在弹出面板生命周期内保持有效。
* @param [IN] theme 主题配色,需在弹出面板生命周期内保持有效。
*/
void Setup(const UiFonts* fonts, const Theme* theme);
/**
* @brief 设置 DPI 缩放比例。
* @param [IN] scale 缩放比例(1.0 对应 96 DPI)。
*/
void SetScale(float scale);
/**
* @brief 在指定位置打开拾色面板。
* @param [IN] bounds 宿主客户区矩形,用于把面板限制在窗口内。
* @param [IN] anchor 触发控件矩形(客户区坐标),面板会紧贴其下方或上方展开。
* @param [IN] color 初始颜色(0xAARRGGBB,仅取 RGB 分量)。
*/
void Open(const gd::RectF& bounds, const gd::RectF& anchor, std::uint32_t color);
/**
* @brief 关闭拾色面板。
*/
void Close();
/**
* @brief 判断拾色面板是否处于打开状态。
* @return 打开返回 true。
*/
bool IsOpen() const { return m_open; }
/**
* @brief 获取当前取色结果。
* @return 0xAARRGGBB 颜色值(Alpha 固定为不透明)。
*/
std::uint32_t CurrentColor() const;
/**
* @brief 取出并清除“取值已变化”标记。
* @param [OUT] color 变化后的颜色。
* @return 自上次调用以来取值发生过变化返回 true。
*/
bool TakeChanged(std::uint32_t& color);
/**
* @brief 绘制拾色面板(应在宿主绘制的最后一步调用)。
* @param [IN,OUT] g 绘图上下文。
*/
void Paint(gd::Graphics& g);
/**
* @brief 处理鼠标移动(含滑块拖拽)。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 需要重绘返回 true。
*/
bool OnMouseMove(int x, int y);
/**
* @brief 处理鼠标左键按下。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 事件已被消费返回 true。
*/
bool OnLButtonDown(int x, int y);
/**
* @brief 处理鼠标左键抬起。
* @return 需要重绘返回 true。
*/
bool OnLButtonUp();
/**
* @brief 处理键盘按键(Esc 收起、回车提交输入)。
* @param [IN] key 虚拟键码。
* @return 事件已被消费返回 true。
*/
bool OnKeyDown(WPARAM key);
/**
* @brief 处理字符输入。
* @param [IN] character 输入字符('\r' 提交、'\b' 退格、0x03 复制)。
* @return 界面状态发生变化返回 true。
*/
bool OnChar(wchar_t character);
/**
* @brief 判断面板是否处于内联文本输入状态。
* @return 正在输入返回 true。
*/
bool IsEditing() const { return m_editField != EditField::NONE; }
/**
* @brief 编辑态下按下鼠标右键:取消选区并把插入符移到文本末尾。
* @return 发生改变返回 true;不在编辑态返回 false。
*/
bool CollapseEditSelectionToEnd();
/**
* @brief 编辑态下在输入框内按下鼠标左键:定位插入符并开始拖拽框选。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 命中输入框返回 true。
*/
bool OnEditMouseDown(int x, int y);
/**
* @brief 编辑态下拖拽鼠标:扩展选区。
* @param [IN] x 客户区横坐标。
* @return 选区发生变化返回 true。
*/
bool OnEditMouseMove(int x);
/**
* @brief 编辑态下松开鼠标左键:结束拖拽框选。
* @return 之前处于拖拽中返回 true。
*/
bool OnEditMouseUp();
/**
* @brief 设置剪贴板所有者窗口句柄(用于输入框的复制/粘贴)。
* @param [IN] owner 宿主窗口句柄,允许为空。
*/
void SetOwnerWindow(HWND owner) { m_owner = owner; }
/**
* @brief 判断坐标是否落在拾色面板内部。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 落在内部返回 true。
*/
bool ContainsPoint(int x, int y) const;
/**
* @brief 查询该坐标应使用的系统光标。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 系统光标句柄;面板未打开时返回空指针。
*/
HCURSOR QueryCursor(int x, int y) const;
private:
/**
* @brief 面板内可进行内联文本输入的字段。
*/
enum class EditField : int
{
NONE = 0, /**< 未处于编辑状态。 */
R = 1, /**< 红色分量(0-255)。 */
G = 2, /**< 绿色分量(0-255)。 */
B = 3, /**< 蓝色分量(0-255)。 */
HEX = 4 /**< 十六进制色号。 */
};
/**
* @brief 面板内正在拖拽的控件。
*/
enum class DragTarget : int
{
NONE = 0, /**< 未拖拽。 */
SLIDER = 1, /**< RGB 分量滑块(具体下标见 `m_draggingSlider`)。 */
GAMUT = 2, /**< 色域方块(饱和度-明度平面)。 */
HUE = 3 /**< 色相条。 */
};
/**
* @brief 依据锚点矩形与客户区计算面板矩形与内部控件矩形。
* @param [IN] bounds 宿主客户区矩形。
* @param [IN] anchor 触发控件矩形。
*/
void BuildLayout(const gd::RectF& bounds, const gd::RectF& anchor);
/**
* @brief 进入指定字段的内联输入状态。
* @param [IN] field 目标字段。
* @return 状态发生变化返回 true。
*/
bool BeginEdit(EditField field);
/**
* @brief 结束内联输入状态。
* @param [IN] commit 是否提交输入值。
* @return 状态发生变化返回 true。
*/
bool EndEdit(bool commit);
/**
* @brief 计算当前输入框的矩形。
* @return 输入框矩形。
*/
gd::RectF EditFieldRect() const;
/**
* @brief 查询坐标命中的滑块下标。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 滑块下标(0=R,1=G,2=B),未命中返回 -1。
*/
int SliderAtPoint(int x, int y) const;
/**
* @brief 应用滑块拖拽位置到对应通道。
* @param [IN] index 滑块下标。
* @param [IN] x 客户区横坐标。
*/
void ApplySlider(int index, float x);
/**
* @brief 在色域方块内按下/拖拽:按位置取出饱和度与明度。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
*/
void ApplyGamut(float x, float y);
/**
* @brief 在色相条内按下/拖拽:按纵向位置取出色相。
* @param [IN] y 客户区纵坐标。
*/
void ApplyHue(float y);
/**
* @brief 当前有效色相(0-1)。灰度/黑色时 HSV 的色相无定义,沿用上一次的有效值。
* @return 色相。
*/
double CurrentHue() const;
/**
* @brief RGB 分量变化后刷新派生的色相与饱和度/明度选择状态。
*/
void RefreshSelectionFromChannels();
/**
* @brief 销毁色域方块缓存位图(尺寸或色相变化时重建)。
*/
void InvalidateGamutCache();
/**
* @brief 确保色域方块缓存与当前色相/缩放匹配(必要时重建)。
* @return 可用返回 true。
*/
bool EnsureGamutCache();
const UiFonts* m_fonts = nullptr; /**< 字体资源。 */
const Theme* m_theme = nullptr; /**< 主题资源。 */
HWND m_owner = nullptr; /**< 剪贴板所有者窗口句柄。 */
float m_scale = 1.0f; /**< DPI 缩放比例。 */
bool m_open = false; /**< 是否处于打开状态。 */
bool m_changed = false; /**< 取值是否发生变化。 */
int m_draggingSlider = -1; /**< 正在拖拽的滑块下标,-1 表示未拖拽。 */
int m_hoverSlider = -1; /**< 当前悬停的滑块下标。 */
DragTarget m_drag = DragTarget::NONE; /**< 正在拖拽的控件。 */
std::array<double, 3> m_channels = {0.0, 0.0, 0.0}; /**< RGB 分量(0-1,唯一数据源)。 */
double m_hue = 0.0; /**< 记忆色相(0-1);灰度/黑色时保留上次取值。 */
/**
* @brief 色域方块上的选点位置(饱和度/明度,均为 0-1)。
*
* 必须**与 RGB 分开保存**:黑色/灰色的饱和度无定义,若每帧都从 RGB 反推,
* 把选点拖到方块右边界之外(明度归零)时选点会瞬间弹回最左侧。
*/
double m_sat = 0.0;
double m_val = 0.0;
gd::RectF m_rect; /**< 面板整体矩形。 */
gd::RectF m_titleRect; /**< 标题区域。 */
std::array<gd::RectF, 3> m_tracks; /**< 滑块轨道矩形。 */
std::array<gd::RectF, 3> m_values; /**< 数值文本矩形。 */
gd::RectF m_previewRect; /**< 预览色块矩形。 */
gd::RectF m_hexRect; /**< 十六进制文本矩形。 */
gd::RectF m_gamutRect; /**< 色域方块矩形(横轴 S,纵轴 V)。 */
gd::RectF m_hueRect; /**< 色相条矩形(纵轴色相)。 */
/** 色域方块缓存位图:按当前色相预渲染,避免每帧重画数十条渐变。 */
std::unique_ptr<gd::Bitmap> m_gamutCache;
double m_gamutCacheHue = -1.0; /**< 缓存对应的色相。 */
int m_gamutCacheSize = 0; /**< 缓存位图边长(像素)。 */
EditField m_editField = EditField::NONE; /**< 当前处于输入状态的字段。 */
TextEdit m_edit; /**< 输入框编辑模型(光标/选区/剪贴板)。 */
};
} // namespace jdi
#endif // __COLOR_PICKER_POPUP_HPP__