大学时尝试过很多次写一个UI库,

初次使用 GDI 绘图, 当时水平很低, GDI功能太弱, 以失败而告终.

之后使用 GDI+ 绘图, 当时水平依旧很低, GDI功能很强, 但效率实在太慢, 以失败而告终.

现在使用 Direct 2D绘图, 水平还是很低, 但凑合着没多少问题.

Demo展示

Demo写的有点粗糙, 咱们把重点放在 L Window 库上.

设计思路

思路跟cocos2dx有点相似.

每一个控件都继承自虚基类 LNode.

LNode 只需要提供2个纯虚函数:

  doDraw, doInput.

前者用于重写绘制.

后者用于重写消息响应.

定义如下.

class LNode {
    virtual void doDraw(ID2D1RenderTarget *pRT, float opacity) = 0;
    virtual void doInput(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
};

doDraw需要传入2个参数.

pRT 是Direct 2D的渲染器, 它托管了整个窗口, 渲染的工作都归它.

opacity 是顾名思义, 是指透明度, 因为 L Window 所有控件都支持透明度.

每一个 LNode 都保存了一个 _opacity, 那为什么这里还需要传递一个opacity?

因为所有控件都可能作为其他控件的子节点.

如果父节点的opacity是0.5f, 子节点的opacity是1.0f.

此时子节点被绘制出来的透明度应该传承父节点的0.5f.

LNode.h 清单

#pragma once

#include "FileList.h"

namespace mmc {

class LNode {
public:
    typedef std::shared_ptr<LNode>    ChildNodeT;

    LNode();
    virtual ~LNode();
    void onDraw(ID2D1RenderTarget *pRT, float opacity);
    void onInput(UINT uMsg, WPARAM wParam, LPARAM lParam);
    ;
    ;

    void setParent(LNode *pNode);
    void setFocus();
    void setPosition(float x, float y) ;
    void setAnchor(float x, float y);
    void setContentSize(float width, float height);
    void setScale(float width, float height);
    void setZorder(int z);
    void setOpacity(float op);
    void appendChild(LNode *pNode);

    LNode *getParent();
    LNode *getFocus();
    D2D1_POINT_2F getPosition();
    D2D1_POINT_2F getAnchor();
    D2D1_SIZE_F getContentSize();
    D2D1_SIZE_F getScale();
    int getZorder();
    float getOpacity();
    std::vector< ChildNodeT > &getChilds();

private:
    LNode *getHitNode(D2D1_POINT_2F hitPoint, D2D1_SIZE_F scale);
    D2D1_POINT_2F getScalePoint();
    D2D1_POINT_2F getTranslationPoint();
    D2D1_RECT_F getHitPointAndRect(const D2D1_SIZE_F &scale);
    void callDraw(ID2D1RenderTarget *pRT, float opacity,
        const std::function<void (ID2D1RenderTarget *, float)> &callback);
    const D2D1_MATRIX_3X2_F &getMatrix();

