From 3572cd9c478de0c6d03db851231e2292c2954c17 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Mon, 22 Mar 2021 07:48:52 -0500 Subject: [PATCH] more work on controls menu --- source/InputFormatter.hx | 91 +++++++++-------- source/ui/AtlasMenuList.hx | 4 +- source/ui/AtlasText.hx | 13 ++- source/ui/ControlsMenu.hx | 197 ++++++++++++++++++++++++++++++------- source/ui/MenuList.hx | 40 ++++++-- source/ui/TextMenuList.hx | 4 +- 6 files changed, 254 insertions(+), 95 deletions(-) diff --git a/source/InputFormatter.hx b/source/InputFormatter.hx index 2ace8b0ac..413910543 100644 --- a/source/InputFormatter.hx +++ b/source/InputFormatter.hx @@ -34,37 +34,37 @@ class InputFormatter case SEVEN : "7"; case EIGHT : "8"; case NINE : "9"; - case PAGEUP : "PgU"; - case PAGEDOWN : "PgD"; - case HOME : "Hm"; - case END : "End"; - case INSERT : "Ins"; - case ESCAPE : "Esc"; - case MINUS : "-"; - case PLUS : "+"; - case DELETE : "Del"; - case BACKSPACE : "Bck"; + case PAGEUP : "PgUp"; + case PAGEDOWN : "PgDown"; + // case HOME : "Hm"; + // case END : "End"; + // case INSERT : "Ins"; + // case ESCAPE : "Esc"; + // case MINUS : "-"; + // case PLUS : "+"; + // case DELETE : "Del"; + case BACKSPACE : "BckSpc"; case LBRACKET : "["; case RBRACKET : "]"; case BACKSLASH : "\\"; - case CAPSLOCK : "Cap"; + case CAPSLOCK : "Caps"; case SEMICOLON : ";"; case QUOTE : "'"; - case ENTER : "Ent"; - case SHIFT : "Shf"; + // case ENTER : "Ent"; + // case SHIFT : "Shf"; case COMMA : ","; case PERIOD : "."; case SLASH : "/"; case GRAVEACCENT : "`"; - case CONTROL : "Ctl"; + case CONTROL : "Ctrl"; case ALT : "Alt"; - case SPACE : "Spc"; - case UP : "Up"; - case DOWN : "Dn"; - case LEFT : "Lf"; - case RIGHT : "Rt"; - case TAB : "Tab"; - case PRINTSCREEN : "Prt"; + // case SPACE : "Spc"; + // case UP : "Up"; + // case DOWN : "Dn"; + // case LEFT : "Lf"; + // case RIGHT : "Rt"; + // case TAB : "Tab"; + case PRINTSCREEN : "PrtScrn"; case NUMPADZERO : "#0"; case NUMPADONE : "#1"; case NUMPADTWO : "#2"; @@ -79,7 +79,7 @@ class InputFormatter case NUMPADPLUS : "#+"; case NUMPADPERIOD : "#."; case NUMPADMULTIPLY: "#*"; - default: titleCaseTrim(FlxKey.toStringMap[id]); + default: titleCase(FlxKey.toStringMap[id]); } } @@ -98,39 +98,36 @@ class InputFormatter return switch (name == null ? "" : name.toLowerCase()) { case "": "[?]"; - case "square" : "[]"; - case "circle" : "()"; - case "triangle": "/\\"; - case "plus" : "+"; - case "minus" : "-"; - case "home" : "Hm"; - case "guide" : "Gd"; - case "back" : "Bk"; - case "select" : "Bk"; - case "start" : "St"; - case "left" : "Lf"; - case "right" : "Rt"; - case "down" : "Dn"; - case "up" : "Up"; + // case "square" : "[]"; + // case "circle" : "()"; + // case "triangle": "/\\"; + // case "plus" : "+"; + // case "minus" : "-"; + // case "home" : "Hm"; + // case "guide" : "Gd"; + // case "back" : "Bk"; + // case "select" : "Bk"; + // case "start" : "St"; + // case "left" : "Lf"; + // case "right" : "Rt"; + // case "down" : "Dn"; + // case "up" : "Up"; case dir if (dirReg.match(dir)): - dirReg.matched(1).toUpperCase() + "-" - + switch (dirReg.matched(2)) - { - case "left" : "L"; - case "right": "R"; - case "down" : "D"; - case "up" : "U"; - default: throw "Unreachable exaustiveness case"; - }; - case label: titleCaseTrim(label); + dirReg.matched(1).toUpperCase() + " " + titleCase(dirReg.matched(2)); + case label: titleCase(label); } } - inline static function titleCaseTrim(str:String, length = 3) + inline static function titleCaseTrim(str:String, length = 8) { return str.charAt(0).toUpperCase() + str.substr(1, length - 1).toLowerCase(); } + inline static function titleCase(str:String) + { + return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase(); + } + inline static public function parsePadName(name:String):ControllerName { return ControllerName.parseName(name); diff --git a/source/ui/AtlasMenuList.hx b/source/ui/AtlasMenuList.hx index e9572d07c..68232da5c 100644 --- a/source/ui/AtlasMenuList.hx +++ b/source/ui/AtlasMenuList.hx @@ -10,9 +10,9 @@ class AtlasMenuList extends MenuTypedList { public var atlas:FlxAtlasFrames; - public function new (atlas, navControls:NavControls = Vertical) + public function new (atlas, navControls:NavControls = Vertical, ?wrapMode) { - super(navControls); + super(navControls, wrapMode); if (Std.is(atlas, String)) this.atlas = Paths.getSparrowAtlas(cast atlas); diff --git a/source/ui/AtlasText.hx b/source/ui/AtlasText.hx index 6e3f8fa48..3ba3a746a 100644 --- a/source/ui/AtlasText.hx +++ b/source/ui/AtlasText.hx @@ -3,6 +3,7 @@ package ui; import flixel.FlxSprite; import flixel.group.FlxSpriteGroup; import flixel.graphics.frames.FlxAtlasFrames; +import flixel.util.FlxStringUtil; @:forward abstract BoldText(AtlasText) from AtlasText to AtlasText @@ -68,7 +69,7 @@ class AtlasText extends FlxTypedSpriteGroup if (value == "") return this.text; - appendTextCased(this.text); + appendTextCased(caseValue); return this.text; } @@ -157,6 +158,16 @@ class AtlasText extends FlxTypedSpriteGroup } } } + + override function toString() + { + return "InputItem, " + FlxStringUtil.getDebugString( + [ LabelValuePair.weak("x", x) + , LabelValuePair.weak("y", y) + , LabelValuePair.weak("text", text) + ] + ); + } } class AtlasChar extends FlxSprite diff --git a/source/ui/ControlsMenu.hx b/source/ui/ControlsMenu.hx index 034c18d35..cb471c2cf 100644 --- a/source/ui/ControlsMenu.hx +++ b/source/ui/ControlsMenu.hx @@ -1,6 +1,7 @@ package ui; -import ui.MenuList; +import flixel.input.actions.FlxActionInput; +import flixel.input.gamepad.FlxGamepadInputID; import flixel.FlxG; import flixel.FlxCamera; import flixel.FlxObject; @@ -10,10 +11,12 @@ import flixel.input.keyboard.FlxKey; import Controls; import ui.AtlasText; +import ui.MenuList; import ui.TextMenuList; class ControlsMenu extends ui.OptionsState.Page { + inline static public var COLUMNS = 2; 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, @@ -28,8 +31,14 @@ class ControlsMenu extends ui.OptionsState.Page var itemGroups:Array> = [for (i in 0...controlGroups.length) []]; var controlGrid:MenuTypedList; + var deviceList:TextMenuList; var menuCamera:FlxCamera; var prompt:Prompt; + var camFollow:FlxObject; + var labels:FlxTypedGroup; + + var currentDevice:Device = Keys; + var deviceListSelected = false; public function new() { @@ -45,16 +54,36 @@ class ControlsMenu extends ui.OptionsState.Page menuCamera.bgColor = 0x0; camera = menuCamera; - var labels = new FlxTypedGroup(); + labels = new FlxTypedGroup(); var headers = new FlxTypedGroup(); - add(controlGrid = new MenuTypedList(Columns(2))); + controlGrid = new MenuTypedList(Columns(COLUMNS), Vertical); add(labels); add(headers); add(controlGrid); + if (FlxG.gamepads.numActiveGamepads > 0) + { + var devicesBg = new FlxSprite(); + devicesBg.makeGraphic(FlxG.width, 100, 0xFF808080); + add(devicesBg); + deviceList = new TextMenuList(Horizontal, None); + add(deviceList); + deviceListSelected = true; + + var item; + + item = deviceList.createItem("Keyboard", Bold, selectDevice.bind(Keys)); + item.x = FlxG.width / 2 - item.width - 30; + item.y = (devicesBg.height - item.height) / 2; + + item = deviceList.createItem("Gamepad", Bold, selectDevice.bind(Gamepad(FlxG.gamepads.firstActive.id))); + item.x = FlxG.width / 2 + 30; + item.y = (devicesBg.height - item.height) / 2; + } + // FlxG.debugger.drawDebug = true; - var y = 30; + var y = deviceList == null ? 30 : 120; var spacer = 70; var currentHeader:String = null; // list order is determined by enum order @@ -78,25 +107,33 @@ class ControlsMenu extends ui.OptionsState.Page if (currentHeader != null && name.indexOf(currentHeader) == 0) name = name.substr(currentHeader.length); - var label = labels.add(new BoldText(250, y, name)); + var label = labels.add(new BoldText(150, y, name)); label.alpha = 0.6; - createItem(label.x + 400, y, control, 0); - createItem(label.x + 600, y, control, 1); + for (i in 0...COLUMNS) + createItem(label.x + 400 + i * 300, y, control, i); + y += spacer; } - labels.members[0].alpha = 1.0; - var selected = controlGrid.members[0]; - var camFollow = new FlxObject(FlxG.width / 2, selected.y, 70, 70); + camFollow = new FlxObject(FlxG.width / 2, 0, 70, 70); + if (deviceList != null) + { + camFollow.y = deviceList.selectedItem.y; + controlGrid.selectedItem.idle(); + } + else + camFollow.y = controlGrid.selectedItem.y; + menuCamera.follow(camFollow, null, 0.06); var margin = 100; menuCamera.deadzone.set(0, margin, menuCamera.width, menuCamera.height - margin * 2); + menuCamera.minScrollY = 0; controlGrid.onChange.add(function (selected) { camFollow.y = selected.y; labels.forEach((label)->label.alpha = 0.6); - labels.members[Std.int(controlGrid.selectedIndex / 2)].alpha = 1.0; + labels.members[Std.int(controlGrid.selectedIndex / COLUMNS)].alpha = 1.0; }); prompt = new Prompt("\nPress any key to rebind\n\n\n\n Escape to cancel", None); @@ -109,7 +146,7 @@ class ControlsMenu extends ui.OptionsState.Page function createItem(x = 0.0, y = 0.0, control:Control, index:Int) { - var item = new InputItem(x, y, control, index, onSelect); + var item = new InputItem(x, y, currentDevice, control, index, onSelect); for (i in 0...controlGroups.length) { if (controlGroups[i].contains(control)) @@ -126,36 +163,95 @@ class ControlsMenu extends ui.OptionsState.Page prompt.exists = true; } + function goToDeviceList() + { + controlGrid.selectedItem.idle(); + labels.members[Std.int(controlGrid.selectedIndex / COLUMNS)].alpha = 0.6; + controlGrid.enabled = false; + deviceList.enabled = true; + canExit = true; + camFollow.y = deviceList.selectedItem.y; + deviceListSelected = true; + } + + function selectDevice(device:Device) + { + currentDevice = device; + + for (item in controlGrid.members) + item.updateDevice(currentDevice); + + var inputName = device == Keys ? "key" : "button"; + var cancel = device == Keys ? "Escape" : "Back"; + prompt.setText('\nPress any $inputName to rebind\n\n\n\n $cancel to cancel'); + + controlGrid.selectedItem.select(); + labels.members[Std.int(controlGrid.selectedIndex / COLUMNS)].alpha = 1.0; + controlGrid.enabled = true; + deviceList.enabled = false; + deviceListSelected = false; + canExit = false; + } + override function update(elapsed:Float) { super.update(elapsed); + var controls = PlayerSettings.player1.controls; + if (enabled && deviceList != null && deviceListSelected == false && controls.BACK) + goToDeviceList(); + if (prompt.exists) { - var key = FlxG.keys.firstJustPressed(); - if (key != NONE) + switch (currentDevice) { - if (key != ESCAPE) - onKeySelect(key); - closePrompt(); + case Keys: + { + var key = FlxG.keys.firstJustPressed(); + if (key != NONE) + { + if (key != ESCAPE) + onInputSelect(key); + closePrompt(); + } + } + case Gamepad(id): + { + var button = FlxG.gamepads.getByID(id).firstJustPressedID(); + if (button != NONE) + { + if (button != BACK) + onInputSelect(button); + closePrompt(); + } + } } } } - function onKeySelect(key:Int) + function onInputSelect(input:Int) { var item = controlGrid.selectedItem; + + // check if that key is already set for this + var column0 = Math.floor(controlGrid.selectedIndex / 2) * 2; + for (i in 0...COLUMNS) + { + if (controlGrid.members[column0 + i].input == input) + return; + } + + // Check if items in the same group already have the new input 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) + if (otherItem != item && otherItem.input == input) { // replace that input with this items old input. - PlayerSettings.player1.controls.replaceBinding(otherItem.control, Keys, item.input, otherItem.input); + PlayerSettings.player1.controls.replaceBinding(otherItem.control, currentDevice, item.input, otherItem.input); // Don't use resetItem() since items share names/labels otherItem.input = item.input; otherItem.label.text = item.label.text; @@ -164,10 +260,10 @@ class ControlsMenu extends ui.OptionsState.Page } } - PlayerSettings.player1.controls.replaceBinding(item.control, Keys, key, item.input); + PlayerSettings.player1.controls.replaceBinding(item.control, currentDevice, input, item.input); // Don't use resetItem() since items share names/labels - item.input = key; - item.label.text = item.getLabel(key); + item.input = input; + item.label.text = item.getLabel(input); } function closePrompt() @@ -181,42 +277,75 @@ class ControlsMenu extends ui.OptionsState.Page { super.destroy(); + itemGroups = null; + if (FlxG.cameras.list.contains(menuCamera)) FlxG.cameras.remove(menuCamera); } override function set_enabled(value:Bool) { - controlGrid.enabled = value; + if (value == false) + { + controlGrid.enabled = false; + if (deviceList != null) + deviceList.enabled = false; + } + else + { + controlGrid.enabled = !deviceListSelected; + if (deviceList != null) + deviceList.enabled = deviceListSelected; + } return super.set_enabled(value); } } class InputItem extends TextMenuItem { + public var device(default, null):Device = Keys; public var control:Control; public var input:Int = -1; + public var index:Int = -1; - public function new (x = 0.0, y = 0.0, control, index, ?callback) + public function new (x = 0.0, y = 0.0, device, control, index, ?callback) { + this.device = device; this.control = control; + this.index = index; + this.input = getInput(); - var list = PlayerSettings.player1.controls.getInputsFor(control, Keys); + super(x, y, getLabel(input), Default, callback); + } + + public function updateDevice(device:Device) + { + if (this.device != device) + { + this.device = device; + input = getInput(); + label.text = getLabel(input); + } + } + + function getInput() + { + var list = PlayerSettings.player1.controls.getInputsFor(control, device); if (list.length > index) { - if (list[index] != FlxKey.ESCAPE) - input = list[index]; - else if (list.length > 2) + if (list[index] != FlxKey.ESCAPE || list[index] != FlxGamepadInputID.BACK) + return list[index]; + + if (list.length > ControlsMenu.COLUMNS) // Escape isn't mappable, show a third option, instead. - input = list[2]; + return list[ControlsMenu.COLUMNS]; } - ; - super(x, y, getLabel(input), Default, callback); + return -1; } public function getLabel(input:Int) { - return input == -1 ? "---" : InputFormatter.format(input, Keys); + return input == -1 ? "---" : InputFormatter.format(input, device); } } \ No newline at end of file diff --git a/source/ui/MenuList.hx b/source/ui/MenuList.hx index 2dfb84144..6d20d7d82 100644 --- a/source/ui/MenuList.hx +++ b/source/ui/MenuList.hx @@ -19,14 +19,26 @@ class MenuTypedList extends FlxTypedGroup public var navControls:NavControls; /** Set to false to disable nav control */ public var enabled:Bool = true; + /** */ + public var wrapMode:WrapMode = Both; var byName = new Map(); /** Set to true, internally to disable controls, without affecting vars like `enabled` */ var busy:Bool = false; - public function new (navControls:NavControls = Vertical) + public function new (navControls:NavControls = Vertical, ?wrapMode:WrapMode) { this.navControls = navControls; + + if (wrapMode != null) + this.wrapMode = wrapMode; + else + this.wrapMode = switch (navControls) + { + case Horizontal: Horizontal; + case Vertical: Vertical; + default: Both; + } super(); } @@ -64,14 +76,16 @@ class MenuTypedList extends FlxTypedGroup { var controls = PlayerSettings.player1.controls; + var wrapX = wrapMode.match(Horizontal | Both); + var wrapY = wrapMode.match(Vertical | Both); var newIndex = switch(navControls) { - case Vertical : navList(controls.UI_UP_P , controls.UI_DOWN_P); - case Horizontal : navList(controls.UI_LEFT_P, controls.UI_RIGHT_P); - case Both : navList(controls.UI_LEFT_P || controls.UI_UP_P, controls.UI_RIGHT_P || controls.UI_DOWN_P); + case Vertical : navList(controls.UI_UP_P , controls.UI_DOWN_P, wrapY); + case Horizontal : navList(controls.UI_LEFT_P, controls.UI_RIGHT_P, wrapX); + case Both : navList(controls.UI_LEFT_P || controls.UI_UP_P, controls.UI_RIGHT_P || controls.UI_DOWN_P, !wrapMode.match(None)); - case Columns(num): navGrid(num, controls.UI_LEFT_P, controls.UI_RIGHT_P, controls.UI_UP_P , controls.UI_DOWN_P ); - case Rows (num): navGrid(num, controls.UI_UP_P , controls.UI_DOWN_P , controls.UI_LEFT_P, controls.UI_RIGHT_P); + case Columns(num): navGrid(num, controls.UI_LEFT_P, controls.UI_RIGHT_P, wrapX, controls.UI_UP_P , controls.UI_DOWN_P , wrapY); + case Rows (num): navGrid(num, controls.UI_UP_P , controls.UI_DOWN_P , wrapY, controls.UI_LEFT_P, controls.UI_RIGHT_P, wrapX); } if (newIndex != selectedIndex) @@ -114,7 +128,7 @@ class MenuTypedList extends FlxTypedGroup * @param next * @param allowWrap */ - inline function navList(prev:Bool, next:Bool, allowWrap:Bool = true) + inline function navList(prev:Bool, next:Bool, allowWrap:Bool) { return navAxis(selectedIndex, length, prev, next, allowWrap); } @@ -128,7 +142,7 @@ class MenuTypedList extends FlxTypedGroup * @param next Whether the 'next' key is pressed along the variable-lengthed axis. eg: "down" in Column mode * @param allowWrap unused */ - function navGrid(latSize:Int, latPrev:Bool, latNext:Bool, prev:Bool, next:Bool, allowWrap:Bool = true):Int + function navGrid(latSize:Int, latPrev:Bool, latNext:Bool, latAllowWrap:Bool, prev:Bool, next:Bool, allowWrap:Bool):Int { // The grid lenth along the variable-length axis var size = Math.ceil(length / latSize); @@ -137,7 +151,7 @@ class MenuTypedList extends FlxTypedGroup // The selected position along the fixed axis var latIndex = selectedIndex % latSize; - latIndex = navAxis(latIndex, latSize, latPrev, latNext, allowWrap); + latIndex = navAxis(latIndex, latSize, latPrev, latNext, latAllowWrap); index = navAxis(index, size, prev, next, allowWrap); return Std.int(Math.min(length - 1, index * latSize + latIndex)); @@ -341,4 +355,12 @@ enum NavControls Both; Columns(num:Int); Rows(num:Int); +} + +enum WrapMode +{ + Horizontal; + Vertical; + Both; + None; } \ No newline at end of file diff --git a/source/ui/TextMenuList.hx b/source/ui/TextMenuList.hx index b7a90afa5..d1c617b17 100644 --- a/source/ui/TextMenuList.hx +++ b/source/ui/TextMenuList.hx @@ -5,9 +5,9 @@ import ui.MenuList; class TextMenuList extends MenuTypedList { - public function new (navControls:NavControls = Vertical) + public function new (navControls:NavControls = Vertical, ?wrapMode) { - super(navControls); + super(navControls, wrapMode); } public function createItem(x = 0.0, y = 0.0, name:String, font:AtlasFont = Bold, callback, fireInstantly = false)