Direct 2D实现界面库 (1)
大学时尝试过很多次写一个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下载
Direct 2D实现界面库 (1)的更多相关文章
- Direct 2D实现界面库 (2)
Direct 2D实现界面库 (1) http://www.cnblogs.com/mmc1206x/p/3924580.html 上篇说完了每个 LNode 的绘制过程. 也就是 onDraw 的实 ...
- DirectUI 2D/3D 界面库集合 分析之总结
DirectUI优点在于能够非常方便的构建高效,绚丽的,非常易于扩展的界面.作者是Bjarke Viksoe, 他的这个界面程序思想和代码都很优秀,他的代码主要表述了他的思想,尽管bug比較多,可是很 ...
- C++ 100款开源界面库 (10)
(声明:Alberl以后说到开源库,一般都是指著名的.或者不著名但维护至少3年以上的.那些把代码一扔就没下文的,Alberl不称之为开源库,只称为开源代码.这里并不是贬低,像Alberl前面那个系列的 ...
- 仿迅雷播放器教程 -- C++ 100款开源界面库 (10)
(声明:Alberl以后说到开源库,一般都是指著名的.或者不著名但维护至少3年以上的.那些把代码一扔就没下文的,Alberl不称之为开源库,只称为开源代码.这里并不是贬低,像Alberl前面那个系 ...
- C++界面库
刚开始用C++做界面的时候,根本不知道怎么用简陋的MFC控件做出比较美观的界面,后来就开始逐渐接触到BCG Xtreme ToolkitPro v15.0.1,Skin++,等界面库,以及一些网友自 ...
- DuiLib DirectUI 界面库
国内首个开源 的directui 界面库,开放,共享,惠众,共赢,遵循bsd协议,可以免费用于商业项目,目前支持Windows 32 .Window CE.Mobile等平台. Duilib 是一款强 ...
- C++界面库(十几种,很全)
刚开始用C++做界面的时候,根本不知道怎么用简陋的MFC控件做出比较美观的界面,后来就开始逐渐接触到BCG Xtreme ToolkitPro v15.0.1,Skin++,等界面库,以及一些网友自 ...
- JUCE 界面库显示中文乱码问题
JUCE 界面库显示中文乱码问题 环境: Windows7 64位 旗舰版 Visual Studio Ultimate 2012 JUCE 4.1 问题描述: 直接使用juce::String存储中 ...
- BCG界面库下的Windows8 UI界面样式www.webui8.com
BCG界面库下的Windows8 UI界面样式(Metro风格)控件主要有以下一些功能: 规则的大块磁贴 支持完整键盘导航 Tile组 标题(Caption) 标题按钮(Caption buttons ...
随机推荐
- Spark使用CombineTextInputFormat缓解小文件过多导致Task数目过多的问题
目前平台使用Kafka + Flume的方式进行实时数据接入,Kafka中的数据由业务方负责写入,这些数据一部分由Spark Streaming进行流式计算:另一部分数据则经由Flume存储至HDFS ...
- Win32中常用消息
一 .WM_PAINT消息 1 WM_PAINT的产生 由于窗口的互相覆盖等,产生需要绘制的区域,那么会产生WM_PAINT消息. 一般情况下,不直接发送WM_PAINT消息,通过API声明需要绘 ...
- 265行JavaScript代码的第一人称3D H5游戏Demo【个人总结1】
本文目的是分解前面的代码.其实,它得逻辑很清楚,只是对于我这种只是用过 Canvas 画线(用过 Fabric.js Canvas库)的人来说,这个还是很复杂的.我研究这个背景天空也是搞了一天,下面就 ...
- 获得当前屏幕的CGRect
CGRect mainScreen = [[UIScreen mainScreen] bounds];
- [转+整理]LINUX学习笔记(1):磁盘结构及分区
整理自: http://vbird.dic.ksu.edu.tw/linux_basic/0130designlinux_2.php http://lengjianxxxx.blog.163.com/ ...
- springMVC能做什么,做j2ee时候要考虑什么
转载: http://jinnianshilongnian.iteye.com/category/231099 [置顶] 跟我学SpringMVC目录汇总贴.PDF下载.源码下载 博客分类: 跟开涛学 ...
- poj 1258 Agri-Net【最小生成树(prime算法)】
Agri-Net Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 44827 Accepted: 18351 Descri ...
- mysql inner join,full outer join,left join,right jion
https://sites.google.com/site/349624yu/courses/mysql/mysqldbgjzcx inner join,full outer join,left jo ...
- js判断input输入框为空时遇到的问题 弹窗后,光标没有定位到输入框,而是直接执行我的处理页面程序
无论是会员注册还是提交订单,我们都要使用到form表单,此时我们在处理数据时,就要判断用户填写的信息.一次是直接通过js判断input输入框是否没有填信息,然后在后台处理文件中通过过滤字符串后再次判断 ...
- TColorPickerButton组件
http://files.cnblogs.com/xe2011/VCL_TColorPB12.rar 在DELPHI7中可以正常使用 在DELPHI XE5中 下面会有些问题 安装方法 打开 DELP ...