Direct 2D实现界面库 (1)

http://www.cnblogs.com/mmc1206x/p/3924580.html

上篇说完了每个 LNode 的绘制过程. 也就是 onDraw 的实现.

这篇谈谈消息响应函数 onInput.

onInput 实现

Windows 窗口程序都有一个 WndProc 函数, 该函数作为消息响应的回调.

一旦有消息到窗口, 系统就会调用该窗口的 WndProc.

我们实现的控件都是自绘的, 都不具备这个 WndProc 回调.

我们也不能指望系统可以知道对哪个控件发送消息.

那就自己实现这个消息响应吧.

实现流程

这个流程并不复杂.

我们在触发父节点消息的时候, 同时对子节点递归触发.

从而实现冒泡消息响应,  从外到内.

因此我们的 onInput 的实现可能如下:

 void LNode::onInput(UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
     for (auto &child: _childs)
     {
         child->noInput(uMsg, wParam, lParam);
     }
 }

但事实并非如此简单.

想想如何处理鼠标点击响应?

通常只有被点到的那个控件做出响应.

LNode 所有控件都是矩形, 因此计算很方便.

只要点击位置在这个控件矩形内, 则该控件被点击了.

这个过程大概是这样:

  查找被点击的控件.

  如果该控件有子控件, 则返回上一步.

  如果该控件无子控件, 则返回该控件.

这样我们可以得到被点击的那个控件了.

除此之外, 我们需要考虑焦点问题.

例如我在某 LNode 按下鼠标, 然后移出该鼠标范围, 此时的 WM_MOUSEMOVE 响应又被点击的那个 LNode 触发.

当然, 如果不在该 LNode 范围内, 就什么也不做.

焦点

如何让 LNode 获取焦点.

也许你会认为, 直接让 Hit 那个 LNode 获取焦点即可.

情况并非如此, 例如某子节点获取了焦点, 该父节点必然也拥有焦点.

焦点在什么时候会变化?

只有在鼠标按下的时候, 焦点才会发生变化.  tab 也可以, 但是我们不考虑这个.

下面给出 onInput 完整定义. ( 按键处理暂时没写.)

onInput 定义

 void LNode::onInput(UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
     //    特殊处理鼠标消息.
     //    特殊处理键盘消息.
     switch (uMsg)
     {
     case WM_LBUTTONUP:
     case WM_MBUTTONUP:
     case WM_RBUTTONUP:
     case WM_MOUSEMOVE:
         {
             getFocus()->doInput(uMsg, wParam, lParam);
         }
         break;
     case WM_LBUTTONDOWN:
     case WM_MBUTTONDOWN:
     case WM_RBUTTONDOWN:
         {
             D2D1_POINT_2F hitPoint = {
                 (float)GET_X_LPARAM(lParam),
                 (float)GET_Y_LPARAM(lParam)
             };
             auto pHitNode = getHitNode(hitPoint, _scale);
             pHitNode->setFocus();
             pHitNode->doInput(uMsg, wParam, lParam);
         }
         break;
     default:
         //    其他消息全部派发至子节点.
         for (auto &child: _childs)
         {
             child->noInput(uMsg, wParam, lParam);
         }
         break;
     }
 }

这个实现还算简单.

重点就在如何实现 getHitNode.

缩放

LNode *LNode::getHitNode(D2D1_POINT_2F hitPoint, D2D1_SIZE_F scale)
{
    const auto &rect = getHitPointAndRect(scale);
    hitPoint.x -= rect.left;
    hitPoint.y -= rect.top;
    scale.width *= _scale.width;
    scale.height *= _scale.height;
    auto pHitNode = this;
    for (auto &child: _childs)
    {
        const auto &hitRect = child->getHitPointAndRect(scale);
        if ( Utils::containPoint(hitRect, hitPoint) )
        {
            pHitNode = child.get();    //    命中子节点.
        }
    }
    if (pHitNode != this)
    {
        pHitNode = pHitNode->getHitNode(hitPoint, scale);
    }
    return pHitNode;
}

getHitNode 通过点击坐标, 返回被点击的LNode.

前面说了, LNode 都是矩形, 只要知道这个点是否在矩形内就可以得到结果了.

