# 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).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