点击测试(hit_test), 用于查找(检测)指定鼠标点(x, y) 位置有什么对象, 以及如果存在
多个对象的情况下, 以什么样的策略进行处理. 现在先看如何检测到有哪个对象.
为检测而提供的函数当前为 GeoPad.hit_test(x, y, mode). 其中参数 x,y 表示鼠标位置,
mode 是一个对象或null, 表示一些选项.
作为 hit_test() 函数的返回结果, 也是一个对象, 而且由于有多重需求, 这一返回需要仔细
分析. 下面先列出对 hit_test() 函数有哪些需求:
1. 当鼠标移动的时候, 未按下, 将根据在当前鼠标可进行的操作(一般是选中), 或在何种对象
上面, 需要给出 a)新的鼠标指针样式; b)可能的statusText,根据鼠标下对象和操作.
此时需要检测到鼠标位置有哪个(或哪些)对象, 并判断有哪些可能的操作.
2. 当选中某一工具操作时, 如画点, 画线, 画圆等工具时, 区分为鼠标按下, 移动, 弹起几种
不同事件, 对鼠标当前位置的对象进行检测. 将根据检测结果产生不同操作.
3. 新的需求, 如近期想做的移动标签(Label)功能, 也与 hit_test 有关, 具体如何做, 与原有
功能如何结合是需要仔细考虑的.
如果以类来描述 hit_test 返回结果, 假设称之为 HitTestResult 类, 则伪代码可如下:
class HitTestResult {
int length; // 此次 hit_test 找到的对象数.
operator []; // 通过索引 0..length-1 可以访问到被点击中的第 i 个对象.
// 相当于用对象模拟数组的访问方式, 原因是(过去)此对象是当一个数组用的.
Point[] points; // 所有被点击中的点的数组.
Object[] hit_ret; // 通过此数组可访问到第 i 个对象的 hit_test() 返回值.
}
当前使用这样的(相对有点冗余有点乱)的结构是为了满足多个不同地方的需求, 也许将这个
返回的对象真的变成类以及重构, 可以减少点这种混乱的.
(*) HitTestResult 类中数据的产生.
在函数 GeoPad.hit_test() 中, 遍历所有对象(不含 tmp 的, 这是一个问题), 对它们调用
各自的 hit_test() 函数, 返回非空(非假)值的, 就认为被击中, 放入 [] 数组中; 返回值
放入 hit_ret[] 数组中; 如果此对象是一个点(Point), 则还放入 points[] 数组中.
比如说, 如果一次点击测试, 鼠标在两条线l1,l2和一个点p1上面, 则这个对象看起来应如下:
{ length:3, 0:l1, 1:l2, 2:p1, points: [p1], hit_ret[true,true,true] ... }
的确是有点乱... 如果不包装为一个更清晰的对象, 似乎不好再改进或增加什么新功能了.
(*) hit_test() 的使用.
1. 在 def_mouse_move() --- 缺省的鼠标移动处理, 此时未选中任何工具, 或称是缺省
SELECT 工具也可. 此时:
var hit_objs = pad.hit_test(x, y, ...); // 执行点击测试.
if (hit_objs.length > 0) // 有至少一个对象在当前鼠标位置.
pad.set_cursor(pointer) -- 鼠标切换为手形(表示可选择,移动等操作)
2. def_mouse_down() --- 缺省鼠标按下, 未选中任何工具时.
var hit_objs = pad.hit_test(x, y, ...); // 点击测试.
if (hit_objs.length > 0)
根据被点击中的对象执行... 选中,取消选中 操作
else
表示点到空白位置, 此时取消所有对象的选中.
TODO: 由于鼠标各种情况下的处理比较多样和复杂, 分解它们到合适的类中.
然后优化 hit_test(), 为移动 Label 做准备.