事实上, 远不止你想的这么容易.

不要忘了, 每一个 LNode 都可以作为容器, 每一个子节点的坐标跟父节点都是相对的.

这样做的好处是, 父节点移动可以顺便带着一群子节点.

这还不是难点.

LNode 可以缩放. 这个问题也不是什么难事.

如果父节点缩放了, 子节点会继承父节点的缩放,

因此这里就不能只考虑自身的缩放值, 还要加上父节点缩放值.

这里该如何设计? 是每一次计算都通过 _pParent 递归上去得出最终缩放值?

我用了一个流水线的办法, 就是在最初的根节点传入自己的缩放值, 该值一直被流传下去.

不要忘了, LNode 支持锚点..

缩放点不一样, 那么缩放后的矩形位置也不一样.

例如说,

  一个 100 * 100的矩形在 0, 0的坐标上.

  对这个矩形x, y缩放0.5, 那么该矩形依旧在坐标0, 0处.

  如果这个矩形的锚点在0.5, 0.5, 那情况就不太一样了, 缩放之后的坐标显然也发生了变化,

  因为是从中心开始缩放.

我觉得我已经快说不下去了.

看看 getHitPointAndRect 的实现.

inline D2D1_RECT_F LNode::getHitPointAndRect(const D2D1_SIZE_F &scale)
{
    const auto &curScale =    D2D1::SizeF(
        scale.width * _scale.width,
        scale.height * _scale.height
        );

    //    计算 X 轴, 左右实际尺寸.
    auto xLeft = _contentSize.width * (  + _anchor.x ) * curScale.width;
    auto xRight = _contentSize.width * (  - _anchor.x ) * curScale.width;
    //    计算 Y 轴, 左右实际尺寸.
    auto yLeft = _contentSize.height * (  + _anchor.y ) * curScale.height;
    auto yRight = _contentSize.height * (  - _anchor.y ) * curScale.height;

    return D2D1::RectF(
        _position.x * scale.width - xLeft,
        _position.y * scale.height - yLeft,
        _position.x * scale.width + xRight,
        _position.y * scale.height + yRight );
}

你只要记住, 该函数返回 LNode 的 Hit 区域就行了.

我觉得可以从矩阵中算出这些值. 但是... 我不会~~~

这里再把流程梳理一遍.

举个栗子:

图中,

父节点100*50, 锚点大概就在那个位置, 你应该看到了.

子节点50*25, 锚点在哪你也看到了.

接下来要缩放了.

缩放之后大概就是这样, 向锚点靠拢.

这样的情况下, 就不能纯粹的以 _contentSize 做碰撞检测.

把 _anchor, _position, _scale, _contentSize 统统都要考虑进来, 并且还需要考虑父节点的部分数据..

getHitNode是一个递归调用..

该函数每次递归都需要把传入的 hitPoint 减去自身的坐标, 再去对子节点进行计算.

缩放值基本同理.

源码

如果有兴趣, 可以看看源码.

只写了3天, 时间比较短, Bug难免会有, 不合理设计难免会有,

如果在阅读源码时, 感觉心脏速率过高, 请马上转移注意力.

L Window Demo 下载

