Class: AnimateIt::Scene

Inherits:
Object
  • Object
show all
Includes:
AnimationHelpers, ViewHelpers
Defined in:
lib/animate_it/scene.rb

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ViewHelpers

#absolute_fill, #build_factory, #build_stubbed, #motion_asset, #motion_image, #render_partial, #render_scene_template, #render_template, #stub_methods, #style

Methods included from AnimationHelpers

#act_local, #at_act, #at_global, #beat_end, #beat_frame, #beat_range, #fade, #freeze_frame, #interpolate_range, #loop_frame, #loop_iteration, #press, #pulse, #slide

Constructor Details

#initialize(context:, props:) ⇒ Scene

Returns a new instance of Scene.



8
9
10
11
# File 'lib/animate_it/scene.rb', line 8

def initialize(context:, props:)
  @context = context
  @props = props
end

Class Attribute Details

.composition_classObject

Composition class this scene belongs to. Set by the engine when the scene is mounted via composition.scene MyScene / single-scene auto-mount; lets animation property procs reach Composition.beats.



53
54
55
# File 'lib/animate_it/scene.rb', line 53

def composition_class
  @composition_class
end

.current_frameObject

Set automatically at the start of every render to the scene's current local_frame. Fixture closures (e.g. a singleton-method stub that needs to vary by frame) can call MyScene.current_frame without the scene having to remember to assign it in body.



59
60
61
# File 'lib/animate_it/scene.rb', line 59

def current_frame
  @current_frame
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



6
7
8
# File 'lib/animate_it/scene.rb', line 6

def context
  @context
end

#propsObject (readonly)

Returns the value of attribute props.



6
7
8
# File 'lib/animate_it/scene.rb', line 6

def props
  @props
end

#view_contextObject (readonly)

Returns the value of attribute view_context.



6
7
8
# File 'lib/animate_it/scene.rb', line 6

def view_context
  @view_context
end

Class Method Details

.animate(name, &block) ⇒ Object

Declare an animatable element by name. Animation properties are attached via the block:

animate :step_1 do
fade  during: 10..30
slide during: 10..30, from: 16
end

The framework auto-generates the [data-anim="step_1"] CSS rules and the per-frame CSS variables — no :css block in the HAML.



42
43
44
# File 'lib/animate_it/scene.rb', line 42

def animate(name, &block)
  animations.add(name, block)
end

.animationsObject



46
47
48
# File 'lib/animate_it/scene.rb', line 46

def animations
  @animations ||= AnimationSet.new
end

.canvas_class(value = nil) ⇒ Object

Outer wrapper class for the scene's content. Defaults to "hero-render-canvas" (the convention used by all current heros).



27
28
29
30
# File 'lib/animate_it/scene.rb', line 27

def canvas_class(value = nil)
  @canvas_class = value.to_s if value
  @canvas_class ||= "hero-render-canvas"
end

.disable_fragment_caching!Object

Toggles fragment caching off for the rendering controller before body runs. Defeats the dev-environment cache do ... end blocks in production partials that key on stable record updated_ats and would otherwise reuse frame 1's HTML for every subsequent frame.



105
106
107
# File 'lib/animate_it/scene.rb', line 105

def disable_fragment_caching!
  @disable_fragment_caching = true
end

.fixtures(seed: nil, &block) ⇒ Object

Declare a class-level fixture builder. With a block, registers the builder lazily (no work done at class load). Without a block, invokes the registered builder once behind a mutex — Faker::Config.random is reset to Random.new(seed) first (when seed: was given) and FactoryBot.find_definitions is invoked if no factories are loaded. Subsequent calls return the memoized hash.

class HeroScene < AnimateIt::Scene
fixtures(seed: 42) do
  company = build_stubbed_company
  { company:, job: build_stubbed_job(company) }
end
end

HeroScene.fixtures  # → builds once, returns hash


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/animate_it/scene.rb', line 76

def fixtures(seed: nil, &block)
  if block
    @fixtures_block = block
    @fixtures_seed = seed
    return nil
  end

  return @fixtures_value if defined?(@fixtures_value) && @fixtures_value

  @fixtures_mutex ||= Mutex.new
  @fixtures_mutex.synchronize do
    next @fixtures_value if @fixtures_value

    FactoryBot.find_definitions if defined?(FactoryBot) && FactoryBot.factories.none?
    Faker::Config.random = Random.new(@fixtures_seed) if defined?(Faker) && @fixtures_seed
    @fixtures_value = @fixtures_block&.call
  end
