#ifndef __POPUP_MENU_HPP__ #define __POPUP_MENU_HPP__ #include #include #include #include #include #include #include "PaintUtil.hpp" #include "Theme.hpp" namespace jdi { /** * @brief 弹出菜单的打开来源,用于把选择结果派发回正确的组件。 */ enum class MenuScope : int { NONE = 0, /**< 无来源。 */ SIGNAL_PANEL = 1, /**< 信号面板。 */ TAB_BAR = 2 /**< 标签栏。 */ }; /** * @brief 弹出菜单项的显示类型。 */ enum class MenuItemKind : int { NORMAL = 0, /**< 普通可点击项。 */ SEPARATOR = 1, /**< 分隔线。 */ HEADER = 2, /**< 分组标题(不可点击)。 */ SWATCHES = 3 /**< 颜色色块行。 */ }; /** * @brief 单个弹出菜单项。 */ struct MenuItem { MenuItemKind kind = MenuItemKind::NORMAL; /**< 项目类型。 */ std::wstring label; /**< 显示文本。 */ int id = 0; /**< 命令标识。 */ bool enabled = true; /**< 是否可用。 */ bool checked = false; /**< 是否处于选中(单选)状态。 */ int swatchCount = 0; /**< 色块数量(仅色块行有效)。 */ std::uint32_t swatches[16] = {}; /**< 色块颜色。 */ int checkedSwatch = -1; /**< 被选中的色块下标。 */ }; /** * @brief 菜单选择结果。 */ struct MenuResult { bool valid = false; /**< 是否产生了有效选择。 */ MenuScope scope = MenuScope::NONE; /**< 来源组件。 */ int id = 0; /**< 被选中项的命令标识。 */ int swatchIndex = -1; /**< 被选中的色块下标(非色块行为 -1)。 */ }; /** * @brief 与界面主题一致的自绘弹出菜单。 * * 菜单不创建独立窗口,而是作为宿主窗口绘制流程的最上层内容直接绘制到后备缓冲区, * 因此外观与主界面完全统一,不会出现系统默认菜单样式。 */ class PopupMenu { public: PopupMenu(); /** * @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] scope 来源组件标识。 * @param [IN] bounds 宿主客户区矩形,用于把菜单限制在窗口内。 * @param [IN] x 锚点横坐标(客户区坐标)。 * @param [IN] y 锚点纵坐标(客户区坐标)。 * @param [IN] items 菜单项集合。 */ void Open(MenuScope scope, const gd::RectF& bounds, int x, int y, const std::vector& items); /** * @brief 关闭菜单。 */ void Close(); /** * @brief 判断菜单是否处于打开状态。 * @return 打开返回 true。 */ bool IsOpen() const { return m_open; } /** * @brief 获取当前菜单的来源组件标识。 * @return 来源标识。 */ MenuScope Scope() const { return m_scope; } /** * @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 客户区纵坐标。 * @param [OUT] result 在菜单外按下时返回被取消的选择结果。 * @return 事件已被菜单消费返回 true。 */ bool OnLButtonDown(int x, int y, MenuResult& result); /** * @brief 处理鼠标左键抬起。 * @param [IN] x 客户区横坐标。 * @param [IN] y 客户区纵坐标。 * @param [OUT] result 选择结果。 * @return 事件已被菜单消费返回 true。 */ bool OnLButtonUp(int x, int y, MenuResult& result); /** * @brief 处理键盘按键。 * @param [IN] key 虚拟键码。 * @param [OUT] result 选择结果。 * @return 事件已被菜单消费返回 true。 */ bool OnKeyDown(WPARAM key, MenuResult& result); /** * @brief 判断坐标是否落在菜单内部。 * @param [IN] x 客户区横坐标。 * @param [IN] y 客户区纵坐标。 * @return 落在菜单内返回 true。 */ bool ContainsPoint(int x, int y) const; private: /** * @brief 计算单个菜单项的纵向高度。 * @param [IN] item 菜单项。 * @return 高度(像素)。 */ float ItemHeight(const MenuItem& item) const; /** * @brief 依据当前项目集合计算整体矩形与各项矩形。 * @param [IN] bounds 宿主客户区矩形。 * @param [IN] x 锚点横坐标。 * @param [IN] y 锚点纵坐标。 */ void BuildLayout(const gd::RectF& bounds, int x, int y); /** * @brief 查询坐标命中的菜单项下标。 * @param [IN] x 客户区横坐标。 * @param [IN] y 客户区纵坐标。 * @param [OUT] swatchIndex 命中的色块下标(非色块行为 -1)。 * @return 项目下标,未命中返回 -1。 */ int ItemAtPoint(int x, int y, int& swatchIndex) const; /** * @brief 将悬停项移动到下一个可用项。 * @param [IN] step 步进(+1 向下,-1 向上)。 */ void MoveHover(int step); /** * @brief 确认当前悬停项并生成结果。 * @param [OUT] result 选择结果。 * @return 生成了有效选择返回 true。 */ bool CommitHover(MenuResult& result); const UiFonts* m_fonts = nullptr; /**< 字体资源。 */ const Theme* m_theme = nullptr; /**< 主题资源。 */ float m_scale = 1.0f; /**< DPI 缩放比例。 */ bool m_open = false; /**< 是否处于打开状态。 */ MenuScope m_scope = MenuScope::NONE; /**< 来源组件标识。 */ std::vector m_items; /**< 菜单项集合。 */ gd::RectF m_rect; /**< 菜单整体矩形。 */ std::vector m_itemRects; /**< 各项矩形。 */ std::vector> m_swatchRects; /**< 各项内的色块矩形。 */ int m_hoverIndex = -1; /**< 当前悬停项。 */ int m_hoverSwatch = -1; /**< 当前悬停色块。 */ int m_pressedIndex = -1; /**< 按下时的项目下标。 */ int m_pressedSwatch = -1; /**< 按下时的色块下标。 */ }; } // namespace jdi #endif // __POPUP_MENU_HPP__