Direct 2D实现界面库 (2)的更多相关文章

  1. Direct 2D实现界面库 (1)

    大学时尝试过很多次写一个UI库, 初次使用 GDI 绘图, 当时水平很低, GDI功能太弱, 以失败而告终. 之后使用 GDI+ 绘图, 当时水平依旧很低, GDI功能很强, 但效率实在太慢, 以失败 ...

  2. DirectUI 2D/3D 界面库集合 分析之总结

    DirectUI优点在于能够非常方便的构建高效,绚丽的,非常易于扩展的界面.作者是Bjarke Viksoe, 他的这个界面程序思想和代码都很优秀,他的代码主要表述了他的思想,尽管bug比較多,可是很 ...

  3. C++ 100款开源界面库 (10)

    (声明:Alberl以后说到开源库,一般都是指著名的.或者不著名但维护至少3年以上的.那些把代码一扔就没下文的,Alberl不称之为开源库,只称为开源代码.这里并不是贬低,像Alberl前面那个系列的 ...

  4. 仿迅雷播放器教程 -- C++ 100款开源界面库 (10)

      (声明:Alberl以后说到开源库,一般都是指著名的.或者不著名但维护至少3年以上的.那些把代码一扔就没下文的,Alberl不称之为开源库,只称为开源代码.这里并不是贬低,像Alberl前面那个系 ...

  5. C++界面库

    刚开始用C++做界面的时候,根本不知道怎么用简陋的MFC控件做出比较美观的界面,后来就开始逐渐接触到BCG  Xtreme ToolkitPro v15.0.1,Skin++,等界面库,以及一些网友自 ...

  6. DuiLib DirectUI 界面库

    国内首个开源 的directui 界面库,开放,共享,惠众,共赢,遵循bsd协议,可以免费用于商业项目,目前支持Windows 32 .Window CE.Mobile等平台. Duilib 是一款强 ...

  7. C++界面库(十几种,很全)

    刚开始用C++做界面的时候,根本不知道怎么用简陋的MFC控件做出比较美观的界面,后来就开始逐渐接触到BCG  Xtreme ToolkitPro v15.0.1,Skin++,等界面库,以及一些网友自 ...

  8. JUCE 界面库显示中文乱码问题

    JUCE 界面库显示中文乱码问题 环境: Windows7 64位 旗舰版 Visual Studio Ultimate 2012 JUCE 4.1 问题描述: 直接使用juce::String存储中 ...

  9. BCG界面库下的Windows8 UI界面样式www.webui8.com

    BCG界面库下的Windows8 UI界面样式(Metro风格)控件主要有以下一些功能: 规则的大块磁贴 支持完整键盘导航 Tile组 标题(Caption) 标题按钮(Caption buttons ...

随机推荐

  1. Linux Shell编程(14)——内部变量

    内建变量影响Bash脚本行为的变量.$BASHBash二进制程序文件的路径 bash$ echo $BASH /bin/bash$BASH_ENV该环境变量保存一个Bash启动文件路径,当启动一个脚本 ...

  2. 在html页面中利用ftp访问协议格式载入服务器图片

    访问格式为:ftp://用户名:密码@服务器ip:服务器端口/具体文件路径 如下所示: <img src="ftp://lxj:123@127.0.0.1:21/IMG_2013051 ...

  3. XP与Win2003下网站配置

    一. 安装.net 4.0 1. 双击打开文件dotNetFx40_Full_x86_x64.exe.如图4.1 所示 (图4.1) 2. 勾选[我已阅读并结束许可条款],点击[安装]按钮.如图4.2 ...

  4. web.xml中的contextConfigLocation的作用

    在web.xml中通过contextConfigLocation配置spring,contextConfigLocation 参数定义了要装入的 Spring 配置文件. 如果想装入多个配置文件,可以 ...

  5. 在MacOSX下使用Github管理Xcode代码

    版本控制应该算是每个程序员所必备的技能,这个重要性,我就不多说了哈.现在版本控制基本上就是两种途径:SVN和Git.对于SVN我并不是非常了解,只知道在Windows下非常实用,但是在MacOSX下, ...

  6. content

    http://www.cnblogs.com/lrysjtu/p/4474900.html lexus - 博客园 http://www.cnblogs.com/rio2607/p/4472456.h ...

  7. 10 个用于收集硬件信息的 Linux 命令

    知道自己的Linux系统运行在什么样的硬件组件上总是好的,因为如果涉及到在系统上安装软件包和驱动程序的话,这将有助于你处理兼容性问题. 因此,下面我们将给出一些非常有用的命令,它们可以帮助你提取你的L ...

  8. 找不到这个cache.properties缓存文件

    Android  Studio在导入第三库同步时报错: C:\Users\Administrator\.gradle\caches\2.4\scripts\asLocalRepo88_4u65z0u2 ...

  9. 一个可视化的retrospective网站

    IdeaBoardz - Brainstorm, Retrospect, Collaborate是一个可视化的retrospective,brainstorm的网站,比较简单易用,可以导出pdf和ex ...

  10. 简单的访客IP获取类-IPHelper.cs

    public class IPHelper { public static string GetVisitorsIPAddress() { string result = String.Empty; ...