Class: Charming::Focus
- Inherits:
-
Object
- Object
- Charming::Focus
- Defined in:
- lib/charming/focus.rb
Overview
Focus manages a stack of focus scopes (rings) for a single controller class. Each scope has a slot ring (a fixed list of named slots) and a current slot within that ring. Multiple scopes can be stacked so the command palette, modals, and layouts can each have their own focus contexts without interfering with one another.
State lives under ‘session[controller_class_name]` so focus persists across controller dispatches within the same session.
Class Method Summary collapse
-
.for(session, controller_class) ⇒ Object
Returns the Focus object for controller_class under the given session, creating the underlying session hash if absent.
Instance Method Summary collapse
-
#current ⇒ Object
Returns the currently focused slot, or nil when no scope is active.
-
#cycle(direction = +1) ⇒ Object
Cycles focus by direction (default +1 forward) within the topmost ring.
-
#define(slots) ⇒ Object
Defines the primary focus ring for the controller with the given slots.
-
#define_layout(slots) ⇒ Object
Defines a layout scope (inserted after the primary ring and before modal scopes).
-
#focus(slot) ⇒ Object
Sets the current slot within the topmost scope to slot.
-
#focused?(slot) ⇒ Boolean
True when slot is the current focus slot.
-
#initialize(state) ⇒ Focus
constructor
A new instance of Focus.
-
#pop_scope ⇒ Object
Pops the topmost focus scope from the stack.
-
#push_scope(slots, origin: :modal) ⇒ Object
Pushes a new focus scope with the given slots onto the stack.
-
#ring ⇒ Object
Returns the slot ring of the topmost scope (an array of slot names).
Constructor Details
#initialize(state) ⇒ Focus
Returns a new instance of Focus.
21 22 23 |
# File 'lib/charming/focus.rb', line 21 def initialize(state) @state = state end |
Class Method Details
.for(session, controller_class) ⇒ Object
Returns the Focus object for controller_class under the given session, creating the underlying session hash if absent.
14 15 16 17 18 19 |
# File 'lib/charming/focus.rb', line 14 def self.for(session, controller_class) session[:focus_state] ||= {} key = controller_class.name session[:focus_state][key] ||= {scopes: []} new(session[:focus_state][key]) end |
Instance Method Details
#current ⇒ Object
Returns the currently focused slot, or nil when no scope is active.
56 57 58 |
# File 'lib/charming/focus.rb', line 56 def current top && top[:current] end |
#cycle(direction = +1) ⇒ Object
Cycles focus by direction (default +1 forward) within the topmost ring. No-op on an empty ring.
73 74 75 76 77 78 |
# File 'lib/charming/focus.rb', line 73 def cycle(direction = +1) return if ring.empty? index = ring.index(current) || 0 top[:current] = ring[(index + direction) % ring.length] end |
#define(slots) ⇒ Object
Defines the primary focus ring for the controller with the given slots. Only effective the first time it is called; subsequent calls are no-ops.
27 28 29 30 31 |
# File 'lib/charming/focus.rb', line 27 def define(slots) return if @state[:scopes].any? { |scope| scope[:origin] == :ring } @state[:scopes] << build_scope(slots, :ring) end |
#define_layout(slots) ⇒ Object
Defines a layout scope (inserted after the primary ring and before modal scopes). slots is the list of pane names; the previously-focused layout slot is preserved when it is still part of the new ring.
36 37 38 39 40 41 42 |
# File 'lib/charming/focus.rb', line 36 def define_layout(slots) current = current_layout_slot(slots) remove_scope(:layout) return if slots.empty? @state[:scopes].insert(layout_scope_index, build_scope(slots, :layout, current)) end |
#focus(slot) ⇒ Object
Sets the current slot within the topmost scope to slot. No-op when slot is not in the ring.
66 67 68 69 70 |
# File 'lib/charming/focus.rb', line 66 def focus(slot) return unless ring.include?(slot) top[:current] = slot end |
#focused?(slot) ⇒ Boolean
True when slot is the current focus slot.
81 82 83 |
# File 'lib/charming/focus.rb', line 81 def focused?(slot) current == slot end |
#pop_scope ⇒ Object
Pops the topmost focus scope from the stack.
51 52 53 |
# File 'lib/charming/focus.rb', line 51 def pop_scope @state[:scopes].pop end |
#push_scope(slots, origin: :modal) ⇒ Object
Pushes a new focus scope with the given slots onto the stack. Used by modals, palettes, and other overlays. origin is a label for the scope kind.
46 47 48 |
# File 'lib/charming/focus.rb', line 46 def push_scope(slots, origin: :modal) @state[:scopes] << build_scope(slots, origin) end |
#ring ⇒ Object
Returns the slot ring of the topmost scope (an array of slot names). Empty when no scope.
61 62 63 |
# File 'lib/charming/focus.rb', line 61 def ring top ? top[:ring] : [] end |