├── .gitignore
├── examples
├── case.png
├── dial.png
├── hour.png
├── min.png
├── sec.png
├── crown_0.png
├── date_disk.png
├── fallback.png
├── stop_hour.png
├── stop_min.png
├── stop_sec.png
├── pusher_a_0.png
├── pusher_a_1.png
├── pusher_b_0.png
├── pusher_b_1.png
├── pusher_c_0.png
├── pusher_c_1.png
└── watch.html
├── .gitmodules
├── Cakefile
└── src
└── jquery.floatinghands.coffee
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | lib/jquery.floatinghands.js
3 | docs/
4 |
--------------------------------------------------------------------------------
/examples/case.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/case.png
--------------------------------------------------------------------------------
/examples/dial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/dial.png
--------------------------------------------------------------------------------
/examples/hour.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/hour.png
--------------------------------------------------------------------------------
/examples/min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/min.png
--------------------------------------------------------------------------------
/examples/sec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/sec.png
--------------------------------------------------------------------------------
/examples/crown_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/crown_0.png
--------------------------------------------------------------------------------
/examples/date_disk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/date_disk.png
--------------------------------------------------------------------------------
/examples/fallback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/fallback.png
--------------------------------------------------------------------------------
/examples/stop_hour.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/stop_hour.png
--------------------------------------------------------------------------------
/examples/stop_min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/stop_min.png
--------------------------------------------------------------------------------
/examples/stop_sec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/stop_sec.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/EaselJS"]
2 | path = lib/EaselJS
3 | url = git://github.com/gskinner/EaselJS.git
4 |
--------------------------------------------------------------------------------
/examples/pusher_a_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/pusher_a_0.png
--------------------------------------------------------------------------------
/examples/pusher_a_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/pusher_a_1.png
--------------------------------------------------------------------------------
/examples/pusher_b_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/pusher_b_0.png
--------------------------------------------------------------------------------
/examples/pusher_b_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/pusher_b_1.png
--------------------------------------------------------------------------------
/examples/pusher_c_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/pusher_c_0.png
--------------------------------------------------------------------------------
/examples/pusher_c_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leonidas-from-XIV/floatinghands/master/examples/pusher_c_1.png
--------------------------------------------------------------------------------
/Cakefile:
--------------------------------------------------------------------------------
1 | {spawn, exec} = require 'child_process'
2 |
3 | task 'build', 'continually build the JavaScript code', ->
4 | coffee = spawn 'coffee', ['-cwb', '-o', 'lib', 'src']
5 | coffee.stdout.on 'data', (data) -> console.log data.toString().trim()
6 |
7 | task 'doc', 'rebuild the Docco documentation', ->
8 | exec([
9 | 'docco src/jquery.floatinghands.coffee'
10 | ].join(' && '), (err) ->
11 | throw err if err
12 | )
13 |
--------------------------------------------------------------------------------
/examples/watch.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example
6 |
7 |
10 |
11 |
12 |
13 |
153 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/src/jquery.floatinghands.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | *jQuery Floating Hands Plugin*
3 |
4 | Written in 2011 by Marek Kubica .
5 |
6 | See https://github.com/Leonidas-from-XIV/floatinghands for details.
7 | ###
8 |
9 | # define the wrapper function for jQuery
10 | (($) ->
11 | # function that gets called to update the bitmap every n milliseconds
12 | # yes, this is a curried function returning a unary function
13 | onUpdate = (bitmap) -> ->
14 | # calculate the degree by passing the function to the degree calculator
15 | # function and set this degree
16 | bitmap.rotation = bitmap.updateFn bitmap.timeFn
17 | # return `true` so it will be called again
18 | true
19 |
20 | # comparison function that sorts elements based on their z attribute in
21 | # descending order
22 | sortByZ = (first, second) ->
23 | second.z - first.z
24 |
25 | ###
26 | a function that provides the number of milliseconds passed since
27 | midnight today (rather than 1970/01/01 midnight). For that, we subtract
28 | create a date that is today 0:00 and subtract it from the current date.
29 | ###
30 | now = ->
31 | atm = new Date()
32 | atm.getTime() - new Date(atm.getFullYear(), atm.getMonth(), atm.getDay()).getTime()
33 |
34 | layerLoaded = (stage, layer) -> (event) ->
35 | image = event.target
36 | bitmap = new Bitmap image
37 | delete layer.image
38 |
39 | if !layer.z?
40 | # Z is default to 10, so it stays in the back
41 | layer.z = 10
42 |
43 | # apply all options that were passed
44 | bitmap[key] = value for own key, value of layer
45 |
46 | if bitmap.updateFn?
47 | # if not specified differently, use `now` as time source
48 | if !bitmap.timeFn?
49 | bitmap.timeFn = now
50 | # get the specialized updater function
51 | updaterFn = onUpdate bitmap
52 | # call it once, so we start with the newest coordinates
53 | updaterFn()
54 | # add the bitmap to the Ticker so it recalculates every tick
55 | Ticker.addListener tick: updaterFn
56 |
57 | # deactivate mouse events on layers, because they are not clickable
58 | bitmap.mouseEnabled = false
59 | # put the object on the stage, thus making it visible
60 | stage.addChild bitmap
61 | # sort the list of children by the Z index, otherwise the order
62 | # will be determined by the time the image is loaded
63 | stage.sortChildren sortByZ
64 |
65 | pusherLoaded = (stage, layer, extra) -> (event) ->
66 | image = event.target
67 | bitmap = new Bitmap image
68 | delete layer.image
69 |
70 | if !layer.z?
71 | # Z is default to 10, so it stays in the back
72 | layer.z = 10
73 |
74 | bitmap[key] = value for own key, value of layer
75 |
76 | if extra.button?
77 | extra.button.data(extra.type, bitmap)
78 |
79 | stage.addChild bitmap
80 | stage.sortChildren sortByZ
81 |
82 | initialize = (stage, onLoad) -> (element) ->
83 | image = new Image
84 | image.src = element.image
85 | image.onload = onLoad stage, element
86 |
87 | initButton = (stage) -> (element) ->
88 | callback = element.pushed
89 | [x1, y1, x2, y2] = element.hotspot
90 | button = $ ''
91 | button.mousedown (event) ->
92 | button.data('normal').visible = false
93 | stage.update()
94 | callback event
95 | button.mouseup (event) ->
96 | button.data('normal').visible = true
97 | stage.update()
98 |
99 | button.css
100 | display: 'block'
101 | position: 'absolute'
102 | left: x1
103 | top: y1
104 | width: x2 - x1
105 | height: y2 - y1
106 | border: 'none'
107 | outline: 'none'
108 | cursor: 'pointer'
109 | background: 'rgba(0, 0, 0, 0)'
110 | $(stage.canvas).after button
111 |
112 | if element.normal?
113 | image = new Image
114 | image.src = element.normal.image
115 | image.onload = pusherLoaded stage, element.normal, button: button, type: 'normal'
116 | if element.pressed?
117 | image = new Image
118 | image.src = element.pressed.image
119 | image.onload = pusherLoaded stage, element.pressed, button: button, type: 'pressed'
120 |
121 | updateElements = ->
122 | stage = this
123 | for element in stage.children
124 | if element.updateFn?
125 | onUpdate(element)()
126 |
127 | class LocalStorage
128 | getLS: (key) =>
129 | localStorage.getItem key
130 |
131 | getCookie: (key) =>
132 | document.cookie
133 |
134 | get: =>
135 | if Modernizr.localstorage
136 | @getLS this, arguments
137 | else
138 | @getCookie this, arguments
139 |
140 | setLS: (key, value) =>
141 | localStorage.setItem key, value
142 |
143 | setCookie: (key, value) =>
144 | date = new Date();
145 | date.setTime date.getTime() + 356 * 24 * 60 * 60 * 1000
146 | expires = "; expires=" + date.toGMTString()
147 | document.cookie = key + "=" + value + expires + "; path=/"
148 |
149 | set: =>
150 | if Modernizr.localStorage
151 | @setLS this, arguments
152 | else
153 | @setCookie this, arguments
154 |
155 | class Stopwatch
156 | constructor: (slot) ->
157 | @slot = slot
158 | @running = false
159 | @frozen = false
160 | @frozenAt = 0
161 | @zero = new Date()
162 | @difference = 0
163 | @offset = 0
164 | @storage = new LocalStorage()
165 | @loadState()
166 |
167 | loadState: =>
168 | # TODO local storage
169 | val = @storage.get @slot
170 | console.log val
171 | @saveState()
172 |
173 | saveState: =>
174 | # TODO convert to JSON and save
175 | val = JSON.stringify
176 | running: @running
177 | frozen: @frozen
178 | frozenAt: @frozenAt
179 | zero: @zero
180 | difference: @difference
181 | offset: @offset
182 |
183 | console.log val
184 | console.log @storage.set
185 | @storage.set @slot, val
186 | console.log jQuery.parseJSON val
187 |
188 | toggleFreeze: =>
189 | if @running
190 | # save the current value as snapshot for later use
191 | @frozenAt = @offset + @difference
192 | @frozen = !@frozen
193 | else
194 | # reset all counters
195 | @frozenAt = @difference = @offset = 0
196 | @frozen = false
197 |
198 | toggleRun: =>
199 | if !@running
200 | #console.log "stopwatch got started"
201 | # started = new zero point
202 | @zero = new Date()
203 | else
204 | #console.log "stopwatch got halted"
205 | # add the difference from zero to now to the offset and reset
206 | # the difference
207 | @offset = @offset + @difference
208 | @difference = 0
209 |
210 | @running = !@running
211 |
212 | timeFn: =>
213 | #console.log "difference", @difference, "offset", @offset
214 | # if we are not running, we don't need to calculate differences
215 | if !@running
216 | return @offset + @difference
217 |
218 | # calculate the new difference
219 | current = new Date()
220 | @difference = current.getTime() - @zero.getTime()
221 |
222 | # if we're frozen, return the old value
223 | if @frozen
224 | return @frozenAt
225 |
226 | # otherwise we return the new value
227 | @offset + @difference
228 |
229 | # define the plugin callback for jQuery
230 | jQuery.fn.floatinghands = ->
231 | attach: => attach.apply this, arguments
232 | Stopwatch: Stopwatch
233 | makeTimeFn: => makeTimeFn.apply this, arguments
234 |
235 | makeTimeFn = (args) ->
236 | ###
237 | this is a function that can return a timeFunction based on certain
238 | arguments passed.
239 | 'total' means the number of milliseconds that is represented by a full
240 | rotation of a hand.
241 | 'step' means how often a hand is supposed to be updated.
242 | ###
243 | (timeFn) ->
244 | Math.floor(timeFn() / args.step) * args.step % args.total / args.total * 360
245 |
246 | attach = (layers, pusher) ->
247 | # bail out early if the browser does not support canvas
248 | if !Modernizr.canvas
249 | return this
250 |
251 | # get the and harvest it for width and height and replace by canvas
252 | candidate = $ this[0]
253 | # we set the attribute on the element and not using jquery, because
254 | # it needs to be an attribute and not CSS
255 | canvas = $('').attr width: candidate.width(), height: candidate.height()
256 | div = $ ''
257 | (div.attr id: originalId) if originalId = candidate.attr 'id'
258 | div.append canvas
259 | candidate.replaceWith div
260 | widget = canvas[0]
261 |
262 | # if Explorer Canvas was loaded, run it on our newly created element
263 | G_vmlCanvasManager?.initElement widget
264 |
265 | # we got a canvas, start initialization
266 | stage = new Stage widget
267 | # extend the stage with helpers
268 | stage.updateElements = updateElements
269 |
270 | # add all images to the stage
271 | initButton(stage) element for element in pusher
272 | initLayer = initialize stage, layerLoaded
273 | initLayer element for element in layers
274 |
275 | # adjust ticks / FPS at will
276 | Ticker.setInterval 125
277 | Ticker.addListener tick: -> stage.update()
278 |
279 | # return 'this' so the plugin call can be chained
280 | this
281 | )(jQuery)
282 |
--------------------------------------------------------------------------------