end

.fragment_caching_disabled?Boolean

Returns:

  • (Boolean)


109
110
111
# File 'lib/animate_it/scene.rb', line 109

def fragment_caching_disabled?
  @disable_fragment_caching == true
end

.reset_fixtures!Object



95
96
97
98
99
# File 'lib/animate_it/scene.rb', line 95

def reset_fixtures!
  return unless @fixtures_mutex

  @fixtures_mutex.synchronize { @fixtures_value = nil }
end

.stub_methods(object, **stubs) ⇒ Object

Class-side mirror of ViewHelpers#stub_methods so class-method fixture builders (which run before any Scene instance exists) can also use the helper to define singleton-method stubs on build_stubbed records.



117
118
119
120
121
122
123
# File 'lib/animate_it/scene.rb', line 117

def stub_methods(object, **stubs)
  stubs.each do |name, value|
    body = value.respond_to?(:call) ? value : ->(*) { value }
    object.define_singleton_method(name) { |*args| body.call(*args) }
  end
  object
end

.template(name = nil) ⇒ Object

Sidecar template name. Defaults to "canvas" — i.e. a scene whose composition is MyHero looks for app/videos/my_hero/canvas.html.haml. Override to point to a sibling template instead.



20
21
22
23
# File 'lib/animate_it/scene.rb', line 20

def template(name = nil)
  @template = name.to_s if name
  @template ||= "canvas"
end

Instance Method Details

#animation_vars(**extras) ⇒ Object

CSS var bag computed from this frame's animations. Available inside custom render methods that want extra inline styles.



190
191
192
# File 'lib/animate_it/scene.rb', line 190

def animation_vars(**extras)
  Style.vars(**self.class.animations.vars_for(self), **extras)
end

#bodyObject

Default markup: canvas wrapper + sidecar template + auto-generated animation CSS. Override in a subclass when the composition is programmatic (no sidecar template) or has multiple acts.



164
165
166
167
168
169
170
171
172
173
# File 'lib/animate_it/scene.rb', line 164

def body
  tag.div(class: self.class.canvas_class, style: canvas_style) do
    absolute_fill(style: animation_var_style) do
      parts = []
      parts << generated_animation_styles if self.class.animations.elements.any?
      parts << render_scene_template(self.class.template)
      view_context.safe_join(parts)
    end
  end
end

#expose(*keys, from: nil, **ivars) ⇒ Object

Set ivars on the view_context so templates rendered via render_scene_template / view_context.render(template:) can read them as @foo. ActionView's assigns: keyword doesn't propagate through template-level renders, so we push ivars directly onto the same view context the template will be evaluated against.

expose(:job, :user, from: fixtures, top_match: matches.first)

The positional keys are pulled from the from: hash by name; the keyword args are set verbatim.



156
157
158
159
# File 'lib/animate_it/scene.rb', line 156

def expose(*keys, from: nil, **ivars)
  keys.each { |k| view_context.instance_variable_set("@#{k}", from[k]) } if from
  ivars.each { |k, v| view_context.instance_variable_set("@#{k}", v) }
end

#render(view_context) ⇒ Object

Stash view_context so helpers (tag, safe_join, capture) work, then call body for the actual markup. Subclasses should override body (no args) — view_context is already available via the accessor.

The framework's filmstrip + frame controllers call render(view_context); don't override that signature unless you really need to.



135
136
137
138
139
140
141
142
143
144
# File 'lib/animate_it/scene.rb', line 135

def render(view_context)
  @view_context = view_context
  self.class.current_frame = local_frame
  if self.class.fragment_caching_disabled? &&
     view_context.respond_to?(:controller) &&
     view_context.controller.respond_to?(:perform_caching=)
    view_context.controller.perform_caching = false
  end
  body
end

#with_canvas(&block) ⇒ Object

Convenience: when a subclass overrides render it can call this to get the same wrapper + animation-var div around custom content.



177
178
179
180
181
182
183
184
185
186
# File 'lib/animate_it/scene.rb', line 177

def with_canvas(&block)
  tag.div(class: self.class.canvas_class, style: canvas_style) do
    absolute_fill(style: animation_var_style) do
      parts = []
      parts << generated_animation_styles if self.class.animations.elements.any?
      parts << view_context.capture(&block) if block
      view_context.safe_join(parts)
    end
  end
end