Back to Home

ESO Lua File v101044

libraries/zo_trianglepicker/zo_trianglepicker.lua

[◄ back to folders ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
function ZO_TrianglePoints_SetPoint(points, index, p, isMirrored)
    -- Use of isMirrored assumes normalized points. Might add support for non-normalized points later
    if(index == 3 and isMirrored) then
        points[index] = { x = p.x, y = 1 - p.y }
    else
        points[index] = { x = p.x, y = p.y }
    end
end
local function Dot(x1, y1, x2, y2)
    return x1 * x2 + y1 * y2
end
--[[ ZO_Triangle:
A simple collection of 3 verts that allows point testing to determine if a user-supplied point is inside or outside
the triangle and facilities to clamp a user-supplied point to the nearest edge.
--]]
ZO_Triangle = ZO_Object:Subclass()
function ZO_Triangle:New(points, isMirrored)
    local triangle = ZO_Object.New(self)
    triangle:SetPoints(points)
    triangle.m_isMirrored = isMirrored
    return triangle
end
function ZO_Triangle:SetPoints(points)
    -- Points are defined in CCW order starting at lower left
    self.m_points = ZO_DeepTableCopy(points)
end
function ZO_Triangle:GetPoint(pointIndex)
    return self.m_points[pointIndex]
end
function ZO_Triangle:GetPreviousPoint(pointIndex)
    return pointIndex == 1 and self.m_points[3] or self.m_points[pointIndex - 1]
end
function ZO_Triangle:GetBarycentricCoordinates(x, y)
    local a = self:GetPoint(1)
    local b = self:GetPoint(2)
    local c = self:GetPoint(3)
    local v0 = { x = b.x - a.x, y = b.y - a.y }
    local v1 = { x = c.x - a.x, y = c.y - a.y }
    local v2 = { x = x - a.x, y = y - a.y }
    local denominator = v0.x * v1.y - v1.x * v0.y
    local factor = 1 / denominator
    --Calculate each value and clamp between 0 and 1
    local right = zo_clamp((v2.x * v1.y - v1.x * v2.y) * factor, 0.0, 1.0)
    local top = zo_clamp((v0.x * v2.y - v2.x * v0.y) * factor, 0.0, 1.0)
    local left = zo_clamp(1 - right - top, 0.0, 1.0)
    return left, right, top
end
function ZO_Triangle:GetClosestPointOnTriangle(x, y)
    local minDist = nil
    local closestX, closestY
    for i=1, 3 do
        local currentPoint = self:GetPoint(i)
        local lastPoint = self:GetPreviousPoint(i)
        local edgeX, edgeY = currentPoint.x - lastPoint.x, currentPoint.y - lastPoint.y
        local edgeLength = Dot(edgeX, edgeY, edgeX, edgeY)
        local edgeNormalX, edgeNormalY = -edgeY, edgeX
        local differenceX, differenceY = x - lastPoint.x, y - lastPoint.y
        if Dot(differenceX, differenceY, edgeNormalX, edgeNormalY) > 0 then
            local edgeProjection = zo_clamp(Dot(differenceX, differenceY, edgeX, edgeY) / edgeLength, 0.0, 1.0)
            local pointOnEdgeX, pointOnEdgeY = lastPoint.x + (edgeX * edgeProjection), lastPoint.y + (edgeY * edgeProjection)
            local diffToEdgeX, diffToEdgeY = pointOnEdgeX - x, pointOnEdgeY - y
            local diffToEdgeLength = Dot(diffToEdgeX, diffToEdgeY, diffToEdgeX, diffToEdgeY)
            if not minDist or diffToEdgeLength < minDist then
                minDist = diffToEdgeLength
                closestX = pointOnEdgeX
                closestY = pointOnEdgeY
            end
        end
    end
    if minDist then
        --Outside
        return closestX, closestY, false
    end
    
    --Inside
    return x, y, true
end
function ZO_Triangle:ContainsPoint(x, y)
    if(self.m_isMirrored) then
        y = 1 - y
    end
    return self:GetClosestPointOnTriangle(x, y)
end
function ZO_Triangle:GetTriangleParams(x, y)
    local vTop = self:GetPoint(3)
    local vLeft = self:GetPoint(1)
    local vRight = self:GetPoint(2)
    local userX = x - vTop.x
    local userY = y - vTop.y
    local rX = vRight.x - vTop.x
    local rY = vRight.y - vTop.y
    local lX = vLeft.x - vTop.x
    local lY = vLeft.y - vTop.y
    local b = (userY * rX - userX * rY) / (lY * rX - lX * rY)
    local a = (userX - b * lX) / rX
    if(self.m_isMirrored) then
        a, b = b, a
    end
    
    if(a < 0) then
        a = zo_abs(a)
        b = b + a
    elseif(b < 0) then
        b = zo_abs(b)
        a = a + b
    end
    return a, b
end
function ZO_Triangle:PointFromParams(a, b)
    if(self.m_isMirrored) then
        a, b = b, a
    end
    local vTop = self:GetPoint(3)
    local xTop, yTop = vTop.x, vTop.y
    local vLeft = self:GetPoint(1)
    local xLeft, yLeft = vLeft.x, vLeft.y
    local vRight = self:GetPoint(2)
    local xRight, yRight = vRight.x, vRight.y
    local x = xTop + (xRight - xTop) * a + (xLeft - xTop) * b
    local y = yTop + (yRight - yTop) * a + (yLeft - yTop) * b
    if(self.m_isMirrored) then
        y = 1 - y
    end
    return x, y        
end
--[[ ZO_TrianglePicker:
Wraps a single triangle object and a gui control so that a thumb button can be positioned within the triangle
--]]
ZO_TrianglePicker = ZO_Object:Subclass()
function ZO_TrianglePicker:New(...)
    local picker = ZO_Object.New(self)
    picker:Initialize(...)
    return picker
end
do
    local currentPickerId = 0
    function ZO_TrianglePicker:Initialize(width, height, parent, control)
        self.width = width or 128
        self.height = height or 128
        self.enabled = true
        if(control) then
            self.control = control
        else
            currentPickerId = currentPickerId + 1
            self.control = CreateControlFromVirtual("ZO_TrianglePickerControl", parent or GuiRoot, "ZO_TrianglePickerTemplate", currentPickerId)
        end
        self.control.owner = self
        self:UpdateTriangle()
    end
end
function ZO_TrianglePicker:UpdateTriangle()
    self.control:SetDimensions(self.width, self.height)
    local points = {
        { x = 0, y = self.height },
        { x = self.width, y = self.height },
        { x = self.width / 2, y = 0 },
    }
    if(self.triangle) then 
        self.triangle:SetPoints(points)
    else
        self.triangle = ZO_Triangle:New(points)
    end
end
function ZO_TrianglePicker:SetThumb(control)
    self.thumb = control
end
function ZO_TrianglePicker:SetEnabled(enabled)
    if(enabled ~= self.enabled) then
        self.enabled = enabled
        self:OnMouseUp()
        local thumb = self.thumb
        if(thumb) then
            if(enabled) then
                thumb:SetState(BSTATE_NORMAL, false)
            else
                thumb:SetState(BSTATE_DISABLED, true)
            end
        end
    end
end
function ZO_TrianglePicker:SetUpdateCallback(callback)
end
function ZO_TrianglePicker:GetControl()
    return self.control
end
local function SetThumbAnchor(thumb, anchorTo, x, y)
    if(thumb) then
        thumb:ClearAnchors()
        thumb:SetAnchor(CENTER, anchorTo, TOPLEFT, x, y)
    end
end
function ZO_TrianglePicker:GetThumbPosition() --In local control space
    return self.lastX, self.lastY
end
function ZO_TrianglePicker:SetThumbPosition(x, y) -- In local control space
    self.lastX, self.lastY = self.triangle:GetClosestPointOnTriangle(x, y)
    SetThumbAnchor(self.thumb, self.control, self.lastX, self.lastY)
end
local function GetControlSpaceCoordinates(control, x, y)
    local scale = control:GetScale()
    return (x - control:GetLeft()) / scale, (y - control:GetTop()) / scale
end
function ZO_TrianglePicker:OnUpdate()
    local closestPointX, closestPointY, isInside = self.triangle:GetClosestPointOnTriangle(x, y)
    self.isInside = isInside
    if(isInside or self.thumbMoving) then
        WINDOW_MANAGER:SetMouseCursor(MOUSE_CURSOR_UI_HAND)
    else
        WINDOW_MANAGER:SetMouseCursor(MOUSE_CURSOR_DO_NOT_CARE)
    end
    if(self.thumbMoving) then
        local lastX, lastY = self.lastX, self.lastY
        if(lastX ~= closestPointX or lastY ~= closestPointY) then
            SetThumbAnchor(self.thumb, self.control, closestPointX, closestPointY)
            if(self.updateCallback) then
                self.updateCallback(self, closestPointX, closestPointY)
            end
        end
    
        self.lastX, self.lastY = closestPointX, closestPointY
    end
end
function ZO_TrianglePicker:SetThumbMoving(moving)
    self.thumbMoving = moving
end
function ZO_TrianglePicker:SetUpdateHandlerEnabled(enableUpdates)
    if(enableUpdates) then
        if(not self.control:GetHandler("OnUpdate")) then
            self.onUpdateFunction = self.onUpdateFunction or function()
                self:OnUpdate()
            end
            self.control:SetHandler("OnUpdate", self.onUpdateFunction)
        end
    else
        self.control:SetHandler("OnUpdate", nil)
    end
end
function ZO_TrianglePicker:OnMouseDown()
    if(not self.enabled) then return end
    if(self.isInside) then    
        self:SetThumbMoving(true)
        self:SetUpdateHandlerEnabled(true)
        PlaySound(SOUNDS.DEFAULT_CLICK)
    end
end
function ZO_TrianglePicker:OnMouseUp()
    if(not self.enabled) then
        WINDOW_MANAGER:SetMouseCursor(MOUSE_CURSOR_DO_NOT_CARE)
        PlaySound(SOUNDS.DEFAULT_CLICK)
        return
    end
    self:SetThumbMoving(false)
    
    if(not self.isInside) then
        WINDOW_MANAGER:SetMouseCursor(MOUSE_CURSOR_DO_NOT_CARE)
    end
    PlaySound(SOUNDS.DEFAULT_CLICK)
    -- Ok, the mouse wasn't over the actual triangle, but we may need to keep running the
    -- update handler because it's still over the control...
end
function ZO_TrianglePicker:OnMouseEnter()
    if(not self.enabled) then return end
end
function ZO_TrianglePicker:OnMouseExit()
    if(not self.enabled) then return end
    if(not self.thumbMoving) then
        self:SetUpdateHandlerEnabled(false)
        WINDOW_MANAGER:SetMouseCursor(MOUSE_CURSOR_DO_NOT_CARE)
    end
end
function ZO_TrianglePicker:GetBarycentricCoordinates()
end
--[[ XML Handlers ]]--
    control.owner:OnMouseDown()
end
    control.owner:OnMouseUp()
end
    control.owner:OnMouseEnter()
end
    control.owner:OnMouseExit()
end