From 9c1866dbdb95da1c887a29eac1124f6cce53c7ef Mon Sep 17 00:00:00 2001 From: George FunBook Date: Tue, 16 Mar 2021 09:56:08 -0500 Subject: [PATCH] add rebind key prompt --- source/Controls.hx | 50 ++++++------ source/ui/AtlasText.hx | 1 + source/ui/ControlsMenu.hx | 165 +++++++++++++++++++++++++++++++++----- source/ui/MenuList.hx | 10 ++- source/ui/OptionsState.hx | 4 +- source/ui/Prompt.hx | 18 ++++- source/ui/TextMenuList.hx | 1 + 7 files changed, 200 insertions(+), 49 deletions(-) diff --git a/source/Controls.hx b/source/Controls.hx index c864cc918..6e49c5387 100644 --- a/source/Controls.hx +++ b/source/Controls.hx @@ -11,6 +11,30 @@ import flixel.input.gamepad.FlxGamepadButton; import flixel.input.gamepad.FlxGamepadInputID; import flixel.input.keyboard.FlxKey; +/** + * Since, in many cases multiple actions should use similar keys, we don't want the + * rebinding UI to list every action. ActionBinders are what the user percieves as + * an input so, for instance, they can't set jump-press and jump-release to different keys. + */ +enum Control +{ + NOTE_UP; + NOTE_LEFT; + NOTE_RIGHT; + NOTE_DOWN; + UI_UP; + UI_LEFT; + UI_RIGHT; + UI_DOWN; + RESET; + ACCEPT; + BACK; + PAUSE; + #if CAN_CHEAT + CHEAT; + #end +} + @:enum abstract Action(String) to String from String { @@ -53,30 +77,6 @@ enum Device Gamepad(id:Int); } -/** - * Since, in many cases multiple actions should use similar keys, we don't want the - * rebinding UI to list every action. ActionBinders are what the user percieves as - * an input so, for instance, they can't set jump-press and jump-release to different keys. - */ -enum Control -{ - UI_UP; - UI_LEFT; - UI_RIGHT; - UI_DOWN; - NOTE_UP; - NOTE_LEFT; - NOTE_RIGHT; - NOTE_DOWN; - RESET; - ACCEPT; - BACK; - PAUSE; - #if CAN_CHEAT - CHEAT; - #end -} - enum KeyboardScheme { Solo; @@ -450,7 +450,7 @@ class Controls extends FlxActionSet bindKeys(Control.NOTE_LEFT, [A, FlxKey.LEFT]); bindKeys(Control.NOTE_RIGHT, [D, FlxKey.RIGHT]); bindKeys(Control.ACCEPT, [Z, SPACE, ENTER]); - bindKeys(Control.BACK, [BACKSPACE, ESCAPE]); + bindKeys(Control.BACK, [X, BACKSPACE, ESCAPE]); bindKeys(Control.PAUSE, [P, ENTER, ESCAPE]); bindKeys(Control.RESET, [R]); case Duo(true): diff --git a/source/ui/AtlasText.hx b/source/ui/AtlasText.hx index 4c5f653b2..6e3f8fa48 100644 --- a/source/ui/AtlasText.hx +++ b/source/ui/AtlasText.hx @@ -148,6 +148,7 @@ class AtlasText extends FlxTypedSpriteGroup } charSprite.x = xPos; charSprite.y = yPos + maxHeight - charSprite.height; + charSprite.alpha = 1;//gets multiplied when added add(charSprite); xPos += charSprite.width; diff --git a/source/ui/ControlsMenu.hx b/source/ui/ControlsMenu.hx index 90d3909bc..04d7fee4f 100644 --- a/source/ui/ControlsMenu.hx +++ b/source/ui/ControlsMenu.hx @@ -1,5 +1,6 @@ package ui; +import ui.MenuList; import flixel.FlxG; import flixel.FlxCamera; import flixel.FlxObject; @@ -13,9 +14,23 @@ import ui.TextMenuList; class ControlsMenu extends ui.OptionsState.Page { - var controlGrid:TextMenuList; + static var controlList = Control.createAll(); + /* + * Defines groups of controls that cannot share inputs, like left and right. Say, if ACCEPT is Z, Back is X, + * if the player sets Back to Z it also set ACCEPT to X. This prevents the player from setting the controls in + * a way the prevents them from changing more controls or exiting the menu. + */ + static var controlGroups:Array> = + [ [ NOTE_UP, NOTE_DOWN, NOTE_LEFT, NOTE_RIGHT ] + , [ UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK ] + ]; + + var itemGroups:Array> = [for (i in 0...controlGroups.length) []]; + + var controlGrid:MenuTypedList; var labels:FlxTypedGroup; var menuCamera:FlxCamera; + var prompt:Prompt; public function new() { @@ -29,54 +44,135 @@ class ControlsMenu extends ui.OptionsState.Page FlxCamera.defaultCameras.remove(menuCamera); } menuCamera.bgColor = 0x0; + camera = menuCamera; add(labels = new FlxTypedGroup()); - labels.camera = menuCamera; - - add(controlGrid = new TextMenuList(Columns(2))); - controlGrid.camera = menuCamera; + add(controlGrid = new MenuTypedList(Columns(2))); // FlxG.debugger.drawDebug = true; - var controlList = Control.createAll(); + var y = 30; + var spacer = 70; + var currentHeader:String = null; + // list order is determined by enum order for (i in 0...controlList.length) { var control = controlList[i]; var name = control.getName(); - var y = (70 * i) + 30; - var label = labels.add(new BoldText(0, y, name)); - label.x += 250; + if (currentHeader != "UI_" && name.indexOf("UI_") == 0) + { + currentHeader = "UI_"; + labels.add(new BoldText(0, y, "UI")).screenCenter(X); + y += spacer; + } + else if (currentHeader != "NOTE_" && name.indexOf("NOTE_") == 0) + { + currentHeader = "NOTE_"; + labels.add(new BoldText(0, y, "NOTES")).screenCenter(X); + y += spacer; + } + + if (currentHeader != null && name.indexOf(currentHeader) == 0) + name = name.substr(currentHeader.length); + + var label = labels.add(new BoldText(250, y, name)); createItem(label.x + 400, y, control, 0); createItem(label.x + 600, y, control, 1); + y += spacer; } + trace(itemGroups.map((group)->group.map((item)->item.label.text))); + var selected = controlGrid.members[0]; var camFollow = new FlxObject(FlxG.width / 2, selected.y, 70, 70); menuCamera.follow(camFollow, null, 0.06); var margin = 100; menuCamera.deadzone.set(0, margin, menuCamera.width, menuCamera.height - margin * 2); controlGrid.onChange.add(function (selected) camFollow.y = selected.y); + + prompt = new Prompt("Press any key to rebind\n\n\n\n Escape to cancel", None); + prompt.create(); + prompt.createBgFromMargin(); + prompt.back.scrollFactor.set(0, 0); + prompt.exists = false; + add(prompt); } function createItem(x = 0.0, y = 0.0, control:Control, index:Int) { - var list = PlayerSettings.player1.controls.getInputsFor(control, Keys); - var name = "---"; - if (list.length > index) + var item = new InputItem(x, y, control, index, onSelect); + for (i in 0...controlGroups.length) { - if (list[index] == FlxKey.ESCAPE) - return createItem(x, y, control, 2); - - name = InputFormatter.format(list[index], Keys); + if (controlGroups[i].contains(control)) + itemGroups[i].push(item); } - trace(control.getName() + " " + index + ": " + name); - return controlGrid.createItem(x, y, name, Default, onSelect.bind(name, control, index)); + return controlGrid.addItem(item.name, item); } - function onSelect(name:String, control:Control, index:Int):Void + function onSelect():Void { controlGrid.enabled = false; - // var prompt = new Prompt(); + canExit = false; + prompt.exists = true; + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + if (prompt.exists) + { + var key = FlxG.keys.firstJustPressed(); + if (key != NONE) + { + if (key != ESCAPE) + onKeySelect(key); + closePrompt(); + } + } + } + + function onKeySelect(key:Int) + { + var item = controlGrid.selectedItem; + for (group in itemGroups) + { + if (group.contains(item)) + { + for (otherItem in group) + { + // Check if items in the same group have the new input + if (otherItem != item && otherItem.input == key) + { + // replace that input with this items old input. + PlayerSettings.player1.controls.replaceBinding(otherItem.control, Keys, item.input, otherItem.input); + // Don't use resetItem() since items share names/labels + otherItem.input = item.input; + otherItem.label.text = item.label.text; + } + } + } + } + + PlayerSettings.player1.controls.replaceBinding(item.control, Keys, key, item.input); + // Don't use resetItem() since items share names/labels + item.input = key; + item.label.text = item.getLabel(key); + } + + function closePrompt() + { + controlGrid.enabled = true; + canExit = true; + prompt.exists = false; + } + + override function destroy() + { + super.destroy(); + + if (FlxG.cameras.list.contains(menuCamera)) + FlxG.cameras.remove(menuCamera); } override function set_enabled(value:Bool) @@ -84,4 +180,33 @@ class ControlsMenu extends ui.OptionsState.Page controlGrid.enabled = value; return super.set_enabled(value); } +} + +class InputItem extends TextMenuItem +{ + public var control:Control; + public var input:Int = -1; + + public function new (x = 0.0, y = 0.0, control, index, ?callback) + { + this.control = control; + + var list = PlayerSettings.player1.controls.getInputsFor(control, Keys); + if (list.length > index) + { + if (list[index] != FlxKey.ESCAPE) + input = list[index]; + else if (list.length > 2) + // Escape isn't mappable, show a third option, instead. + input = list[2]; + } + + ; + super(x, y, getLabel(input), Default, callback); + } + + public function getLabel(input:Int) + { + return input == -1 ? "---" : InputFormatter.format(input, Keys); + } } \ No newline at end of file diff --git a/source/ui/MenuList.hx b/source/ui/MenuList.hx index f4df6066f..2dfb84144 100644 --- a/source/ui/MenuList.hx +++ b/source/ui/MenuList.hx @@ -10,6 +10,7 @@ import flixel.util.FlxSignal; class MenuTypedList extends FlxTypedGroup { public var selectedIndex(default, null) = 0; + public var selectedItem(get, never):T; /** Called when a new item is highlighted */ public var onChange(default, null) = new FlxTypedSignalVoid>(); /** Called when an item is accepted */ @@ -189,6 +190,11 @@ class MenuTypedList extends FlxTypedGroup onChange.removeAll(); onAcceptPress.removeAll(); } + + inline function get_selectedItem():T + { + return members[selectedIndex]; + } } class MenuItem extends FlxSprite @@ -221,8 +227,8 @@ class MenuItem extends FlxSprite /** * Calls setData and resets/redraws the state of the item - * @param name - * @param callback + * @param name the label. + * @param callback Unchanged if null. */ public function setItem(name:String, ?callback:Void->Void) { diff --git a/source/ui/OptionsState.hx b/source/ui/OptionsState.hx index 6801b4270..0e4fae95c 100644 --- a/source/ui/OptionsState.hx +++ b/source/ui/OptionsState.hx @@ -25,6 +25,7 @@ class OptionsState extends MusicBeatState menuBG.setGraphicSize(Std.int(menuBG.width * 1.1)); menuBG.updateHitbox(); menuBG.screenCenter(); + menuBG.scrollFactor.set(0, 0); add(menuBG); var options = addPage(Options, new OptionsMenu(false)); @@ -94,6 +95,7 @@ class Page extends FlxGroup public var onExit(default, null) = new FlxSignal(); public var enabled(default, set) = true; + public var canExit = true; var controls(get, never):Controls; inline function get_controls() return PlayerSettings.player1.controls; @@ -120,7 +122,7 @@ class Page extends FlxGroup function updateEnabled(elapsed:Float) { - if (controls.BACK) + if (canExit && controls.BACK) exit(); } diff --git a/source/ui/Prompt.hx b/source/ui/Prompt.hx index ccac5d576..0ef80007d 100644 --- a/source/ui/Prompt.hx +++ b/source/ui/Prompt.hx @@ -17,13 +17,14 @@ class Prompt extends flixel.FlxSubState public var onNo:Void->Void; public var buttons:TextMenuList; public var field:AtlasText; + public var back:FlxSprite; var style:ButtonStyle; public function new (text:String, style:ButtonStyle = Ok) { this.style = style; - super(0xA0000000); + super(0x80000000); buttons = new TextMenuList(Horizontal); @@ -43,6 +44,21 @@ class Prompt extends flixel.FlxSubState add(buttons); } + public function createBg(width:Int, height:Int, color = 0xFF808080) + { + back = new FlxSprite(); + back.makeGraphic(width, height, color, false, "prompt-bg"); + back.screenCenter(XY); + add(back); + members.unshift(members.pop());// bring to front + } + + + public function createBgFromMargin(margin = MARGIN, color = 0xFF808080) + { + createBg(Std.int(FlxG.width - margin * 2), Std.int(FlxG.height - margin * 2), color); + } + public function setButtons(style:ButtonStyle) { if (this.style != style) diff --git a/source/ui/TextMenuList.hx b/source/ui/TextMenuList.hx index d3134a5c7..b7a90afa5 100644 --- a/source/ui/TextMenuList.hx +++ b/source/ui/TextMenuList.hx @@ -39,6 +39,7 @@ class TextTypedMenuItem extends MenuTypedItem if (label != null) { label.text = name; + label.alpha = alpha; width = label.width; height = label.height; }