Class: Plushie::Command

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/plushie/command.rb,
lib/plushie/command/text.rb,
lib/plushie/command/image.rb,
lib/plushie/command/scroll.rb,
lib/plushie/command/window.rb,
lib/plushie/command/window_query.rb

Overview

Commands describe side effects that update wants the runtime to perform.

They are pure data: inspectable, testable, serializable. The runtime interprets them after update returns. Nothing executes inside update.

== Command categories

  • Basic: async, stream, cancel, done, send_after, exit, batch
  • Focus: focus, focus_next, focus_previous
  • Text (Text): select_all, move_cursor_to, select_range, ...
  • Scroll (Scroll): scroll_to, snap_to, scroll_by, ...
  • Window (Window): resize_window, close_window, focus_window, ...
  • Window queries (WindowQuery): window_size, window_mode, ...
  • Image (Image): create_image, create_image_rgba, update_image, update_image_rgba, delete_image, ...
  • Widget command: widget_command for native widget operations

All submodule methods are also available directly on Command via delegation: Command.scroll_to("list", 0, 100) # via delegation Command::Scroll.scroll_to("list", 0, 100) # directly

Examples:

Async work

[model, Command.task(-> { fetch_data }, :data_loaded)]

Focus a widget

[model, Command.focus("input_field")]

Native widget command

[model, Command.widget_command("chart-1", "append_data", {values: [1.0, 2.5]})]

Defined Under Namespace

Modules: Image, Scroll, Text, Window, WindowQuery Classes: Cmd

Class Method Summary collapse

Class Method Details

.advance_frame(timestamp) ⇒ Cmd

Advance the animation clock. For deterministic testing.

Parameters:

  • timestamp (Integer)

    frame timestamp in milliseconds

Returns:



277
# File 'lib/plushie/command.rb', line 277

def self.advance_frame(timestamp) = Cmd.new(type: :advance_frame, payload: {timestamp:})

.announce(text, politeness = :polite) ⇒ Cmd

Screen reader announcement.

+politeness+ controls how assistive technology delivers the announcement. +:polite+ (the default) waits for a gap in the current announcement and is correct for most toast-style feedback. +:assertive+ interrupts the current announcement and should be reserved for urgent context the user must hear immediately.

Parameters:

  • text (String)
  • politeness (Symbol) (defaults to: :polite)

    +:polite+ (default) or +:assertive+

Returns:



263
264
265
266
267
268
# File 'lib/plushie/command.rb', line 263

def self.announce(text, politeness = :polite)
  unless %i[polite assertive].include?(politeness)
    raise ArgumentError, "politeness must be :polite or :assertive, got #{politeness.inspect}"
  end
  Cmd.new(type: :widget_op, payload: {op: "announce", text:, politeness: politeness.to_s})
end

.batch(commands) ⇒ Cmd

Combine multiple commands. Executed sequentially.

Parameters:

  • commands (Array<Cmd>)

Returns:



89
# File 'lib/plushie/command.rb', line 89

def self.batch(commands) = Cmd.new(type: :batch, payload: {commands:})

.cancel(tag) ⇒ Cmd

Cancel a running async or stream task by tag.

Parameters:

  • tag (Symbol)

Returns:



68
# File 'lib/plushie/command.rb', line 68

def self.cancel(tag) = Cmd.new(type: :cancel, payload: {tag:})

.dispatch(value, mapper_fn) ⇒ Cmd

Lift an already-resolved value into the command pipeline.

Parameters:

  • value (Object)

    the resolved value

  • mapper_fn (Proc)

    function that wraps value into an event

Returns:



74
# File 'lib/plushie/command.rb', line 74

def self.dispatch(value, mapper_fn) = Cmd.new(type: :dispatch, payload: {value:, mapper: mapper_fn})

.exitCmd

Terminate the application.

Returns:



84
# File 'lib/plushie/command.rb', line 84

def self.exit = Cmd.new(type: :exit, payload: {})

.find_focused(tag) ⇒ Cmd

Find focused widget. Result via Event::System.

Parameters:

  • tag (Symbol)