    //    data.
    int                _zOrder;
    float            _opacity;
    LNode *            _pFocus;
    LNode *            _pParent;
    D2D1_POINT_2F    _position;
    D2D1_POINT_2F    _anchor;
    D2D1_SIZE_F        _scale;
    D2D1_SIZE_F        _contentSize;
    D2D1_MATRIX_3X2_F _matrix;
    std::vector< ChildNodeT > _childs;
};

};

LNode 定义也比较简单.

除去一切set, get.

剩下的就是 onDraw, onInput, 一些私有成员函数.

LNode::onDraw 实现

咱们先来想想 LNode 该如何实现整个绘制流程.

既然每一个 LNode 都可能作为父节点, 那么每一个 LNode 绘制都需要把子节点绘制出来.

因此可能会出现如下代码.

//    绘制自身.
doDraw();

for_each (_childs)
{
    child->doDraw();
}

结果已经很接近了, 但是并非这么简单.

因为 LNode 还支持Z序, 因此还要考虑子节点绘制顺序问题.

Z序也可能是负数, 所以这也是在考虑的范围内.

同时还要考虑相对坐标的问题.

因为每一个子节点的坐标都是相对父节点的,

假设父节点的X坐标是100, 子节点的X坐标是100, 显示出来的子节点X坐标是200.

也许你认为在绘制的时候递归父节点得出最终绘制坐标.

但是, D2D为我们提供更科学的方法 ------ 矩阵.

不管2D还是3D, 所有的坐标计算都是通过矩阵计算.

一个矩阵可以记录平移, 缩放, 旋转 信息.

我们把渲染目标看作一块画布.

通常我们画画时, 都是纸不动, 笔动.

这里需要换一下, 纸动, 笔不动.

如果我们的父节点X坐标是100, 那么平移100, 纸被平移了.

再绘制子节点的时候, 再把纸平移100, 位置就已经是200了.

缩放原理也是一样, 这一些信息都由一个 _matrix 矩阵保存.

那我们绘制大概的顺序就是:

子节点 Z 从小到大排序.

遍历子节点逐个调用 onDraw, 遇到Z为0中断.

调用自己的 doDraw.

遍历剩下子节点逐个调用 onDraw.

下面是完整的定义.

void LNode::onDraw(ID2D1RenderTarget *pRT, float opacity)
{
    //    每次根据Z序排序.
    std::sort(std::begin(_childs), std::end(_childs), [&](const ChildNodeT &pNode1, const ChildNodeT &pNode2) {
        return pNode1->getZorder() < pNode2->getZorder();
    });

    //    递归调用绘制各个子节点.
    auto curIter = std::begin(_childs);
    auto endIter = std::end(_childs);

    //    传递透明度.
    opacity -= ( - _opacity);
    opacity = max(opacity, );
    opacity = min(opacity, );
    //    绘制Z序小于0的子节点.
    ; ++curIter)
    {
        callDraw( pRT, opacity, BIND_2(&LNode::onDraw, *curIter) );
    }

    //    绘制自身.
    callDraw( pRT, opacity, BIND_2(&LNode::doDraw, this) );

    for (; curIter != endIter; ++curIter)
    {
        callDraw( pRT, opacity, BIND_2(&LNode::onDraw, *curIter) );
    }
}

里面有一个 callDraw,

该函数负责调用 onDraw, doDraw,

并且控制矩阵变化.

inline void LNode::callDraw(ID2D1RenderTarget *pRT, float opacity, const std::function<void (ID2D1RenderTarget *, float)> &callback)
{
    D2D1_MATRIX_3X2_F saveMatrix;
    pRT->GetTransform(&saveMatrix);
    pRT->SetTransform(getMatrix() * saveMatrix);
    callback(pRT, opacity);
    pRT->SetTransform(saveMatrix);
}

每一次绘制结束后, 都需要还原绘制前的矩阵.

这里出现了一个 getMatrix() 函数..

该函数就纯粹的返回当前对象的矩阵, 结果与当前矩阵叉乘, 得到需要的矩阵.

叉乘是什么?

这玩意儿很让人头大,

这让没有上过高中的我情何以堪,

不过还好, 这玩意儿大学才有, 而我恰巧上过大学.

但是, 我初中之后数学就没上过50分~~~

 //    该方法被需要绘制该Node的操作调用.
const D2D1_MATRIX_3X2_F &LNode::getMatrix()
{
    //    缩放.
    D2D1_MATRIX_3X2_F scale = D2D1::Matrix3x2F::Scale(_scale, getScalePoint());

    //    平移.
    const auto &transPoint = getTranslationPoint();
    D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(transPoint.x, transPoint.y);

    _matrix = scale * translation;

    return _matrix;}

每一个 LNode 都支持平移, 缩放, 因此每一次返回的矩阵都需要包含这2个信息.

缩放和平移的顺序必须固定. 如果我没有记错的话, 乘法因子的顺序是可以交换的.

