Class: Zif::Actions::Action
- Inherits:
-
Object
- Object
- Zif::Actions::Action
- Includes:
- Serializable
- Defined in:
- lib/zif/actions/action.rb
Overview
Inspired by developer.apple.com/documentation/spritekit/skaction
and Squirrel Eiserloh’s GDC talk on nonlinear transformations www.youtube.com/watch?v=mr5xkf6zSzk
A transition of a set of attributes over time using an easing function (aka tweening, easing) Meant to be applied to an object using the Actionable mixin
Constant Summary collapse
- REPEAT_NAMES =
A list of convenient names for repeat counts
{ once: 1, twice: 2, thrice: 3, forever: Float::INFINITY, always: Float::INFINITY }.freeze
- EASING_FUNCS =
%i[ immediate linear flip smooth_start smooth_start3 smooth_start4 smooth_start5 smooth_stop smooth_stop3 smooth_stop4 smooth_stop5 smooth_step smooth_step3 smooth_step4 smooth_step5 ].freeze
- ROUNDING_FUNCS =
%i[ceil floor round none].freeze
Instance Attribute Summary collapse
-
#callback ⇒ Proc
A callback to run at the end of the action.
-
#dirty ⇒ Boolean
readonly
True if this action caused a change on the node during this tick.
-
#duration ⇒ Integer
The number of ticks it will take to reach the finish condition.
-
#easing ⇒ Symbol
Method name of the easing function to apply over the duration, (see EASING_FUNCS).
-
#finish ⇒ Hash<Symbol, Object>
Key-value pair of attributes being acted upon, and their final state.
-
#finish_early ⇒ Boolean
Set this to
true
to override the normal duration and complete this iteration on next tick. -
#follow ⇒ Object
An object being followed.
-
#repeat ⇒ Numeric
The number of times this will repeat.
-
#rounding ⇒ Symbol
The rounding strategy for values being adjusted during the action (see ROUNDING_FUNCS).
-
#start ⇒ Hash
The start conditions for the keys referenced in #finish.
-
#started_at ⇒ Integer
Set to $gtk.args.tick_count - 1 when created / started.
1. Public Interface collapse
-
#complete? ⇒ Boolean
True if there are no more #repeats left on this action.
-
#finish_early! ⇒ Object
Forces the easing to finish on the next #perform_tick, ignoring duration.
-
#initialize(node, finish, follow: nil, duration: 1.seconds, easing: :linear, rounding: :round, repeat: 1, &block) ⇒ Action
constructor
rubocop:disable Metrics/PerceivedComplexity rubocop:disable Layout/LineLength.
-
#iteration_complete? ⇒ Boolean
True if #progress is 1.0.
-
#progress ⇒ Float
0.0 -> 1.0 Percentage of duration passed.
-
#reset_duration ⇒ Object
Resets #started_at to the current tick.
-
#reset_start ⇒ Object
Recalculates the start conditions for the action based on node state.
2. Private-ish methods collapse
-
#ease(start_val, finish_val) ⇒ Numeric
private
Returns a value between
start_val
andfinish_val
based on function specified by #easing. -
#perform_callback ⇒ Object
private
Calls #callback with self.
-
#perform_tick ⇒ Boolean
private
Performs one tick’s worth of easing on all attributes specified by #finish conditions.
3. Easing Functions collapse
- #crossfade(a = :linear, b = :linear, x = progress) ⇒ Object
- #flip(x = progress) ⇒ Object
- #immediate(_x = nil) ⇒ Object
- #linear(x = progress) ⇒ Object
- #mix(a = :linear, b = :linear, rate = 0.5, x = progress) ⇒ Object
- #smooth_start(x = progress) ⇒ Object
- #smooth_start3(x = progress) ⇒ Object
- #smooth_start4(x = progress) ⇒ Object
- #smooth_start5(x = progress) ⇒ Object
- #smooth_step(x = progress) ⇒ Object
- #smooth_step3(x = progress) ⇒ Object
- #smooth_step4(x = progress) ⇒ Object
- #smooth_step5(x = progress) ⇒ Object
- #smooth_stop(x = progress) ⇒ Object
- #smooth_stop3(x = progress) ⇒ Object
- #smooth_stop4(x = progress) ⇒ Object
- #smooth_stop5(x = progress) ⇒ Object
Methods included from Serializable
#exclude_from_serialize, #inspect, #serialize, #to_s
Constructor Details
#initialize(node, finish, follow: nil, duration: 1.seconds, easing: :linear, rounding: :round, repeat: 1, &block) ⇒ Action
Important! Each key in the finish
hash must map to an accessible attribute on the node
.
(:key
getter and :key=
setter, as you get with an attr_accessor
, but could also be defined manually. See Layers::Camera#pos_x= for an example of a manually defined Actionable attribute)
rubocop:disable Metrics/PerceivedComplexity rubocop:disable Layout/LineLength
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 |
# File 'lib/zif/actions/action.rb', line 133 def initialize( node, finish, follow: nil, duration: 1.seconds, easing: :linear, rounding: :round, repeat: 1, &block ) unless node.is_a? Zif::Actions::Actionable raise ArgumentError, "Invalid node: #{node}, expected a Zif::Actions::Actionable" end @node = node @follow = follow unless EASING_FUNCS.include? easing raise ArgumentError, "Invalid easing function: '#{easing}'. Must be in #{EASING_FUNCS}" end @easing = easing unless ROUNDING_FUNCS.include? rounding raise ArgumentError, "Invalid rounding function: '#{rounding}'. Must be in #{ROUNDING_FUNCS}" end @rounding = rounding @start = {} finish.each_key do |key| [key, "#{key}="].each do |req_meth| unless @node.respond_to?(req_meth) raise ArgumentError, "Invalid finish condition: #{@node} doesn't have a method named '##{req_meth}'" end end end if @follow finish.each do |key, val| unless val.is_a? Symbol raise ArgumentError, "You provided an object to follow. A Symbol was expected instead of '#{val}' (#{val.class}) for the key-value pair (#{key}: #{val}) in the finish condition. Action needs this symbol to be the name of a method on the followed object (#{@follow.class})" end unless @follow.respond_to?(val) raise ArgumentError, "You provided an object to follow, but it doesn't respond to '##{val}' (for finish '#{key}')" end end end @finish = finish @finish_early = false reset_start @repeat = REPEAT_NAMES[repeat] || repeat @duration = [duration.to_i, 1].max # in ticks @callback = block if block_given? # puts "Action: #{@start} -> #{@finish} in #{@duration} using #{@easing}. Block present? #{block_given?}" reset_duration end |
Instance Attribute Details
#callback ⇒ Proc
Returns A callback to run at the end of the action.
25 26 27 |
# File 'lib/zif/actions/action.rb', line 25 def callback @callback end |
#dirty ⇒ Boolean (readonly)
Returns True if this action caused a change on the node during this tick.
40 41 42 |
# File 'lib/zif/actions/action.rb', line 40 def dirty @dirty end |
#duration ⇒ Integer
Returns The number of ticks it will take to reach the finish condition.
34 35 36 |
# File 'lib/zif/actions/action.rb', line 34 def duration @duration end |
#easing ⇒ Symbol
Returns Method name of the easing function to apply over the duration, (see EASING_FUNCS).
28 29 30 |
# File 'lib/zif/actions/action.rb', line 28 def easing @easing end |
#finish ⇒ Hash<Symbol, Object>
Returns Key-value pair of attributes being acted upon, and their final state. The Symbol
keys must represent a getter and setter on the node. These can be traditional attributes defined using attr_accessor
, or manually defined e.g. def x=(new_x) ..
19 20 21 |
# File 'lib/zif/actions/action.rb', line 19 def finish @finish end |
#finish_early ⇒ Boolean
Returns Set this to true
to override the normal duration and complete this iteration on next tick.
46 47 48 |
# File 'lib/zif/actions/action.rb', line 46 def finish_early @finish_early end |
#follow ⇒ Object
Returns An object being followed. Reset #finish based on this object’s values each tick.
22 23 24 |
# File 'lib/zif/actions/action.rb', line 22 def follow @follow end |
#repeat ⇒ Numeric
Returns The number of times this will repeat.
31 32 33 |
# File 'lib/zif/actions/action.rb', line 31 def repeat @repeat end |
#rounding ⇒ Symbol
Returns The rounding strategy for values being adjusted during the action (see ROUNDING_FUNCS).
43 44 45 |
# File 'lib/zif/actions/action.rb', line 43 def rounding @rounding end |
#start ⇒ Hash
Returns The start conditions for the keys referenced in #finish.
13 14 15 |
# File 'lib/zif/actions/action.rb', line 13 def start @start end |
#started_at ⇒ Integer
Returns Set to $gtk.args.tick_count - 1 when created / started.
37 38 39 |
# File 'lib/zif/actions/action.rb', line 37 def started_at @started_at end |
Instance Method Details
#complete? ⇒ Boolean
Returns True if there are no more #repeats left on this action.
226 227 228 229 |
# File 'lib/zif/actions/action.rb', line 226 def complete? # puts "Action#complete?: Action complete! #{self.inspect} #{@node.class}" if @repeat.zero? @repeat.zero? end |
#crossfade(a = :linear, b = :linear, x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
309 310 311 |
# File 'lib/zif/actions/action.rb', line 309 def crossfade(a=:linear, b=:linear, x=progress) mix(a, b, x, x) end |
#ease(start_val, finish_val) ⇒ Numeric
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns a value between start_val
and finish_val
based on function specified by #easing
273 274 275 |
# File 'lib/zif/actions/action.rb', line 273 def ease(start_val, finish_val) ((finish_val - start_val) * send(@easing)) + start_val end |
#finish_early! ⇒ Object
Forces the easing to finish on the next #perform_tick, ignoring duration
211 212 213 |
# File 'lib/zif/actions/action.rb', line 211 def finish_early! @finish_early = true end |
#flip(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
299 300 301 |
# File 'lib/zif/actions/action.rb', line 299 def flip(x=progress) 1 - x end |
#immediate(_x = nil) ⇒ Object
Meant to be called indirectly via setting #easing
289 290 291 |
# File 'lib/zif/actions/action.rb', line 289 def immediate(_x=nil) 1.0 end |
#iteration_complete? ⇒ Boolean
Returns True if #progress is 1.0.
221 222 223 |
# File 'lib/zif/actions/action.rb', line 221 def iteration_complete? progress >= 1.0 end |
#linear(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
294 295 296 |
# File 'lib/zif/actions/action.rb', line 294 def linear(x=progress) x end |
#mix(a = :linear, b = :linear, rate = 0.5, x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
304 305 306 |
# File 'lib/zif/actions/action.rb', line 304 def mix(a=:linear, b=:linear, rate=0.5, x=progress) (1 - rate) * send(a, x) + rate * send(b, x) end |
#perform_callback ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Calls #callback with self
279 280 281 282 |
# File 'lib/zif/actions/action.rb', line 279 def perform_callback # puts "Action#perform_callback: Callback triggered" @callback.call(self) end |
#perform_tick ⇒ Boolean
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 |
# File 'lib/zif/actions/action.rb', line 238 def perform_tick @dirty = false @finish.each do |key, val| target = @follow ? @follow.send(val) : val start = @node.send(key) # puts " easing #{key} #{start} -> #{val}" if start.is_a? Numeric change_to = ease(@start[key], target) change_to = change_to.send(@rounding) unless @rounding == :none else change_to = target end @dirty = true if start != change_to # puts " assigning #{key}= #{change_to}" @node.send("#{key}=", change_to) end # puts "iteration_complete? : #{iteration_complete?}, duration: #{@duration}, repeat: #{@repeat}" if iteration_complete? @finish_early = false @repeat -= 1 reset_duration end perform_callback if @callback && complete? @dirty end |
#progress ⇒ Float
Returns 0.0 -> 1.0 Percentage of duration passed.
216 217 218 |
# File 'lib/zif/actions/action.rb', line 216 def progress @finish_early ? 1.0 : ($gtk.args.tick_count - @started_at).fdiv(@duration) end |
#reset_duration ⇒ Object
Resets #started_at to the current tick
206 207 208 |
# File 'lib/zif/actions/action.rb', line 206 def reset_duration @started_at = $gtk.args.tick_count - 1 end |
#reset_start ⇒ Object
Recalculates the start conditions for the action based on node state. Easing is calculated as difference between start and finish conditions over time.
199 200 201 202 203 |
# File 'lib/zif/actions/action.rb', line 199 def reset_start @finish.each_key do |key| @start[key] = @node.send(key) end end |
#smooth_start(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
314 315 316 |
# File 'lib/zif/actions/action.rb', line 314 def smooth_start(x=progress) x * x end |
#smooth_start3(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
319 320 321 |
# File 'lib/zif/actions/action.rb', line 319 def smooth_start3(x=progress) x * x * x end |
#smooth_start4(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
324 325 326 |
# File 'lib/zif/actions/action.rb', line 324 def smooth_start4(x=progress) x * x * x * x * x end |
#smooth_start5(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
329 330 331 |
# File 'lib/zif/actions/action.rb', line 329 def smooth_start5(x=progress) x * x * x * x * x * x end |
#smooth_step(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
354 355 356 |
# File 'lib/zif/actions/action.rb', line 354 def smooth_step(x=progress) crossfade(:smooth_start, :smooth_stop, x) end |
#smooth_step3(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
359 360 361 |
# File 'lib/zif/actions/action.rb', line 359 def smooth_step3(x=progress) crossfade(:smooth_start3, :smooth_stop3, x) end |
#smooth_step4(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
364 365 366 |
# File 'lib/zif/actions/action.rb', line 364 def smooth_step4(x=progress) crossfade(:smooth_start4, :smooth_stop4, x) end |
#smooth_step5(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
369 370 371 |
# File 'lib/zif/actions/action.rb', line 369 def smooth_step5(x=progress) crossfade(:smooth_start5, :smooth_stop5, x) end |
#smooth_stop(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
334 335 336 |
# File 'lib/zif/actions/action.rb', line 334 def smooth_stop(x=progress) flip(smooth_start(flip(x))) end |
#smooth_stop3(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
339 340 341 |
# File 'lib/zif/actions/action.rb', line 339 def smooth_stop3(x=progress) flip(smooth_start3(flip(x))) end |
#smooth_stop4(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
344 345 346 |
# File 'lib/zif/actions/action.rb', line 344 def smooth_stop4(x=progress) flip(smooth_start4(flip(x))) end |
#smooth_stop5(x = progress) ⇒ Object
Meant to be called indirectly via setting #easing
349 350 351 |
# File 'lib/zif/actions/action.rb', line 349 def smooth_stop5(x=progress) flip(smooth_start5(flip(x))) end |