-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathwidget_stepper.lua
More file actions
486 lines (401 loc) · 15.5 KB
/
widget_stepper.lua
File metadata and controls
486 lines (401 loc) · 15.5 KB
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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
-- Copyright © 2013 Corona Labs Inc. All Rights Reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
-- * Neither the name of the company nor the names of its contributors
-- may be used to endorse or promote products derived from this software
-- without specific prior written permission.
-- * Redistributions in any form whatsoever must retain the following
-- acknowledgment visually in the program (e.g. the credits of the program):
-- 'This product includes software developed by Corona Labs Inc. (http://www.coronalabs.com).'
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL CORONA LABS INC. BE LIABLE FOR ANY
-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
local M =
{
_options = {},
_widgetName = "widget.newStepper",
}
-- Require needed widget files
local _widget = require( "widget" )
local isGraphicsV1 = ( 1 == display.getDefault( "graphicsCompatibility" ) )
-- Localize math functions
local mHuge = math.huge
-- Creates a new stepper from a sprite
local function initWithSprite( stepper, options )
-- Create a local reference to our options table
local opt = options
-- Animation options
local sheetOptions =
{
{
name = "default",
start = opt.defaultFrame,
count = 1,
time = 1,
},
{
name = "noMinus",
start = opt.noMinusFrame,
count = 1,
time = 1,
},
{
name = "noPlus",
start = opt.noPlusFrame,
count = 1,
time = 1,
},
{
name = "minusActive",
start = opt.minusActiveFrame,
count = 1,
time = 1,
},
{
name = "plusActive",
start = opt.plusActiveFrame,
count = 1,
time = 1,
},
}
-- Forward references
local imageSheet, view, decrementOverlay, incrementOverlay
-- Create the imageSheet
if opt.sheet then
imageSheet = opt.sheet
else
local themeData = require( opt.themeData )
imageSheet = graphics.newImageSheet( opt.themeSheetFile, themeData:getSheet() )
end
-- Create the view
view = display.newSprite( stepper, imageSheet, sheetOptions )
view:setSequence( "default" )
view.x = stepper.x + ( view.contentWidth * 0.5 )
view.y = stepper.y + ( view.contentHeight * 0.5 )
-- Create the decrement overlay rectangle
decrementOverlay = display.newRect( stepper, 0, 0, ( view.contentWidth * 0.5 ) - 1, view.contentHeight )
decrementOverlay.x = view.x - ( decrementOverlay.contentWidth * 0.5 ) - 1
decrementOverlay.y = view.y
decrementOverlay.isVisible = false
decrementOverlay.isHitTestable = true
-- Create the increment overlay rectangle
incrementOverlay = display.newRect( stepper, 0, 0, ( view.contentWidth * 0.5 ) , view.contentHeight )
incrementOverlay.x = view.x + ( incrementOverlay.contentWidth * 0.5 )
incrementOverlay.y = view.y
incrementOverlay.isVisible = false
incrementOverlay.isHitTestable = true
-------------------------------------------------------
-- Assign properties to the view
-------------------------------------------------------
-- Assign to the view
view._isEnabled = opt.isEnabled
view._timerIncrementSpeed = opt.timerIncrementSpeed or 1000
view._changeIncrementSpeedAtTime = view._timerIncrementSpeed
view._increments = 0
view._changeSpeedAtIncrement = opt.changeSpeedAtIncrement or 5
view._timer = nil
view._minimumValue = opt.minimumValue
view._maximumValue = opt.maximumValue
view._currentValue = opt.initialValue
view._event = {} -- Our event table for the view
view._previousX = 0
view._onPress = opt.onPress
-- Assign objects to the view
view._decrementOverlay = decrementOverlay
view._incrementOverlay = incrementOverlay
-- If the startNumber is equal to/greater than the minimum or maxium values, set the steppers image sequence to reflect it
if view._currentValue <= view._minimumValue then
view:setSequence( "noMinus" )
elseif view._currentValue >= view._maximumValue then
view._currentValue = view._maximumValue
view:setSequence( "noPlus" )
end
-------------------------------------------------------
-- Assign properties/objects to the stepper
-------------------------------------------------------
-- Assign objects to the stepper
stepper._imageSheet = imageSheet
stepper._view = view
--
----------------------------------------------------------
-- PUBLIC METHODS
----------------------------------------------------------
-- Function to set the stepper's value programatically
function stepper:setValue( newValue )
return self._view:_setValue( newValue )
end
-- Function to set a button as active
function stepper:setEnabled( isEnabled )
self._view._isEnabled = isEnabled
end
----------------------------------------------------------
-- PRIVATE METHODS
----------------------------------------------------------
-- Handle touch events on the stepper
function view:touch( event )
local phase = event.phase
local _stepper = self.parent
event.target = _stepper
-- If the button isn't active, just return
if not view._isEnabled then
return
end
-- The content bounds of our increment/decrement segments
local decrementBounds = self._decrementOverlay.contentBounds
local incrementBounds = self._incrementOverlay.contentBounds
if "began" == phase then
-- Set focus on the stepper (if focus isn't already on it)
if not self._isFocus then
display.getCurrentStage():setFocus( self, event.id )
self._isFocus = true
end
-- If we have pressed the right side of the stepper (the plus)
if event.x >= incrementBounds.xMin and event.x <= incrementBounds.xMax then
self:_dispatchIncrement()
elseif event.x >= decrementBounds.xMin and event.x <= decrementBounds.xMax then
-- We have pressed the left side of the stepper (the minus)
self:_dispatchDecrement()
end
-- Set the previous x position of the event
self._previousX = event.x
-- Manage the steppers pressed state ( & animation )
self:_manageStepperPressState()
-- Exectute the onPress method if there is one
if self._onPress then
self._onPress( self._event )
end
elseif self._isFocus then
if "moved" == phase then
-- Handle switching from one side of the stepper to the other whilst still holding your finger on the screen
if event.x >= incrementBounds.xMin and event.x <= incrementBounds.xMax then
if self._event.phase ~= "increment" then
self:dispatchEvent( { name = "touch", phase = "began", x = event.x } )
end
elseif event.x >= decrementBounds.xMin and event.x <= decrementBounds.xMax then
-- Dispatch a touch event to self
if self._event.phase ~= "decrement" then
self:dispatchEvent( { name = "touch", phase = "began", x = event.x } )
end
end
elseif "ended" == phase or "cancelled" == phase then
-- Manage the steppers released state
view:_manageStepperReleaseState()
-- Remove focus from stepper
display.getCurrentStage():setFocus( self, nil )
self._isFocus = false
end
end
return true
end
view:addEventListener( "touch" )
-- Function to dispatch a increment event for the stepper
function view:_dispatchIncrement()
local newPhase = nil
-- If the currentNumber is less then the maxiumum value then set the phase to "increment"
if self._currentValue < self._maximumValue then
newPhase = "increment"
self._currentValue = self._currentValue + 1
else
-- The currentNumber is more then the maxiumum value, set the phase to "maxLimit"
newPhase = "maxLimit"
end
-- Set up the event to dispatch
local eventToDispatch =
{
phase = newPhase,
target = stepper,
value = self._currentValue,
minimumValue = self._minimumValue,
maximumValue = self._maximumValue,
}
-- Pass the event
self._event = eventToDispatch
end
-- Function to dispatch a decrement event for the stepper
function view:_dispatchDecrement()
local newPhase = nil
-- If the currentNumber is more then the minimum value then set the phase to "decrement"
if self._currentValue > self._minimumValue then
newPhase = "decrement"
self._currentValue = self._currentValue - 1
else
-- The currentNumber is less then the minimum value, set the phase to "minLimit"
newPhase = "minLimit"
end
-- Set up the event to dispatch
local eventToDispatch =
{
phase = newPhase,
target = stepper,
value = self._currentValue,
minimumValue = self._minimumValue,
maximumValue = self._maximumValue,
}
-- Pass the event
self._event = eventToDispatch
end
-- Function to manage the steppers pressed/held touch state
function view:_manageStepperPressState()
local phase = self._event.phase
-- Start the steppers timer
if not self._timer then
self._timer = timer.performWithDelay( self._changeIncrementSpeedAtTime, self, 0 )
end
-- Set the steppers sequence according to the phase
if "increment" == phase then
self:setSequence( "plusActive" )
elseif "decrement" == phase then
self:setSequence( "minusActive" )
elseif "maxLimit" == phase then
self:setSequence( "noPlus" )
elseif "minLimit" == phase then
self:setSequence( "noMinus" )
end
end
-- Function to manage the stepper's released touch state (released ie not being touched)
function view:_manageStepperReleaseState()
-- Set the steppers default sequence
if self._currentValue > self._minimumValue and self._currentValue < self._maximumValue then
self:setSequence( "default" )
end
-- Change the steppers sequence according to if it reaches it's max or min range
if self._currentValue >= self._maximumValue then
self:setSequence( "noPlus" )
elseif self._currentValue <= self._minimumValue then
self:setSequence( "noMinus" )
end
-- Cancel the timer and reset the changeTime
if self._timer then
self:_cancelTimer()
self._changeIncrementSpeedAtTime = self._timerIncrementSpeed
end
end
-- Function to increment/decrement the steppers values while touch on the stepper is held/active - Speed of the increment/decrement ramps up linearly
function view:timer()
-- Increase the increments
self._increments = self._increments + 1
-- If the current increment is more or equal to the requested change value
if self._increments >= self._changeSpeedAtIncrement then
-- Cancel any active timer
self:_cancelTimer()
-- Half the Increment speed
self._changeIncrementSpeedAtTime = self._changeIncrementSpeedAtTime * 0.5
-- Re-start the timer at the new incremental value
if not self._timer then
self._timer = timer.performWithDelay( self._changeIncrementSpeedAtTime, self, 0 )
end
-- Reset the increments back to 0
self._increments = 0
end
-- Dispatch a touch event to self
self:dispatchEvent( { name = "touch", phase = "began", x = self._previousX } )
end
-- Function to cancel the stepper's timer
function view:_cancelTimer()
if self._timer then
timer.cancel( self._timer )
self._timer = nil
end
end
-- Function to set the stepper's value programatically
function view:_setValue( newValue )
local value = newValue or self._currentValue
self._currentValue = newValue
end
-- Finalize function
function stepper:_finalize()
-- Nil out the stepper's event table
self._view._event = nil
-- Cancel the timer
self._view:_cancelTimer()
-- Set the ImageSheet to nil
self._imageSheet = nil
end
return self
end
-- Function to create a new Stepper object ( widget.newStepper)
function M.new( options, theme )
local customOptions = options or {}
local themeOptions = theme or {}
-- Create a local reference to our options table
local opt = M._options
-- Check if the requirements for creating a widget has been met (throws an error if not)
_widget._checkRequirements( customOptions, themeOptions, M._widgetName )
-------------------------------------------------------
-- Properties
-------------------------------------------------------
-- Positioning & properties
opt.left = customOptions.left or 0
opt.top = customOptions.top or 0
opt.x = customOptions.x or nil
opt.y = customOptions.y or nil
if customOptions.x and customOptions.y then
opt.left = 0
opt.top = 0
end
opt.width = customOptions.width or themeOptions.width or error( "ERROR: " .. M._widgetName .. ": width expected, got nil", 3 )
opt.height = customOptions.height or themeOptions.height or error( "ERROR: " .. M._widgetName .. ": height expected, got nil", 3 )
opt.id = customOptions.id
opt.baseDir = customOptions.baseDir or system.ResourceDirectory
opt.initialValue = customOptions.initialValue or 0
opt.minimumValue = customOptions.minimumValue or 0
opt.maximumValue = customOptions.maximumValue or mHuge
opt.onPress = customOptions.onPress
opt.onHold = customOptions.onHold
opt.timerIncrementSpeed = customOptions.timerIncrementSpeed or 1000
opt.changeSpeedAtIncrement = customOptions.changeSpeedAtIncrement or 5
opt.isEnabled = customOptions.isEnabled
-- If the user didn't pass in a isEnabled flag, set it to true
if nil == opt.isEnabled then
opt.isEnabled = true
end
-- Frames & Images
opt.sheet = customOptions.sheet
opt.themeSheetFile = themeOptions.sheet
opt.themeData = themeOptions.data
opt.defaultFrame = customOptions.defaultFrame or _widget._getFrameIndex( themeOptions, themeOptions.defaultFrame )
opt.noMinusFrame = customOptions.noMinusFrame or _widget._getFrameIndex( themeOptions, themeOptions.noMinusFrame )
opt.noPlusFrame = customOptions.noPlusFrame or _widget._getFrameIndex( themeOptions, themeOptions.noPlusFrame )
opt.minusActiveFrame = customOptions.minusActiveFrame or _widget._getFrameIndex( themeOptions, themeOptions.minusActiveFrame )
opt.plusActiveFrame = customOptions.plusActiveFrame or _widget._getFrameIndex( themeOptions, themeOptions.plusActiveFrame )
-------------------------------------------------------
-- Create the Stepper
-------------------------------------------------------
-- Create the stepper object
local stepper = _widget._new
{
left = opt.left,
top = opt.top,
id = opt.id or "widget_stepper",
baseDir = opt.baseDir,
}
-- Create the stepper
initWithSprite( stepper, opt )
-- Set the stepper's position ( set the reference point to center, just to be sure )
if ( isGraphicsV1 ) then
stepper:setReferencePoint( display.CenterReferencePoint )
end
local x, y = opt.x, opt.y
if not opt.x or not opt.y then
x = opt.left + stepper.contentWidth * 0.5
y = opt.top + stepper.contentHeight * 0.5
end
stepper.x, stepper.y = x, y
return stepper
end
return M