Class: Gemba::AppController

Inherits:
Object
  • Object
show all
Includes:
Gemba, BusEmitter, Locale::Translatable
Defined in:
lib/gemba/app_controller.rb

Overview

Application controller — the brain of the app.

Owns menus, hotkeys, modals, config, rom library, input maps, frame lifecycle, and mode tracking. MainWindow is a pure Tk shell that this controller drives.

This is what CLI instantiates.

Constant Summary collapse

DEFAULT_SCALE =
3
EVENT_LOOP_FAST_MS =
1
EVENT_LOOP_IDLE_MS =
50
GAMEPAD_PROBE_MS =
2000
GAMEPAD_LISTEN_MS =
50
{
  settings: 'menu.settings',
  picker: 'menu.save_states',
  rom_info: 'menu.rom_info',
  replay_player: 'replay.replay_player',
}.freeze

Constants included from Gemba

ASSETS_DIR, GBA_BIOS_CHECKSUM, GBA_BTN_BITS, GBA_DS_BIOS_CHECKSUM, KEY_A, KEY_B, KEY_DOWN, KEY_L, KEY_LEFT, KEY_R, KEY_RIGHT, KEY_SELECT, KEY_START, KEY_UP, VERSION

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Gemba

count_changed_pixels, gba_bios_checksum, load_locale, log, open_directory, toast_background, xor_delta

Constructor Details

#initialize(rom_path = nil, sound: true, fullscreen: false, frames: nil) ⇒ AppController

Returns a new instance of AppController.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/gemba/app_controller.rb', line 34

def initialize(rom_path = nil, sound: true, fullscreen: false, frames: nil)
  @window = MainWindow.new
  @app = @window.app
  @frame_stack = @window.frame_stack

  Gemba.bus = EventBus.new

  @sound = sound
  @config = Gemba.user_config
  # Config may have been created (and subscribed to the bus) before this
  # point — e.g. the CLI calls Gemba.user_config before AppController runs.
  # Re-subscribe now that the real bus is in place so bus events like
  # :rom_loaded actually reach it (fixes recent ROMs not updating).
  @config.resubscribe
  @scale = @config.scale
  @fullscreen = fullscreen
  @frame_limit = frames
  @platform = Platform.default
  @initial_rom = rom_path
  @running = true
  @rom_path = nil
  @gamepad = nil
  @rom_library = RomLibrary.new

  @kb_map = KeyboardMap.new(@config)
  @gp_map = GamepadMap.new(@config)
  @keyboard = VirtualKeyboard.new
  @kb_map.device = @keyboard
  @hotkeys = HotkeyMap.new(@config)

  check_writable_dirs

  @window.set_timer_speed(EVENT_LOOP_IDLE_MS)
  @window.set_geometry(@platform.width * @scale, @platform.height * @scale)
  @window.set_title("gemba")

  build_menu

  @modal_stack = ModalStack.new(
    on_enter: method(:modal_entered),
    on_exit:  method(:modal_exited),
    on_focus_change: method(:modal_focus_changed),
  )

  dismiss = proc { @modal_stack.pop }

  @rom_info_window = RomInfoWindow.new(@app, callbacks: {
    on_dismiss: dismiss, on_close: dismiss,
  })
  @state_picker = SaveStatePicker.new(@app, callbacks: {
    on_dismiss: dismiss, on_close: dismiss,
  })
  @settings_window = SettingsWindow.new(@app, tip_dismiss_ms: @config.tip_dismiss_ms, callbacks: {
    on_validate_hotkey:     method(:validate_hotkey),
    on_validate_kb_mapping: method(:validate_kb_mapping),
    on_dismiss: dismiss, on_close: dismiss,
  })

  @settings_window.refresh_gamepad(@kb_map.labels, @kb_map.dead_zone_pct)
  @settings_window.refresh_hotkeys(@hotkeys.labels)
  push_settings_to_ui

  setup_achievement_backend

  boxart_backend = BoxartFetcher::LibretroBackend.new
  @boxart_fetcher = BoxartFetcher.new(app: @app, cache_dir: Config.boxart_dir, backend: boxart_backend)
  @rom_overrides  = RomOverrides.new
  @game_picker = GamePickerFrame.new(app: @app, rom_library: @rom_library,
                                     boxart_fetcher: @boxart_fetcher, rom_overrides: @rom_overrides)
  @frame_stack.push(:picker, @game_picker)
  @window.set_geometry(GamePickerFrame::PICKER_DEFAULT_W, GamePickerFrame::PICKER_DEFAULT_H)
  @window.set_minsize(GamePickerFrame::PICKER_MIN_W, GamePickerFrame::PICKER_MIN_H)
  apply_frame_aspect(@game_picker)

  @help_auto_paused = false
  @cursor_hidden    = false
  @cursor_hide_job  = nil

  setup_drop_target
  setup_global_hotkeys
  setup_bus_subscriptions
  setup_cursor_autohide
end

Instance Attribute Details

#appObject (readonly)

Returns the value of attribute app.



32
33
34
# File 'lib/gemba/app_controller.rb', line 32

def app
  @app
end

#configObject (readonly)

Returns the value of attribute config.



32
33
34
# File 'lib/gemba/app_controller.rb', line 32

def config
  @config
end

#gp_mapObject (readonly)

Returns the value of attribute gp_map.



32
33
34
# File 'lib/gemba/app_controller.rb', line 32

def gp_map
  @gp_map
end

#kb_mapObject (readonly)

Returns the value of attribute kb_map.



32
33
34
# File 'lib/gemba/app_controller.rb', line 32

def kb_map
  @kb_map
end

#runningObject

Returns the value of attribute running.



32
33
34
# File 'lib/gemba/app_controller.rb', line 32

def running
  @running
end

#scaleObject (readonly)

Returns the value of attribute scale.



32
33
34
# File 'lib/gemba/app_controller.rb', line 32

def scale
  @scale
end

#settings_windowObject (readonly)

Returns the value of attribute settings_window.



32
33
34
# File 'lib/gemba/app_controller.rb', line 32

def settings_window
  @settings_window
end

Instance Method Details

#current_viewObject



129
# File 'lib/gemba/app_controller.rb', line 129

def current_view = @frame_stack.current

#disable_confirmations!Object



139
140
141
# File 'lib/gemba/app_controller.rb', line 139

def disable_confirmations!
  @disable_confirmations = true
end

#frameObject

Current active frame (for tests and external access)



128
# File 'lib/gemba/app_controller.rb', line 128

def frame = @frame_stack.current_frame

#ready?Boolean

Returns:

  • (Boolean)


125
# File 'lib/gemba/app_controller.rb', line 125

def ready? = @initial_rom ? frame&.rom_loaded? : true

#runObject



118
119
120
121
122
123
# File 'lib/gemba/app_controller.rb', line 118

def run
  @app.after(1) { load_rom(@initial_rom) } if @initial_rom
  @app.mainloop
ensure
  cleanup
end