PresentlySourcePresentlyPresentationController

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)

Initialize a new controller for the given presentation.

Signature

parameter presentation Presentation

The presentation to control.

parameter state State | Nil

The state persistence object. If provided, state is saved on changes and restored on initialization.

Implementation

def initialize(presentation, state: nil)
	@presentation = presentation
	@current_index = 0
	@clock = Clock.new
	@listeners = []
	@state = state
	
	@state&.restore(self)
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 nil if no slides are loaded.

Implementation

def current_slide
	@presentation.slides[@current_index]
end

def next_slide

The slide following the current one.

Signature

returns Slide | Nil

The next slide, or nil if 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 nil if 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.

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)
	(time_into_slide / slide.duration).clamp(0.0, 1.0)
end

def reset_timer!

Reset the timer so that elapsed time matches the expected time for the current slide.

Implementation

def reset_timer!
	@clock.reset!(@presentation.expected_time_at(@current_index))
	notify_listeners!
end

def pacing

The current pacing status relative to the slide timing.

Signature

returns Symbol

One of :on_time, :ahead, or :behind.

Implementation

def pacing
	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
	return total_duration unless @clock.started?
	
	expected_remaining = @presentation.expected_time_at(slide_count) - @clock.elapsed
	
	[expected_remaining, 0].max
end

def go_to(index)

Navigate to a specific slide by index. Ignores out-of-bounds indices. Notifies listeners on change.

Signature

parameter index Integer

The slide index to navigate to.

Implementation

def go_to(index)
	return if index < 0 || index >= slide_count
	
	@current_index = index
	notify_listeners!
end

def advance!

Advance to the next slide.

Implementation

def advance!
	go_to(@current_index + 1)
end

def retreat!

Go back 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 listener Object

The listener to add.

Implementation

def add_listener(listener)
	@listeners << listener
end

def remove_listener(listener)

Remove a previously registered listener.

Signature

parameter listener Object

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 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