Direct 2D实现界面库 (2)
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难免会有, 不合理设计难免会有,
如果在阅读源码时, 感觉心脏速率过高, 请马上转移注意力.
Direct 2D实现界面库 (2)的更多相关文章
- Direct 2D实现界面库 (1)
大学时尝试过很多次写一个UI库, 初次使用 GDI 绘图, 当时水平很低, GDI功能太弱, 以失败而告终. 之后使用 GDI+ 绘图, 当时水平依旧很低, GDI功能很强, 但效率实在太慢, 以失败 ...
- 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 ...
随机推荐
- 最大流算法---Edmond-Karp
这个算法是基于FF方法,就是通过不断求残余网络的增广路来增广流量,直到找不到增广路为止.注意:每次找到增广路以后都要更新原网络.EK算法通过BFS寻找源S到汇T的一条最短路径,因此时间复杂度是O(VE ...
- javad的Collection集合
集合框架:★★★★★,用于存储数据的容器. 特点: 1:对象封装数据,对象多了也需要存储.集合用于存储对象. 2:对象的个数确定可以使用数组,但是不确定怎么办?可以用集合.因为集合是可变长度的. 集合 ...
- cocos2d的安装
安装cocos2d其实就是在Xcode中安装几个模板,然后在Xcode里面就可以直接使用这些模板了. 其实说是模板,也就是封装了许许多多引擎的文件,相对于原生的程序,也许使用引擎模板更加方便. 下 ...
- 关于cocos2d和cocos2dx,还有iOS上的cocos2d的ARC问题
好吧,我承认这个我花了N个小时所做的努力都白费了. 事情的开始是这样的,今天在写cocos2dx的时候,测试发现总是出现溢出的问题,总是在main.m的autorelease报错.(好吧,如果我以后发 ...
- OpenStack网络的前世今生
声明: 本文转自OpenStack中国社区,原文链接:http://www.openstack.cn/p353.html,作者Joshua,转载请注明. 在OpenStack世界中,网络组件最初叫no ...
- 利用Trie树对字符串集合进行排序并计算特征值
该算法用于将一组乱序的字符串反序列化到一个Trie树中,这个过程即可视为对字符串进行了一次排序. 还可以通过调用 GetFeatureString 将该 Trie 树重新序列化. #include & ...
- poj 1192最优连通子集(简单树形dp)
题目链接:http://poj.org/problem?id=1192 #include<cstdio> #include<cstring> #include<iostr ...
- java 检查是否是数组 检查是否是空数组 检查数组是否包含某个元素
/** * Determine whether the given object is an array: * either an Object array or a primitive array. ...
- Div 3 - SGU 105(找规律)
分析:很容易知道序列1,2,3, 4,5, 6......与3的关系就是1,2, 0,1, 2,0,......如果是在一个数后面添加一个数就变成了这种序列1, 0, 0, 1, 0, 0, 1, 0 ...
- 自己动手画一个HTML5的按钮
<!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8& ...