Class: Charming::Controller

Inherits:
Object
  • Object
show all
Extended by:
ClassMethods
Includes:
ActionHooks, CommandPalette, ComponentDispatching, Dispatching, FocusManagement, Rendering, SessionState, SidebarNavigation
Defined in:
lib/charming/controller.rb,
lib/charming/controller/focus.rb,
lib/charming/controller/rendering.rb,
lib/charming/controller/dispatching.rb,
lib/charming/controller/action_hooks.rb,
lib/charming/controller/class_methods.rb,
lib/charming/controller/session_state.rb,
lib/charming/controller/command_palette.rb,
lib/charming/controller/focus_management.rb,
lib/charming/controller/sidebar_navigation.rb,
lib/charming/controller/component_dispatching.rb

Overview

Controller is the base class for all controller implementations in a Charming application. It provides the action dispatch pipeline, key/command/timer/task bindings, sidebar navigation, command palette management, and view rendering with layout composition.

Defined Under Namespace

Modules: ActionHooks, ClassMethods, CommandPalette, ComponentDispatching, Dispatching, FocusManagement, Rendering, SessionState, SidebarNavigation Classes: Focus, TaskBinding, TimerBinding

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ClassMethods

auto_render, auto_render_action, command, command_bindings, focus_ring, focus_ring_slots, key, key_binding_scopes, key_bindings, layout, on_task, on_task_progress, task_bindings, task_progress_bindings, timer, timer_bindings

Methods included from CommandPalette

#close_command_palette, #command_palette, #command_palette_open?, #open_command_palette

Methods included from SidebarNavigation

#content_focused?, #current_route?, #focus_content, #focus_sidebar, #sidebar_focused?, #sidebar_index, #sidebar_routes

Methods included from FocusManagement

#focus, #focused?

Methods included from SessionState

#cancel_task, #form, #mouse_targets, #register_mouse_targets, #run_task, #session, #state

Methods included from ActionHooks

included

Constructor Details

#initialize(application:, event: nil, params: {}, screen: nil, route: nil) ⇒ Controller

Initializes the controller with its parent application and optional event. Defaults to an 80x24 screen when no backend size is available.



25
26
27
28
29
30
31
32
# File 'lib/charming/controller.rb', line 25

def initialize(application:, event: nil, params: {}, screen: nil, route: nil)
  @application = application
  @event = event
  @params = params
  @screen = screen || Screen.new(width: 80, height: 24)
  @route = route
  @response = nil
end

Instance Attribute Details

#applicationObject (readonly)

Returns the value of attribute application.



21
22
23
# File 'lib/charming/controller.rb', line 21

def application
  @application
end

#eventObject (readonly)

Returns the value of attribute event.



21
22
23
# File 'lib/charming/controller.rb', line 21

def event
  @event
end

#paramsObject (readonly)

Returns the value of attribute params.



21
22
23
# File 'lib/charming/controller.rb', line 21

def params
  @params
end

#routeObject (readonly)

Returns the value of attribute route.



21
22
23
# File 'lib/charming/controller.rb', line 21

def route
  @route
end

#screenObject (readonly)

Returns the value of attribute screen.



21
22
23
# File 'lib/charming/controller.rb', line 21

def screen
  @screen
end

Instance Method Details

#dispatch(action) ⇒ Object

Dispatches a named action on this controller (e.g. :show), running all before/around/after hooks and rescue_from handlers.



36
37
38
39
40
# File 'lib/charming/controller.rb', line 36

def dispatch(action)
  run_action_with_hooks(action)
  render_default_action if response.nil? && auto_render_after?(action)
  response || render("")
end

#dispatch_keyObject

Key event dispatch, in priority order:

  1. Command palette (when open) consumes everything.

  2. A focused text-capturing component (TextInput, TextArea, Form, …) gets printable characters BEFORE key bindings — typing “q” into a field must insert a q, not quit the app.

  3. Global key bindings.

  4. An overlay focus scope (a pushed modal) captures all remaining keys.

  5. Sidebar keys (when focused), content bindings, then the focused component —which sees Tab before ring traversal so forms can do field navigation.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/charming/controller.rb', line 51

