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. BZOJ2083: [Poi2010]Intelligence test

    2083: [Poi2010]Intelligence test Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 241  Solved: 96[Sub ...

  2. TSSAO Temporal Screen-Space Ambient Occlusion (Unity3d 5 示例实现)

    前提 环境光(ambient occlusion)是一种GI,其简化形式SSAO可以用“微量高效”来形容,消耗得很少,得到的效果很好.环 境光遮蔽(ambient occlusion)的本质是计算在一 ...

  3. Android学习笔记(二)Manifest文件节点详解

    在上一篇博文中简单介绍了Manifest文件及其存放位置,本篇就来详细介绍一下Manifest文件中的节点和一些节点的基本作用,首先看一下Manifest文件最基本的结构: <manifest ...

  4. C++之函数指针

    函数指针常用的有三类 1.指向普通函数的函数指针 2.指向类中静态成员函数的函数指针 3.指向类的成员函数的函数指针 一.指向普通函数的函数指针 #include <iostream> u ...

  5. 自动化运维工具Ansible详细部署 - 人生理想在于坚持不懈 - 51CTO技术博客

    自动化运维工具Ansible详细部署 - 人生理想在于坚持不懈 - 51CTO技术博客 自动化运维工具Ansible详细部署

  6. ios打包ipa的四种实用方法

    总结一下,目前.app包转为.ipa包的方法有以下几种: 1.Apple推荐的方式,即实用xcode的archive功能 Xcode菜单栏->Product->Archive->三选 ...

  7. 细谈Java

    重载:相同函数名,不同参数. 重写(覆写):父类和子类之间的,子类重写了父类的方法. java的多态:重载+覆写 1.      Main方法: 是public的,也是static,也是void的,参 ...

  8. ORA-01858: a non-numeric character was found where a numeric was expected

    [ERROR] [2017-01-05 13:18:52,617] [org.hibernate.engine.jdbc.spi.SqlExceptionHelper.http-bio-8080-ex ...

  9. SQL-LINQ-Lambda语法对照

    SQL LINQ Lambda SELECT *FROM HumanResources.Employee from e in Employees select e Employees .Select ...

  10. windows下 tomcat7 配置成服务

    最简单方法:下载windows安装版,下一步下一步搞定! 非安装版: 1.下载tomcat7 windows版 2.首先找到F:\apache\bin\service.bat(不同的计算机Tomcat ...