mirror of
https://github.com/mattrberry/crab.git
synced 2025-02-04 08:46:04 +01:00
the start to a proper config editor
This commit is contained in:
parent
5e9720f9b0
commit
83bb9d29e3
6 changed files with 215 additions and 99 deletions
|
@ -54,10 +54,16 @@ class Config
|
|||
|
||||
class GBA
|
||||
include YAML::Serializable
|
||||
property bios : String = "bios.bin"
|
||||
property bios : String?
|
||||
|
||||
DEFAULT_BIOS = Path["#{__DIR__}/../../../bios.bin"].normalize
|
||||
|
||||
def initialize
|
||||
end
|
||||
|
||||
def bios : String
|
||||
@bios || DEFAULT_BIOS.to_s
|
||||
end
|
||||
end
|
||||
|
||||
class GBC
|
||||
|
|
|
@ -71,7 +71,7 @@ class SDLOpenGLImGuiFrontend < Frontend
|
|||
pause(true) if stubbed?
|
||||
|
||||
@file_explorer = ImGui::FileExplorer.new @config
|
||||
@keybindings = ImGui::Keybindings.new @config
|
||||
@config_editor = ConfigEditor.new @config, @file_explorer
|
||||
end
|
||||
|
||||
def run : NoReturn
|
||||
|
@ -119,31 +119,21 @@ class SDLOpenGLImGuiFrontend < Frontend
|
|||
pause(false)
|
||||
end
|
||||
|
||||
private def load_new_bios(bios : String) : Nil
|
||||
if @controller.class == GBController
|
||||
@config.gbc.bios = bios
|
||||
elsif @controller.class == GBAController
|
||||
@config.gba.bios = bios
|
||||
else
|
||||
abort "Internal error: Cannot set bios #{bios} for controller #{@controller}"
|
||||
end
|
||||
@config.commit
|
||||
end
|
||||
|
||||
private def handle_input : Nil
|
||||
while event = SDL::Event.poll
|
||||
ImGui::SDL2.process_event(event)
|
||||
case event
|
||||
when SDL::Event::Keyboard
|
||||
if @keybindings.wants_input?
|
||||
@keybindings.key_released(event.sym) unless event.pressed? # pass on key release
|
||||
next if @io.want_capture_keyboard # let ImGui handle keyboard input when focused
|
||||
if @config_editor.keybindings.wants_input?
|
||||
@config_editor.keybindings.key_released(event.sym) unless event.pressed? # pass on key release
|
||||
elsif event.mod.includes?(LibSDL::Keymod::LCTRL)
|
||||
case event.sym
|
||||
when LibSDL::Keycode::P then pause(!@pause) unless event.pressed?
|
||||
when LibSDL::Keycode::F then @window.fullscreen = (@fullscreen = !@fullscreen) unless event.pressed?
|
||||
when LibSDL::Keycode::Q then exit
|
||||
end
|
||||
elsif input = @keybindings[event.sym]?
|
||||
elsif input = @config_editor.keybindings[event.sym]?
|
||||
@controller.handle_input(input, event.pressed?)
|
||||
elsif event.sym == LibSDL::Keycode::TAB
|
||||
@controller.toggle_sync if event.pressed?
|
||||
|
@ -193,8 +183,7 @@ class SDLOpenGLImGuiFrontend < Frontend
|
|||
private def show_menu_bar? : Bool
|
||||
window_focused = LibSDL.get_mouse_focus == @window.to_unsafe
|
||||
mouse_timed_out = LibSDL.get_ticks - @last_mouse_motion > 3000 # 3 second timeout
|
||||
dialog_open = @file_explorer.open? || @keybindings.open?
|
||||
res = stubbed? || (window_focused && !mouse_timed_out) || dialog_open
|
||||
res = stubbed? || (window_focused && !mouse_timed_out)
|
||||
LibSDL.show_cursor(res)
|
||||
res
|
||||
end
|
||||
|
@ -206,14 +195,11 @@ class SDLOpenGLImGuiFrontend < Frontend
|
|||
|
||||
overlay_height = 10.0
|
||||
open_rom_selection = false
|
||||
open_bios_selection = false
|
||||
open_keybindings = false
|
||||
|
||||
if show_menu_bar?
|
||||
ImGui.main_menu_bar do
|
||||
ImGui.menu "File" do
|
||||
open_rom_selection = ImGui.menu_item "Open ROM"
|
||||
open_bios_selection = ImGui.menu_item "Select BIOS" unless stubbed?
|
||||
ImGui.menu "Recent", @config.recents.size > 0 do
|
||||
@config.recents.each do |recent|
|
||||
load_new_rom(recent) if ImGui.menu_item recent
|
||||
|
@ -225,7 +211,7 @@ class SDLOpenGLImGuiFrontend < Frontend
|
|||
end
|
||||
end
|
||||
ImGui.separator
|
||||
open_keybindings = ImGui.menu_item "Keybindings"
|
||||
@config_editor.open = true if ImGui.menu_item "Settings"
|
||||
ImGui.separator
|
||||
exit if ImGui.menu_item "Exit", "Ctrl+Q"
|
||||
end
|
||||
|
@ -265,11 +251,8 @@ class SDLOpenGLImGuiFrontend < Frontend
|
|||
@file_explorer.render("ROM", open_rom_selection, ROM_EXTENSIONS) do |path|
|
||||
load_new_rom(path.to_s)
|
||||
end
|
||||
@file_explorer.render("BIOS", open_bios_selection) do |path|
|
||||
load_new_bios(path.to_s)
|
||||
end
|
||||
|
||||
@keybindings.render(open_keybindings)
|
||||
@config_editor.render
|
||||
|
||||
if @enable_overlay
|
||||
ImGui.set_next_window_pos(ImGui::ImVec2.new 10, overlay_height)
|
||||
|
|
80
src/crab/common/frontend/widgets/bios_selection.cr
Normal file
80
src/crab/common/frontend/widgets/bios_selection.cr
Normal file
|
@ -0,0 +1,80 @@
|
|||
require "./resolvable"
|
||||
|
||||
class BiosSelection < Resolvable
|
||||
RED_TEXT_COL = ImGui::ImVec4.new(1, 0.5, 0.5, 1)
|
||||
|
||||
@config : Config
|
||||
@file_explorer : ImGui::FileExplorer
|
||||
|
||||
@gbc_bios_text_buffer = ImGui::TextBuffer.new(128)
|
||||
@gba_bios_text_buffer = ImGui::TextBuffer.new(128)
|
||||
@gbc_bios_text_buffer_valid = false
|
||||
@gba_bios_text_buffer_valid = false
|
||||
@run_bios : Bool = false # initialized on reset
|
||||
|
||||
def initialize(@config : Config, @file_explorer : ImGui::FileExplorer)
|
||||
end
|
||||
|
||||
def render : Nil
|
||||
ImGui.text("GBC BIOS File:")
|
||||
ImGui.same_line
|
||||
gbc_bios_text_buffer_valid = @gbc_bios_text_buffer_valid
|
||||
ImGui.push_style_color(ImGui::ImGuiCol::Text, RED_TEXT_COL) unless gbc_bios_text_buffer_valid
|
||||
ImGui.input_text_with_hint("##gbc_bios", "optional", @gbc_bios_text_buffer, ImGui::ImGuiInputTextFlags::CallbackAlways) do
|
||||
@gbc_bios_text_buffer_valid = @gbc_bios_text_buffer.bytesize == 0 || File.file?(@gbc_bios_text_buffer.to_s)
|
||||
0 # allow input to proceed
|
||||
end
|
||||
ImGui.pop_style_color unless gbc_bios_text_buffer_valid
|
||||
ImGui.same_line
|
||||
gbc_bios_browse = ImGui.button("Browse##gbc_bios")
|
||||
|
||||
ImGui.text("GBA BIOS File:")
|
||||
ImGui.same_line
|
||||
gba_bios_text_buffer_valid = @gba_bios_text_buffer_valid
|
||||
ImGui.push_style_color(ImGui::ImGuiCol::Text, RED_TEXT_COL) unless gba_bios_text_buffer_valid
|
||||
ImGui.input_text_with_hint("##gba_bios", "optional", @gba_bios_text_buffer, ImGui::ImGuiInputTextFlags::CallbackAlways) do |data|
|
||||
@gba_bios_text_buffer_valid = @gba_bios_text_buffer.bytesize == 0 || File.file?(@gba_bios_text_buffer.to_s)
|
||||
0 # allow input to proceed
|
||||
end
|
||||
ImGui.pop_style_color unless gba_bios_text_buffer_valid
|
||||
ImGui.same_line
|
||||
gba_bios_browse = ImGui.button("Browse##gba_bios")
|
||||
|
||||
ImGui.indent(106) # align with text boxes above
|
||||
ImGui.checkbox("Run BIOS intro", pointerof(@run_bios))
|
||||
ImGui.unindent(106)
|
||||
|
||||
@file_explorer.render("GBC BIOS", gbc_bios_browse) do |path|
|
||||
@gbc_bios_text_buffer.clear
|
||||
@gbc_bios_text_buffer.write(path.to_s.to_slice)
|
||||
@gbc_bios_text_buffer_valid = @gbc_bios_text_buffer.bytesize == 0 || File.file?(@gbc_bios_text_buffer.to_s)
|
||||
end
|
||||
@file_explorer.render("GBA BIOS", gba_bios_browse) do |path|
|
||||
@gba_bios_text_buffer.clear
|
||||
@gba_bios_text_buffer.write(path.to_s.to_slice)
|
||||
@gba_bios_text_buffer_valid = @gba_bios_text_buffer.bytesize == 0 || File.file?(@gba_bios_text_buffer.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def reset : Nil
|
||||
@gbc_bios_text_buffer.clear
|
||||
@gba_bios_text_buffer.clear
|
||||
if gbc_bios = @config.gbc.bios
|
||||
@gbc_bios_text_buffer.write(gbc_bios.to_slice)
|
||||
@gbc_bios_text_buffer_valid = File.file?(@gbc_bios_text_buffer.to_s)
|
||||
end
|
||||
if gba_bios = @config.gba.bios
|
||||
@gba_bios_text_buffer.write(gba_bios.to_slice) if Path[gba_bios].normalize != Path[Config::GBA::DEFAULT_BIOS].normalize
|
||||
@gba_bios_text_buffer_valid = File.file?(@gba_bios_text_buffer.to_s)
|
||||
end
|
||||
@run_bios = @config.run_bios
|
||||
end
|
||||
|
||||
def apply : Nil
|
||||
@config.gbc.bios = nil
|
||||
@config.gbc.bios = @gbc_bios_text_buffer.to_s if @gbc_bios_text_buffer.bytesize != 0
|
||||
@config.gba.bios = nil
|
||||
@config.gba.bios = @gba_bios_text_buffer.to_s if @gba_bios_text_buffer.bytesize != 0
|
||||
@config.run_bios = @run_bios
|
||||
end
|
||||
end
|
56
src/crab/common/frontend/widgets/config_editor.cr
Normal file
56
src/crab/common/frontend/widgets/config_editor.cr
Normal file
|
@ -0,0 +1,56 @@
|
|||
class ConfigEditor
|
||||
getter keybindings : Keybindings
|
||||
property open : Bool = false
|
||||
@previously_open : Bool = false
|
||||
|
||||
def initialize(@config : Config, @file_explorer : ImGui::FileExplorer)
|
||||
@bios_selection = BiosSelection.new(@config, @file_explorer)
|
||||
@keybindings = Keybindings.new @config
|
||||
end
|
||||
|
||||
def render : Nil
|
||||
reset if @open && !@previously_open
|
||||
@previously_open = @open
|
||||
|
||||
if @open
|
||||
ImGui.begin("Settings", pointerof(@open), flags: ImGui::ImGuiWindowFlags::AlwaysAutoResize)
|
||||
apply if ImGui.button("Apply")
|
||||
ImGui.same_line
|
||||
reset if ImGui.button("Revert")
|
||||
ImGui.same_line
|
||||
if ImGui.button("OK")
|
||||
apply
|
||||
@open = false
|
||||
end
|
||||
|
||||
ImGui.separator
|
||||
|
||||
ImGui.tab_bar("SettingsTabBar") do
|
||||
render_resolvable_tab(@bios_selection, "BIOS")
|
||||
render_resolvable_tab(@keybindings, "Keybindings")
|
||||
end
|
||||
ImGui.end
|
||||
end
|
||||
end
|
||||
|
||||
private def reset : Nil
|
||||
@bios_selection.reset
|
||||
@keybindings.reset
|
||||
end
|
||||
|
||||
private def apply : Nil
|
||||
@bios_selection.apply
|
||||
@keybindings.apply
|
||||
@config.commit
|
||||
end
|
||||
|
||||
# Render a Resolvable in a tab item and set its `vislble` property.
|
||||
private def render_resolvable_tab(res : Resolvable, name : String) : Nil
|
||||
if res.visible = ImGui.begin_tab_item(name)
|
||||
ImGui.group do
|
||||
res.render
|
||||
end
|
||||
ImGui.end_tab_item
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,82 +1,60 @@
|
|||
module ImGui
|
||||
class Keybindings
|
||||
POPUP_NAME = "Keybindings"
|
||||
BUTTON_SIZE = ImGui::ImVec2.new(32, 0)
|
||||
require "./resolvable"
|
||||
|
||||
@config : Config
|
||||
@open = false
|
||||
@selection : Input? = nil
|
||||
@editing_keycodes : Hash(LibSDL::Keycode, Input) = {} of LibSDL::Keycode => Input
|
||||
class Keybindings < Resolvable
|
||||
BUTTON_SIZE = ImGui::ImVec2.new(32, 0)
|
||||
|
||||
delegate :[]?, to: @config.keybindings
|
||||
@config : Config
|
||||
@selection : Input? = nil
|
||||
@editing_keycodes : Hash(LibSDL::Keycode, Input) = {} of LibSDL::Keycode => Input
|
||||
|
||||
def initialize(@config : Config)
|
||||
overwrite_hash(@editing_keycodes, @config.keybindings)
|
||||
end
|
||||
def initialize(@config : Config)
|
||||
@hovered_button_color = ImGui.get_style_color_vec4(ImGui::ImGuiCol::ButtonHovered)
|
||||
end
|
||||
|
||||
def open? : Bool
|
||||
@open
|
||||
end
|
||||
delegate :[]?, to: @config.keybindings
|
||||
|
||||
def wants_input? : Bool
|
||||
@open && !@selection.nil?
|
||||
end
|
||||
def wants_input? : Bool
|
||||
@visible && !@selection.nil?
|
||||
end
|
||||
|
||||
def key_released(keycode : LibSDL::Keycode) : Nil
|
||||
if selection = @selection
|
||||
@editing_keycodes.reject!(@editing_keycodes.key_for?(selection))
|
||||
@editing_keycodes[keycode] = selection
|
||||
@selection = Input.from_value?(selection.value + 1)
|
||||
else
|
||||
puts "Something went wrong when setting keybinding.."
|
||||
end
|
||||
end
|
||||
|
||||
def render(open_popup : Bool) : Nil
|
||||
@open ||= open_popup
|
||||
if open_popup
|
||||
overwrite_hash(@editing_keycodes, @config.keybindings)
|
||||
ImGui.open_popup(POPUP_NAME)
|
||||
end
|
||||
center = ImGui.get_main_viewport.get_center
|
||||
ImGui.set_next_window_pos(center, ImGui::ImGuiCond::Appearing, ImGui::ImVec2.new(0.5, 0.5))
|
||||
hovered_button_color = ImGui.get_style_color_vec4(ImGui::ImGuiCol::ButtonHovered)
|
||||
ImGui.popup_modal(POPUP_NAME, flags: ImGui::ImGuiWindowFlags::AlwaysAutoResize) do
|
||||
Input.each do |input|
|
||||
selected = @selection == input
|
||||
keycode = @editing_keycodes.key_for?(input)
|
||||
button_text = keycode ? String.new(LibSDL.get_key_name(keycode)) : ""
|
||||
x_pos = ImGui.get_window_content_region_max.x - BUTTON_SIZE.x
|
||||
ImGui.text(input.to_s)
|
||||
ImGui.same_line(x_pos)
|
||||
ImGui.push_style_color(ImGui::ImGuiCol::Button, hovered_button_color) if selected
|
||||
if ImGui.button(button_text, BUTTON_SIZE)
|
||||
@selection = input
|
||||
end
|
||||
ImGui.pop_style_color if selected
|
||||
end
|
||||
apply if ImGui.button "Apply"
|
||||
ImGui.same_line
|
||||
close if ImGui.button "Cancel"
|
||||
end
|
||||
end
|
||||
|
||||
private def overwrite_hash(to_hash : Hash(K, V), from_hash : Hash(K, V)) : Hash(K, V) forall K, V
|
||||
to_hash.clear
|
||||
from_hash.each { |key, val| to_hash[key] = val }
|
||||
to_hash
|
||||
end
|
||||
|
||||
private def apply : Nil
|
||||
overwrite_hash(@config.keybindings, @editing_keycodes)
|
||||
@config.commit
|
||||
close
|
||||
end
|
||||
|
||||
private def close : Nil
|
||||
@open = false
|
||||
@selection = nil
|
||||
ImGui.close_current_popup
|
||||
def key_released(keycode : LibSDL::Keycode) : Nil
|
||||
if selection = @selection
|
||||
@editing_keycodes.reject!(@editing_keycodes.key_for?(selection))
|
||||
@editing_keycodes[keycode] = selection
|
||||
@selection = Input.from_value?(selection.value + 1)
|
||||
else
|
||||
puts "Something went wrong when setting keybinding.."
|
||||
end
|
||||
end
|
||||
|
||||
def render : Nil
|
||||
Input.each do |input|
|
||||
selected = @selection == input
|
||||
keycode = @editing_keycodes.key_for?(input)
|
||||
button_text = keycode ? String.new(LibSDL.get_key_name(keycode)) : ""
|
||||
ImGui.push_style_color(ImGui::ImGuiCol::Button, @hovered_button_color) if selected
|
||||
if ImGui.button(button_text, BUTTON_SIZE)
|
||||
@selection = input
|
||||
end
|
||||
ImGui.pop_style_color if selected
|
||||
ImGui.same_line
|
||||
ImGui.text(input.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def reset : Nil
|
||||
@selection = nil
|
||||
overwrite_hash(@editing_keycodes, @config.keybindings)
|
||||
end
|
||||
|
||||
def apply : Nil
|
||||
overwrite_hash(@config.keybindings, @editing_keycodes)
|
||||
@selection = nil
|
||||
end
|
||||
|
||||
private def overwrite_hash(to_hash : Hash(K, V), from_hash : Hash(K, V)) : Hash(K, V) forall K, V
|
||||
to_hash.clear
|
||||
from_hash.each { |key, val| to_hash[key] = val }
|
||||
to_hash
|
||||
end
|
||||
end
|
||||
|
|
13
src/crab/common/frontend/widgets/resolvable.cr
Normal file
13
src/crab/common/frontend/widgets/resolvable.cr
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Represents a widget which stores temporary state that can be committed or
|
||||
# dropped on request.
|
||||
abstract class Resolvable
|
||||
# Whether the widget is visible.
|
||||
property visible : Bool = false
|
||||
|
||||
# Called once per frame for rendering the widget.
|
||||
abstract def render : Nil
|
||||
# Called to indicate the widget should be reset.
|
||||
abstract def reset : Nil
|
||||
# Called to indicate the selection should be written back to the config.
|
||||
abstract def apply : Nil
|
||||
end
|
Loading…
Add table
Reference in a new issue