Class: RGame::Engine::UI::Menu

Inherits:
Node2D
  • Object
show all
Defined in:
lib/rgame/engine/ui/menu.rb

Overview

A vertical list of things to choose from, navigated by keyboard or controller.

menu = layer.add_node(UI::Menu.new(item_width: 220, item_height: 44))
menu.add_item('Resume').on_activated { close }
menu.add_item('Quit').on_activated   { game.close }

Focus is the whole design

With no pointer there is no hover, so something has to own which control is focused and how the directions move it. That is this class, and everything else about a menu follows from it: an item draws differently because it is focused, and ui_confirm activates the focused one.

Focus is per player, and that costs nothing

A Menu inside a PlayerLayer inherits that player as its input_owner, so the actions its on_control receives are already that player's. Two players with a menu open at once are independent without either menu knowing the other exists, and without a word of focus-specific per-player machinery. That falls out of ownership being inherited down the tree — see docs/api/scene_graph.md, "Who a node answers to".

What this is not

It is a menu, not a widget library. Items are stacked vertically at a fixed size, and that is the whole of its layout. The package this replaces positioned everything absolutely and hit-tested a mouse; none of it is a reference, and how UI should be laid out in general is still an open question — see docs/api/ui.md, "What this is not".

Instance Attribute Summary collapse

Attributes inherited from Node2D

#abs_angle, #abs_band, #abs_input_owner, #abs_x, #abs_y, #angle, #band, #children, #components, #context, #height, #input_owner, #parent, #paused, #scene, #sibling_order, #width, #x, #y, #z

Instance Method Summary collapse

Methods inherited from Node2D

#add_component, #add_node, #children_unsorted!, #control, #draw, #enter_tree, #exit_tree, #freed?, #get_component, #in_tree?, #on_add, #on_draw, #on_remove, #on_update, #queue_free, #remove_component, #remove_node, #root, #sweep_freed, #system, #update

Methods included from Signal::DSL

#signal

Constructor Details

#initialize(item_width:, item_height:, spacing: 8, style: MenuItem::STYLE) ⇒ Menu

Navigation wraps: a short vertical list is quicker to use when the ends join, and every console menu does it.



40
41
42
43
44
45
46
47
48
49
# File 'lib/rgame/engine/ui/menu.rb', line 40

def initialize(item_width:, item_height:, spacing: 8,
               style: MenuItem::STYLE, **)
  super(**)
  @item_width = item_width
  @item_height = item_height
  @spacing = spacing
  @style = style
  @items = []
  @focused_index = 0
end

Instance Attribute Details

#focused_indexObject (readonly)

Returns the value of attribute focused_index.



51
52
53
# File 'lib/rgame/engine/ui/menu.rb', line 51

def focused_index
  @focused_index
end

#itemsObject (readonly)

Returns the value of attribute items.



51
52
53
# File 'lib/rgame/engine/ui/menu.rb', line 51

def items
  @items
end

Instance Method Details

#add_item(label, enabled: true) ⇒ Object

Adds an item below the last one and returns it, so a caller can connect to its signal in the same line.



55
56
57
58
59
60
61
62
63
# File 'lib/rgame/engine/ui/menu.rb', line 55

def add_item(label, enabled: true)
  item = MenuItem.new(label: label, enabled: enabled, style: @style,
                      x: 0, y: @items.size * (@item_height + @spacing),
                      width: @item_width, height: @item_height)
  @items << item
  add_node(item)
  refocus
  item
end

#focus(index) ⇒ Object



82
83
84
85
# File 'lib/rgame/engine/ui/menu.rb', line 82

def focus(index)
  @focused_index = index
  @items.each_with_index { |item, i| item.focused = (i == index) }
end

#focus_by(delta) ⇒ Object

Moves focus by delta, skipping anything disabled, and wrapping. Does nothing at all if no item can take focus.



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/rgame/engine/ui/menu.rb', line 69

def focus_by(delta)
  return if @items.empty?

  index = @focused_index
  @items.size.times do
    index = (index + delta) % @items.size
    next unless @items[index].enabled?

    focus(index)
    return
  end
end

#focusedObject



65
# File 'lib/rgame/engine/ui/menu.rb', line 65

def focused = @items[@focused_index]

#on_control(actions) ⇒ Object



87
88
89
90
91
92
93
94
95
96
# File 'lib/rgame/engine/ui/menu.rb', line 87

def on_control(actions)
  focus_by(-1) if actions.pressed?(:ui_up)
  focus_by(1) if actions.pressed?(:ui_down)

  current = focused
  return if current.nil?

  current.pressed = actions.held?(:ui_confirm)
  current.activate if actions.pressed?(:ui_confirm)
end