Returns:



231
# File 'lib/plushie/command.rb', line 231

def self.find_focused(tag) = Cmd.new(type: :widget_op, payload: {op: "find_focused", tag: tag.to_s})

.focus(widget_id) ⇒ Cmd

Focus the widget identified by +widget_id+. Supports window-qualified paths: +"main#email"+.

Parameters:

  • widget_id (String)

Returns:



131
132
133
# File 'lib/plushie/command.rb', line 131

def self.focus(widget_id)
  widget_command(widget_id, "focus")
end

.focus_nextCmd

Move focus to the next focusable widget.

Returns:



137
# File 'lib/plushie/command.rb', line 137

def self.focus_next = Cmd.new(type: :widget_op, payload: {op: "focus_next"})

.focus_next_within(scope) ⇒ Cmd

Move focus to the next focusable widget within the subtree rooted at +scope+. Focus wraps at the subtree boundary. Useful for menus, pane grids, and other keyboard containers that want a bounded Tab cycle without leaking focus to siblings.

Parameters:

  • scope (String)

    the widget ID of the subtree root

Returns:



149
150
151
# File 'lib/plushie/command.rb', line 149

def self.focus_next_within(scope)
  Cmd.new(type: :widget_op, payload: {op: "focus_next_within", scope: scope})
end

.focus_previousCmd

Move focus to the previous focusable widget.

Returns:



141
# File 'lib/plushie/command.rb', line 141

def self.focus_previous = Cmd.new(type: :widget_op, payload: {op: "focus_previous"})

.focus_previous_within(scope) ⇒ Cmd

Move focus to the previous focusable widget within the subtree rooted at +scope+. See focus_next_within for semantics.

Parameters:

  • scope (String)

    the widget ID of the subtree root

Returns:



157
158
159
# File 'lib/plushie/command.rb', line 157

def self.focus_previous_within(scope)
  Cmd.new(type: :widget_op, payload: {op: "focus_previous_within", scope: scope})
end

.load_font(family, data) ⇒ Cmd

Load a font at runtime from TTF/OTF data.

The renderer accepts this as a typed top-level +load_font+ message. MessagePack carries the font bytes as native binary; JSON carries them base64-encoded. Framing handles the per-format conversion.

Parameters:

  • family (String)

    font family name the app will refer to this font by

  • data (String)

    font file bytes

Returns:



246
# File 'lib/plushie/command.rb', line 246

def self.load_font(family, data) = Cmd.new(type: :load_font, payload: {family:, data:})

.noneCmd

Returns no-op command.

Returns:

  • (Cmd)

    no-op command



50
# File 'lib/plushie/command.rb', line 50

def self.none = Cmd.new(type: :none, payload: {})

.pane_close(grid_id, pane_id) ⇒ Cmd

Close a pane in the pane grid.

Parameters:

  • grid_id (String)
  • pane_id (String)

Returns:



179
180
181
# File 'lib/plushie/command.rb', line 179

def self.pane_close(grid_id, pane_id)
  widget_command(grid_id, "pane_close", {pane: pane_id})
end

.pane_maximize(grid_id, pane_id) ⇒ Cmd

Maximize a pane in the pane grid.

Parameters:

  • grid_id (String)
  • pane_id (String)

Returns:



196
197
198
# File 'lib/plushie/command.rb', line 196

def self.pane_maximize(grid_id, pane_id)
  widget_command(grid_id, "pane_maximize", {pane: pane_id})
end

.pane_restore(grid_id) ⇒ Cmd

Restore all panes from maximized state.

Parameters:

  • grid_id (String)

Returns:



203
204
205
# File 'lib/plushie/command.rb', line 203

def self.pane_restore(grid_id)
  widget_command(grid_id, "pane_restore")
end

.pane_split(grid_id, pane_id, axis, new_pane_id) ⇒ Cmd

Split a pane in the pane grid.

Parameters:

  • grid_id (String)
  • pane_id (String)
  • axis (Symbol)

    :horizontal or :vertical

  • new_pane_id (String)

Returns:



171
172
173
# File 'lib/plushie/command.rb', line 171

def self.pane_split(grid_id, pane_id, axis, new_pane_id)
  widget_command(grid_id, "pane_split", {pane: pane_id, axis: axis.to_s, new_pane_id: new_pane_id})
end

.pane_swap(grid_id, pane_a, pane_b) ⇒ Cmd

Swap two panes in the pane grid.

Parameters:

  • grid_id (String)
  • pane_a (String)
  • pane_b (String)

Returns:



188
189
190
# File 'lib/plushie/command.rb', line 188

def self.pane_swap(grid_id, pane_a, pane_b)
  widget_command(grid_id, "pane_swap", {a: pane_a, b: pane_b})
end

.send_after(delay_ms, event) ⇒ Cmd

Send an event to update after a delay.

Parameters:

  • delay_ms (Integer)

    delay in milliseconds

  • event (Object)

    event to deliver

Returns:



80
# File 'lib/plushie/command.rb', line 80

def self.send_after(delay_ms, event) = Cmd.new(type: :send_after, payload: {delay: delay_ms, event:})

.stream(callable, tag) ⇒ Cmd

Run a callable that emits multiple values via an emit callback. Each emit delivers Event::Stream; the final return delivers Event::Async.

Parameters:

  • callable (Proc)

    receives an emit proc as argument

  • tag (Symbol)

    event tag

Returns:



63
# File 'lib/plushie/command.rb', line 63

def self.stream(callable, tag) = Cmd.new(type: :stream, payload: {callable:, tag:})

.system_info(tag) ⇒ Cmd

Parameters:

  • tag (Symbol)

Returns:



217
# File 'lib/plushie/command.rb', line 217

def self.system_info(tag) = Cmd.new(type: :system_query, payload: {op: "get_system_info", tag: tag.to_s})

.system_theme(tag) ⇒ Cmd

Parameters:

  • tag (Symbol)

Returns:



213
# File 'lib/plushie/command.rb', line 213

def self.system_theme(tag) = Cmd.new(type: :system_query, payload: {op: "get_system_theme", tag: tag.to_s})

.task(callable, tag) ⇒ Cmd

Run a callable asynchronously. Result delivered as Event::Async.

Parameters:

  • callable (Proc, Lambda)

    the work to run

  • tag (Symbol)

    event tag for the result

Returns:



56
# File 'lib/plushie/command.rb', line 56

def self.task(callable, tag) = Cmd.new(type: :task, payload: {callable:, tag:})

.tree_hash(tag) ⇒ Cmd

Compute tree hash. Result via Event::System.

Parameters:

  • tag (Symbol)

Returns:



226
# File 'lib/plushie/command.rb', line 226

def self.tree_hash(tag) = Cmd.new(type: :widget_op, payload: {op: "tree_hash", tag: tag.to_s})

.widget_batch(commands) ⇒ Cmd

Send a batch of widget commands processed atomically in one cycle.

Each command in the list is a +family:, value:+ Hash. All commands are applied before any resulting events are emitted.

Parameters:

  • commands (Array<Hash>)

    each with :id, :family, :value keys

Returns:



119
120
121
# File 'lib/plushie/command.rb', line 119

def self.widget_batch(commands)
  Cmd.new(type: :commands, payload: {commands: commands})
end

.widget_command(id, family, value = nil) ⇒ Cmd

Send a command to a widget by ID.

Uses the unified wire format matching events: "command", "id": "gauge", "family": "set_value", "value": 72.0

The +value+ defaults to nil for commands with no payload (e.g. reset). The +family+ string identifies the operation. For native widgets, it maps to the Rust widget's handle_widget_op dispatch.

Parameters:

  • id (String)

    target widget ID (supports "window#path" format)

  • family (String)

    operation name

  • value (Object, nil) (defaults to: nil)

    operation-specific data

Returns:



108
109
110
# File 'lib/plushie/command.rb', line 108

def self.widget_command(id, family, value = nil)
  Cmd.new(type: :command, payload: {id: id, family: family.to_s, value: value})
end