cube-tube/🕹️cube-tube/app/scenes/menu.rb

166 lines
4.2 KiB
Ruby

# frozen_string_literal: true
# A scene which updates and renders a list of options that get passed through.
#
# +menu_options+ data structure:
# [
# {
# text: 'some string',
# on_select: -> (args) { 'do some stuff in this lambda' }
# }
# ]
class MenuScene < SceneInstance
def initialize(args, opts = {}, title = title(), menu_options = [])
super args, opts
@menu_state ||= {
current_option_i: 0,
hold_delay: 0
}
@spacer ||= 80
@menu_options ||= menu_options
@menu_y = opts.menu_y._?(440)
@title ||= title
@rand_strings = (0..@menu_options.length).map do |i|
(0...@title.length).map { ('A'..'Z').to_a[rand(26)] }.join
end
@first_render = nil
end
def render_options(args)
labels = []
@menu_options.each.with_index do |option, i|
active = @menu_state.current_option_i == i && (!mobile? || (mobile? && args.inputs.controller_one.connected))
text = case option.kind
when :toggle
"#{text(option[:key])}: #{text_for_setting_val(args, option[:key])}"
else
text(option[:key])
end
if (args.state.tick_count - @first_render) < 60 * (1.5 + i) * 0.2
if args.state.tick_count % 4 == 0
@rand_strings[i] = (0...(rand(text.length >= 3 ? text.length : 3) + 3)).map { ('A'..'Z').to_a[rand(26)] }.join
end
text = @rand_strings[i]
active = false
end
l = label(
text.upcase,
x: (args.grid.w / 2) - 70,
y: @menu_y + (@menu_options.length - (i * @spacer)),
align: ALIGN_CENTER,
size: 16,
font: FONT_DOTMATRIX_BOLD
)
l.key = option[:key]
l.width, l.height = args.gtk.calcstringbox(l.text, l.size_enum, l.font)
labels << l.merge(active ? WHITE : YELLOW)
if active
labels << label(
'.',
x: l.x - (l.width / 2) - 26 - (Math.sin(args.state.tick_count / 8) * 4),
y: l.y + 15,
align: ALIGN_CENTER,
size: 18,
font: FONT_DOTMATRIX
).merge(WHITE)
end
button_border = { w: 340, h: 80, x: l.x - 170, y: l.y - 55 }.merge(WHITE)
# (args.outputs.borders << button_border) if mobile?
if args.inputs.mouse.up && args.inputs.mouse.inside_rect?(button_border)
o = @menu_options.find { |o| o[:key] == l[:key] }
Sound.play(args, :menu)
o[:on_select].call(args) if o
end
end
args.outputs.labels << labels
end
# called every tick of the game loop
def tick(args)
super
@first_render = args.state.tick_count if @first_render.nil?
Sprite.for(:menu_l).render(args)
args.outputs.labels << label(
@title.to_s.upcase,
x: (args.grid.w / 2) - 70,
y: args.grid.top - 175,
align: ALIGN_CENTER,
size: SIZE_LG,
font: FONT_RUBIK_BLACK
).merge(TRUE_BLACK)
render_options(args)
move = nil
if Input.down?(args)
move = :down
elsif Input.up?(args)
move = :up
else
@menu_state.hold_delay = 0
end
if move
@menu_state.hold_delay -= 1
if @menu_state.hold_delay <= 0
Sound.play(args, :menu)
index = @menu_state.current_option_i
if move == :up
index -= 1
else
index += 1
end
if index.negative?
index = @menu_options.length - 1
elsif index > @menu_options.length - 1
index = 0
end
@menu_state.current_option_i = index
@menu_state.hold_delay = 10
end
end
if Input.pressed?(args, :primary)
@menu_options[@menu_state.current_option_i][:on_select].call(args)
Sound.play(args, :select)
end
if Input.pressed?(args, :secondary) && !Scene.stack(args).empty?
Sound.play(args, :select)
Scene.pop(args)
end
end
# custom logic to reset this scene
def reset(args)
super
@menu_state.current_option_i = 0
@menu_state.hold_delay = 0
@first_render = nil
end
def text_for_setting_val(args, key)
val = args.state.setting[key]
case val
when true
text(:on)
when false
text(:off)
else
val
end
end
end