好吧, 这里不是乘法, 是叉乘.

这里需要注意的有3点,

第1点, 缩放需要设置一个缩放点, 这个缩放点我们通过 getScalePoint 获得.

第2点, 平移需要考虑缩放值, 锚点, 坐标. LNode 是有锚点的, 难度又提升了一小段.

  我保证这真的只是一小段. 通过 getTranslationPoint 获得最终绘制的坐标.

第3点, 叉乘因子真的不能交换顺序.

inline D2D1_POINT_2F LNode::getScalePoint()
{
    return D2D1::Point2F(
        _anchor.x * _contentSize.width,
        _anchor.y * _contentSize.height
        );
}
_contentSize 保存了 LNode的实际尺寸._anchor 则是锚点, 取值 0-1.因此我们想得到这个缩放点, 只需要把 尺寸 * 锚点. 
inline D2D1_POINT_2F LNode::getTranslationPoint()
{
    return D2D1::Point2F(
        _position.x - _anchor.x * _contentSize.width,
        _position.y - _anchor.y * _contentSize.height
        );
}

因为D2D绘制坐标系原点在左上角.

因此把 LNode 坐标减去锚点偏移的坐标, 就可以得到实际绘制的坐标.

上班时间写博客影响不好.

下次换个上班时间再写..

demo下载

L Window Demo 下载地址!!!

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

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

    Direct 2D实现界面库 (1) http://www.cnblogs.com/mmc1206x/p/3924580.html 上篇说完了每个 LNode 的绘制过程. 也就是 onDraw 的实 ...

  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. Unity FisheyeShader using Spherical Mapping

    Shader "Hidden/FisheyeShader" { Properties { _MainTex ("Base (RGB)", 2D) = " ...

  2. 正则表达式的先行断言(lookahead)和后行断言(lookbehind)

    正则表达式的先行断言和后行断言一共有4种形式: (?=pattern) 零宽正向先行断言(zero-width positive lookahead assertion) (?!pattern) 零宽 ...

  3. Howto Setup yum repositories to update or install package from ISO CDROM Image

    Step # 1: Mount an ISO file Type the following command (replace iso file name with the actual iso fi ...

  4. About using UTF-8 fields in MySQL

    https://www.adayinthelifeof.nl/2010/12/04/about-using-utf-8-fields-in-mysql/ I sometimes hear: “make ...

  5. objective-c保护属性

    #import <Foundation/Foundation.h> @interface ClassVirable : NSObject{ NSInteger year;//保护树形 } ...

  6. sonarQube Scanner

    到现在为止,已经将sonarQube服务器搭建好,服务器是Linux服务器,基于sonarQube6.1配置的 原理关于sonarQube的工作原理,暂时还不是太清楚,据同事的分析,是在本机调用服务器 ...

  7. [转]Android UI:看看Google官方自定义带旋转动画的ImageView-----RotateImageView怎么写(附 图片淡入淡出效果)

    http://blog.csdn.net/yanzi1225627/article/details/22439119 众所周知,想要让ImageView旋转的话,可以用setRotation()让其围 ...

  8. HDU--5280(dp或枚举)

    官方题解: 这个题有非常多O(n2)的算法.这里说一种:枚举每个区间,在枚举区间的同一时候维护区间内的最小值和区间和,将最小值与P的大小进行比較,贪心地取最大值就可以.注意若枚举到的区间是整个数组,则 ...

  9. HTML5事件——contextmenu 隐藏鼠标右键菜单

    在window中单击右键或在Mac中Ctrl+单击时会触发contextmenu事件,通过取消其默认动作能够提供自己定义菜单. 首先先写一个自己的菜单: <style> ul, li { ...

  10. hadoop的wordcount的改动版

    //这个是在原来的基础上改动以后得到的,将当中的分词的根据给换掉了,而且进行词频统计的时候会自己主动的忽略大写和小写 packageorg.apache.hadoop.mapred; importja ...