def dispatch_key
  return dispatch_command_palette_key if command_palette_open?

  if printable_text_event? && focused_component_captures_text?
    return response if dispatch_to_focused_component == :handled
  end

  return dispatch(global_key_action) if global_key_action

  if focus.overlay?
    dispatch_to_focused_component
    return response
  end

  return dispatch_sidebar_key if sidebar_focused?
  return dispatch(content_key_action) if content_key_action

  # Text-capturing components (forms, editors) own their remaining keys — Tab
  # included, so forms do field navigation. Everything else keeps ring traversal
  # ahead of the component.
  if focused_component_captures_text?
    return response if dispatch_to_focused_component == :handled
    return response if dispatch_tab_traversal == :handled
  else
    return response if dispatch_tab_traversal == :handled
    return response if dispatch_to_focused_component == :handled
  end
  nil
end

#dispatch_mouseObject

Mouse event dispatcher: command palette (if open) wins, then sidebar clicks (route rows navigate directly), then named layout panes/components.



120
121
122
123
124
125
126
127
# File 'lib/charming/controller.rb', line 120

def dispatch_mouse
  return dispatch_command_palette_mouse if command_palette_open?

  sidebar_response = dispatch_sidebar_mouse
  return sidebar_response if sidebar_response

  dispatch_component_mouse
end

#dispatch_pasteObject

Paste event dispatcher: forwards pasted text to the focused component’s ‘handle_paste` (TextInput, TextArea, and form fields support it).



104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/charming/controller.rb', line 104

def dispatch_paste
  slot = focus.current
  return nil unless slot && respond_to?(slot, true)

  component = send(slot)
  return nil unless component.respond_to?(:handle_paste)

  result = component.handle_paste(event)
  return nil if result.nil?

  dispatch_component_result(slot, result)
  response
end

#dispatch_taskObject

Task event dispatcher: looks up the handler in task bindings.



91
92
93
94
# File 'lib/charming/controller.rb', line 91

def dispatch_task
  b = self.class.task_bindings[event.name.to_sym]
  b ? dispatch(b.action) : nil
end

#dispatch_task_progressObject

Task progress dispatcher: looks up the handler in task progress bindings.



97
98
99
100
# File 'lib/charming/controller.rb', line 97

def dispatch_task_progress
  b = self.class.task_progress_bindings[event.name.to_sym]
  b ? dispatch(b.action) : nil
end

#dispatch_timerObject

Timer event dispatcher: looks up the named action in timer bindings.



82
83
84
85
86
87
88
# File 'lib/charming/controller.rb', line 82

def dispatch_timer
  b = self.class.timer_bindings[event.name.to_sym]
  return nil unless b

  public_send(b.action)
  response
end

#loggerObject

Returns the application logger. The default logger writes to File::NULL, so logging calls are safe in TUI code unless the app explicitly configures a file or custom logger.



157
158
159
# File 'lib/charming/controller.rb', line 157

def logger
  application.logger
end

Navigates to the given URL path.



169
170
171
# File 'lib/charming/controller.rb', line 169

def navigate_to(path)
  @response = Response.navigate(path)
end

#open_theme_paletteObject

Opens the theme picker (a CommandPalette populated with the registered themes) and renders.



162
163
164
165
166
# File 'lib/charming/controller.rb', line 162

def open_theme_palette
  session[:command_palette] = command_palette_state(:themes)
  focus.push_scope([:command_palette], origin: :command_palette)
  render_default_action
end

#quitObject

Exits the application — sets a quit response that terminates the event loop.



174
175
176
# File 'lib/charming/controller.rb', line 174

def quit
  @response = Response.quit
end

#render(body = "", **assigns) ⇒ Object

Renders a body or template wrapped in the controller’s layout.



130
131
132
133
# File 'lib/charming/controller.rb', line 130

def render(body = "", **assigns)
  body = view_body(default_template_name(body), **assigns) if body.is_a?(Symbol)
  @response = Response.render(render_with_layout(body))
end

#render_template(name, **assigns) ⇒ Object

Renders a template from ‘app/views` by name, applying the controller’s layout. name is the template path (e.g., “home/show”) and additional keyword assigns are forwarded to the view.



141
142
143
# File 'lib/charming/controller.rb', line 141

def render_template(name, **assigns)
  @response = Response.render(render_with_layout(template_body(name, **assigns)))
end

#render_view(view_class, **assigns) ⇒ Object



135
136
137
# File 'lib/charming/controller.rb', line 135

def render_view(view_class, **assigns)
  @response = Response.render(render_with_layout(view_class.new(**template_assigns(assigns))))
end

#themeObject

Returns the active theme for this request, delegated to the application.



146
147
148
# File 'lib/charming/controller.rb', line 146

def theme
  application.theme
end

#use_theme(name) ⇒ Object

Switches the active theme to name and persists the choice in the application session.



151
152
153
# File 'lib/charming/controller.rb', line 151

def use_theme(name)
  application.use_theme(name)
end