class PresentationController
Manages the mutable state of a presentation: current slide, clock, and listeners.
Wraps an immutable class Presently::Presentation and provides navigation, timing, and listener notification.
Multiple views (display, presenter) can register as listeners to receive updates.
Definitions
def initialize(presentation, state: nil, recordings: nil)
Initialize a new controller for the given presentation.
Signature
-
parameter
presentationPresentation The presentation to control.
-
parameter
stateState | Nil The state persistence object. If provided, state is saved on changes and restored on initialization.
-
parameter
recordingsRecordings | Nil The narration recording store, when recording is enabled.
Implementation
def initialize(presentation, state: nil, recordings: nil)
@presentation = presentation
@recordings = recordings
@current_index = 0
@clock = Clock.new
@listeners = []
state&.restore(self)
@state = state
end
attr :presentation
Signature
-
attribute
Presentation The underlying presentation data.
attr :current_index
Signature
-
attribute
Integer The index of the current slide.
attr :clock
Signature
-
attribute
Clock The presentation timer.
def templates
The template resolver, delegated to the presentation.
Signature
-
returns
Templates The templates instance.
Implementation
def templates
@presentation.templates
end
def slides
The ordered list of slides, delegated to the presentation.
Signature
-
returns
Array(Slide) The slides.
Implementation
def slides
@presentation.slides
end
def current_slide
The currently displayed slide.
Signature
-
returns
Slide | Nil The current slide, or
nilif no slides are loaded.
Implementation
def current_slide
@presentation.slides[@current_index]
end
def recording_available?(slide)
Whether the given slide has a narration recording.
Signature
-
parameter
slideSlide The slide to check.
-
returns
Boolean
Implementation
def recording_available?(slide)
@recordings&.exist?(slide) || false
end
def next_slide
The slide following the current one.
Signature
-
returns
Slide | Nil The next slide, or
nilif on the last slide.
Implementation
def next_slide
@presentation.slides[@current_index + 1]
end
def previous_slide
The slide preceding the current one.
Signature
-
returns
Slide | Nil The previous slide, or
nilif on the first slide.
Implementation
def previous_slide
@presentation.slides[@current_index - 1] if @current_index > 0
end
def slide_count
The total number of slides.
Signature
-
returns
Integer The slide count.
Implementation
def slide_count
@presentation.slide_count
end
def total_duration
The total expected duration of the presentation.
Signature
-
returns
Numeric The total duration in seconds.
Implementation
def total_duration
@presentation.total_duration
end
def slide_progress
The progress through the current slide's allocated time. Slides with durations of zero or less are complete once their expected start time is reached.
Signature
-
returns
Float A value between 0.0 and 1.0.
Implementation
def slide_progress
return 0.0 unless @clock.started?
slide = current_slide
return 0.0 unless slide
time_into_slide = @clock.elapsed - @presentation.expected_time_at(@current_index)
if slide.duration <= 0
time_into_slide.negative? ? 0.0 : 1.0
else
(time_into_slide / slide.duration).clamp(0.0, 1.0)
end
end
def reset_timer!
Reset a paused timer to begin the current slide again.
All slides reset to their expected start time. A timer: start slide becomes
ready to start when advancing; other slides remain paused.
Implementation
def reset_timer!
return unless @clock.paused? && current_slide
@clock.reset!(@presentation.expected_time_at(@current_index), started: current_slide.timer != "start")
notify_listeners!
end
def pacing
The current pacing status relative to the slide timing.
Signature
-
returns
Symbol | Nil One of
:on_time,:ahead, or:behind, ornilwhen no presentation time is allocated.
Implementation
def pacing
return unless total_duration.positive?
return :on_time unless @clock.started?
elapsed = @clock.elapsed
slide_start = @presentation.expected_time_at(@current_index)
slide_end = @presentation.expected_time_at(@current_index + 1)
if elapsed > slide_end
:behind
elsif elapsed < slide_start
:ahead
else
:on_time
end
end
def time_remaining
The estimated time remaining in the presentation.
Signature
-
returns
Numeric The remaining time in seconds.
Implementation
def time_remaining
expected_remaining = @presentation.expected_time_at(slide_count) - @clock.elapsed
[expected_remaining, 0].max
end
def go_to(index, timer: false)
Navigate to a specific slide by index. Ignores out-of-bounds indices. Notifies listeners on change.
Signature
-
parameter
indexInteger The slide index to navigate to.
-
parameter
timerBoolean Whether to apply the outgoing slide's timer action before notifying listeners.
-
returns
Boolean Whether the destination index was valid.
Implementation
def go_to(index, timer: false)
return false if index < 0 || index >= slide_count
advance_timer! if timer
@current_index = index
notify_listeners!
return true
end
def advance!(timer: true)
Advance to the next slide, applying the current slide's timer action. Timer actions only run when there is a next slide.
Signature
-
parameter
timerBoolean Whether to apply the outgoing slide's timer action.
-
returns
Boolean Whether the presentation advanced to the next slide.
Implementation
def advance!(timer: true)
go_to(@current_index + 1, timer: timer)
end
def retreat!
Go back to the previous slide.
Signature
-
returns
Boolean Whether the presentation moved to the previous slide.
Implementation
def retreat!
go_to(@current_index - 1)
end
def add_listener(listener)
Register a listener to be notified when the slide changes.
The listener must respond to #slide_changed!.
Signature
-
parameter
listenerObject The listener to add.
Implementation
def add_listener(listener)
@listeners << listener
end
def remove_listener(listener)
Remove a previously registered listener.
Signature
-
parameter
listenerObject The listener to remove.
Implementation
def remove_listener(listener)
@listeners.delete(listener)
end
def reload!
Reload slides from disk and notify listeners.
Implementation
def reload!
@presentation = @presentation.reload
notify_listeners!
end
def save_state!
Persist the current state to disk.
Implementation
def save_state!
@state&.save(self)
end
def advance_timer!
Apply the outgoing slide's timer action without resetting elapsed time.
Implementation
def advance_timer!
case current_slide.timer
when "start"
@clock.start! unless @clock.started?
when "pause"
@clock.pause!
when "resume"
@clock.resume! if @clock.started?
end
end
def notify_listeners!
Notify all registered listeners that the slide has changed, and persist state.
Implementation
def notify_listeners!
@state&.save(self)
@listeners.each do |listener|
listener.slide_changed!
rescue => error
Console.warn(self, "Listener notification failed", exception: error)
end
end