Class: Gemba::ModalStack

Inherits:
Object
  • Object
show all
Defined in:
lib/gemba/modal_stack.rb

Overview

Push/pop stack for modal child windows.

One modal can push another (e.g. Settings → Replay Player) and the previous modal is automatically re-shown on pop.

Windows must implement the ModalWindow protocol:

show_modal(**args) — reveal the window (deiconify, grab, position)
withdraw           — hide the window (release grab, withdraw — NO callback)

Examples:

stack = ModalStack.new(
  on_enter: ->(name) { pause_emulation },
  on_exit:  -> { unpause_emulation },
  on_focus_change: ->(name) { update_toast(name) },
)
stack.push(:settings, @settings_window, show_args: { tab: :video })
stack.push(:replay, @replay_player)  # settings auto-withdrawn
stack.pop  # replay closed, settings re-shown
stack.pop  # settings closed, on_exit fired

Defined Under Namespace

Classes: Entry

Instance Method Summary collapse

Constructor Details

#initialize(on_enter:, on_exit:, on_focus_change: nil) ⇒ ModalStack

Returns a new instance of ModalStack.

Parameters:

  • on_enter (Proc)

    called with (name) when stack goes empty → non-empty

  • on_exit (Proc)

    called when stack goes non-empty → empty

  • on_focus_change (Proc, nil) (defaults to: nil)

    called with (name) whenever the top modal changes



29
30
31
32
33
34
# File 'lib/gemba/modal_stack.rb', line 29

def initialize(on_enter:, on_exit:, on_focus_change: nil)
  @stack = []
  @on_enter = on_enter
  @on_exit  = on_exit
  @on_focus_change = on_focus_change
end

Instance Method Details

#active?Boolean

Returns true if any modal is open.

Returns:

  • (Boolean)

    true if any modal is open



37
# File 'lib/gemba/modal_stack.rb', line 37

def active? = !@stack.empty?

#currentSymbol?

Returns name of the topmost modal, or nil.

Returns:

  • (Symbol, nil)

    name of the topmost modal, or nil



40
# File 'lib/gemba/modal_stack.rb', line 40

def current = @stack.last&.name

#popObject

Pop the current modal off the stack.

If the stack still has entries, the previous modal is re-shown. If the stack is now empty, on_exit is fired (unpause emulation, etc.).



69
70
71
72
73
74
75
76
77
78
79
# File 'lib/gemba/modal_stack.rb', line 69

def pop
  return unless (entry = @stack.pop)
  entry.window.withdraw

  if (prev = @stack.last)
    @on_focus_change&.call(prev.name)
    prev.window.show_modal(**prev.show_args)
  else
    @on_exit.call
  end
end

#push(name, window, show_args: {}) ⇒ Object

Push a modal onto the stack.

If another modal is on top, it is withdrawn (without callback). If the stack was empty, on_enter is fired (pause emulation, etc.).

Parameters:

  • name (Symbol)

    identifier for the modal (e.g. :settings, :picker)

  • window (#show_modal, #withdraw)

    the modal window object

  • show_args (Hash) (defaults to: {})

    keyword arguments forwarded to window.show_modal



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/gemba/modal_stack.rb', line 53

def push(name, window, show_args: {})
  was_empty = @stack.empty?

  # Withdraw current top without firing its on_dismiss
  @stack.last&.window&.withdraw

  @stack.push(Entry.new(name: name, window: window, show_args: show_args))
  @on_enter.call(name) if was_empty
  @on_focus_change&.call(name)
  window.show_modal(**show_args)
end

#sizeInteger

Returns number of modals on the stack.

Returns:

  • (Integer)

    number of modals on the stack



43
# File 'lib/gemba/modal_stack.rb', line 43

def size = @stack.length