more work on controls menu

This commit is contained in:
George FunBook 2021-03-22 07:48:52 -05:00
parent 7ce5bb40da
commit 3572cd9c47
6 changed files with 254 additions and 95 deletions

View File

@ -34,37 +34,37 @@ class InputFormatter
case SEVEN : "7"; case SEVEN : "7";
case EIGHT : "8"; case EIGHT : "8";
case NINE : "9"; case NINE : "9";
case PAGEUP : "PgU"; case PAGEUP : "PgUp";
case PAGEDOWN : "PgD"; case PAGEDOWN : "PgDown";
case HOME : "Hm"; // case HOME : "Hm";
case END : "End"; // case END : "End";
case INSERT : "Ins"; // case INSERT : "Ins";
case ESCAPE : "Esc"; // case ESCAPE : "Esc";
case MINUS : "-"; // case MINUS : "-";
case PLUS : "+"; // case PLUS : "+";
case DELETE : "Del"; // case DELETE : "Del";
case BACKSPACE : "Bck"; case BACKSPACE : "BckSpc";
case LBRACKET : "["; case LBRACKET : "[";
case RBRACKET : "]"; case RBRACKET : "]";
case BACKSLASH : "\\"; case BACKSLASH : "\\";
case CAPSLOCK : "Cap"; case CAPSLOCK : "Caps";
case SEMICOLON : ";"; case SEMICOLON : ";";
case QUOTE : "'"; case QUOTE : "'";
case ENTER : "Ent"; // case ENTER : "Ent";
case SHIFT : "Shf"; // case SHIFT : "Shf";
case COMMA : ","; case COMMA : ",";
case PERIOD : "."; case PERIOD : ".";
case SLASH : "/"; case SLASH : "/";
case GRAVEACCENT : "`"; case GRAVEACCENT : "`";
case CONTROL : "Ctl"; case CONTROL : "Ctrl";
case ALT : "Alt"; case ALT : "Alt";
case SPACE : "Spc"; // case SPACE : "Spc";
case UP : "Up"; // case UP : "Up";
case DOWN : "Dn"; // case DOWN : "Dn";
case LEFT : "Lf"; // case LEFT : "Lf";
case RIGHT : "Rt"; // case RIGHT : "Rt";
case TAB : "Tab"; // case TAB : "Tab";
case PRINTSCREEN : "Prt"; case PRINTSCREEN : "PrtScrn";
case NUMPADZERO : "#0"; case NUMPADZERO : "#0";
case NUMPADONE : "#1"; case NUMPADONE : "#1";
case NUMPADTWO : "#2"; case NUMPADTWO : "#2";
@ -79,7 +79,7 @@ class InputFormatter
case NUMPADPLUS : "#+"; case NUMPADPLUS : "#+";
case NUMPADPERIOD : "#."; case NUMPADPERIOD : "#.";
case NUMPADMULTIPLY: "#*"; case NUMPADMULTIPLY: "#*";
default: titleCaseTrim(FlxKey.toStringMap[id]); default: titleCase(FlxKey.toStringMap[id]);
} }
} }
@ -98,39 +98,36 @@ class InputFormatter
return switch (name == null ? "" : name.toLowerCase()) return switch (name == null ? "" : name.toLowerCase())
{ {
case "": "[?]"; case "": "[?]";
case "square" : "[]"; // case "square" : "[]";
case "circle" : "()"; // case "circle" : "()";
case "triangle": "/\\"; // case "triangle": "/\\";
case "plus" : "+"; // case "plus" : "+";
case "minus" : "-"; // case "minus" : "-";
case "home" : "Hm"; // case "home" : "Hm";
case "guide" : "Gd"; // case "guide" : "Gd";
case "back" : "Bk"; // case "back" : "Bk";
case "select" : "Bk"; // case "select" : "Bk";
case "start" : "St"; // case "start" : "St";
case "left" : "Lf"; // case "left" : "Lf";
case "right" : "Rt"; // case "right" : "Rt";
case "down" : "Dn"; // case "down" : "Dn";
case "up" : "Up"; // case "up" : "Up";
case dir if (dirReg.match(dir)): case dir if (dirReg.match(dir)):
dirReg.matched(1).toUpperCase() + "-" dirReg.matched(1).toUpperCase() + " " + titleCase(dirReg.matched(2));
+ switch (dirReg.matched(2)) case label: titleCase(label);
{
case "left" : "L";
case "right": "R";
case "down" : "D";
case "up" : "U";
default: throw "Unreachable exaustiveness case";
};
case label: titleCaseTrim(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(); 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 inline static public function parsePadName(name:String):ControllerName
{ {
return ControllerName.parseName(name); return ControllerName.parseName(name);

View File

@ -10,9 +10,9 @@ class AtlasMenuList extends MenuTypedList<AtlasMenuItem>
{ {
public var atlas:FlxAtlasFrames; 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)) if (Std.is(atlas, String))
this.atlas = Paths.getSparrowAtlas(cast atlas); this.atlas = Paths.getSparrowAtlas(cast atlas);

View File

@ -3,6 +3,7 @@ package ui;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup; import flixel.group.FlxSpriteGroup;
import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxAtlasFrames;
import flixel.util.FlxStringUtil;
@:forward @:forward
abstract BoldText(AtlasText) from AtlasText to AtlasText abstract BoldText(AtlasText) from AtlasText to AtlasText
@ -68,7 +69,7 @@ class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
if (value == "") if (value == "")
return this.text; return this.text;
appendTextCased(this.text); appendTextCased(caseValue);
return this.text; return this.text;
} }
@ -157,6 +158,16 @@ class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
} }
} }
} }
override function toString()
{
return "InputItem, " + FlxStringUtil.getDebugString(
[ LabelValuePair.weak("x", x)
, LabelValuePair.weak("y", y)
, LabelValuePair.weak("text", text)
]
);
}
} }
class AtlasChar extends FlxSprite class AtlasChar extends FlxSprite

View File

@ -1,6 +1,7 @@
package ui; package ui;
import ui.MenuList; import flixel.input.actions.FlxActionInput;
import flixel.input.gamepad.FlxGamepadInputID;
import flixel.FlxG; import flixel.FlxG;
import flixel.FlxCamera; import flixel.FlxCamera;
import flixel.FlxObject; import flixel.FlxObject;
@ -10,10 +11,12 @@ import flixel.input.keyboard.FlxKey;
import Controls; import Controls;
import ui.AtlasText; import ui.AtlasText;
import ui.MenuList;
import ui.TextMenuList; import ui.TextMenuList;
class ControlsMenu extends ui.OptionsState.Page class ControlsMenu extends ui.OptionsState.Page
{ {
inline static public var COLUMNS = 2;
static var controlList = Control.createAll(); 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, * 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<Array<InputItem>> = [for (i in 0...controlGroups.length) []]; var itemGroups:Array<Array<InputItem>> = [for (i in 0...controlGroups.length) []];
var controlGrid:MenuTypedList<InputItem>; var controlGrid:MenuTypedList<InputItem>;
var deviceList:TextMenuList;
var menuCamera:FlxCamera; var menuCamera:FlxCamera;
var prompt:Prompt; var prompt:Prompt;
var camFollow:FlxObject;
var labels:FlxTypedGroup<AtlasText>;
var currentDevice:Device = Keys;
var deviceListSelected = false;
public function new() public function new()
{ {
@ -45,16 +54,36 @@ class ControlsMenu extends ui.OptionsState.Page
menuCamera.bgColor = 0x0; menuCamera.bgColor = 0x0;
camera = menuCamera; camera = menuCamera;
var labels = new FlxTypedGroup<AtlasText>(); labels = new FlxTypedGroup<AtlasText>();
var headers = new FlxTypedGroup<AtlasText>(); var headers = new FlxTypedGroup<AtlasText>();
add(controlGrid = new MenuTypedList(Columns(2))); controlGrid = new MenuTypedList(Columns(COLUMNS), Vertical);
add(labels); add(labels);
add(headers); add(headers);
add(controlGrid); 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; // FlxG.debugger.drawDebug = true;
var y = 30; var y = deviceList == null ? 30 : 120;
var spacer = 70; var spacer = 70;
var currentHeader:String = null; var currentHeader:String = null;
// list order is determined by enum order // list order is determined by enum order
@ -78,25 +107,33 @@ class ControlsMenu extends ui.OptionsState.Page
if (currentHeader != null && name.indexOf(currentHeader) == 0) if (currentHeader != null && name.indexOf(currentHeader) == 0)
name = name.substr(currentHeader.length); 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; label.alpha = 0.6;
createItem(label.x + 400, y, control, 0); for (i in 0...COLUMNS)
createItem(label.x + 600, y, control, 1); createItem(label.x + 400 + i * 300, y, control, i);
y += spacer; y += spacer;
} }
labels.members[0].alpha = 1.0; camFollow = new FlxObject(FlxG.width / 2, 0, 70, 70);
var selected = controlGrid.members[0]; if (deviceList != null)
var camFollow = new FlxObject(FlxG.width / 2, selected.y, 70, 70); {
camFollow.y = deviceList.selectedItem.y;
controlGrid.selectedItem.idle();
}
else
camFollow.y = controlGrid.selectedItem.y;
menuCamera.follow(camFollow, null, 0.06); menuCamera.follow(camFollow, null, 0.06);
var margin = 100; var margin = 100;
menuCamera.deadzone.set(0, margin, menuCamera.width, menuCamera.height - margin * 2); menuCamera.deadzone.set(0, margin, menuCamera.width, menuCamera.height - margin * 2);
menuCamera.minScrollY = 0;
controlGrid.onChange.add(function (selected) controlGrid.onChange.add(function (selected)
{ {
camFollow.y = selected.y; camFollow.y = selected.y;
labels.forEach((label)->label.alpha = 0.6); 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); 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) 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) for (i in 0...controlGroups.length)
{ {
if (controlGroups[i].contains(control)) if (controlGroups[i].contains(control))
@ -126,36 +163,95 @@ class ControlsMenu extends ui.OptionsState.Page
prompt.exists = true; 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) override function update(elapsed:Float)
{ {
super.update(elapsed); super.update(elapsed);
var controls = PlayerSettings.player1.controls;
if (enabled && deviceList != null && deviceListSelected == false && controls.BACK)
goToDeviceList();
if (prompt.exists) if (prompt.exists)
{ {
var key = FlxG.keys.firstJustPressed(); switch (currentDevice)
if (key != NONE)
{ {
if (key != ESCAPE) case Keys:
onKeySelect(key); {
closePrompt(); 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; 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) for (group in itemGroups)
{ {
if (group.contains(item)) if (group.contains(item))
{ {
for (otherItem in group) for (otherItem in group)
{ {
// Check if items in the same group have the new input if (otherItem != item && otherItem.input == input)
if (otherItem != item && otherItem.input == key)
{ {
// replace that input with this items old 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 // Don't use resetItem() since items share names/labels
otherItem.input = item.input; otherItem.input = item.input;
otherItem.label.text = item.label.text; 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 // Don't use resetItem() since items share names/labels
item.input = key; item.input = input;
item.label.text = item.getLabel(key); item.label.text = item.getLabel(input);
} }
function closePrompt() function closePrompt()
@ -181,42 +277,75 @@ class ControlsMenu extends ui.OptionsState.Page
{ {
super.destroy(); super.destroy();
itemGroups = null;
if (FlxG.cameras.list.contains(menuCamera)) if (FlxG.cameras.list.contains(menuCamera))
FlxG.cameras.remove(menuCamera); FlxG.cameras.remove(menuCamera);
} }
override function set_enabled(value:Bool) 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); return super.set_enabled(value);
} }
} }
class InputItem extends TextMenuItem class InputItem extends TextMenuItem
{ {
public var device(default, null):Device = Keys;
public var control:Control; public var control:Control;
public var input:Int = -1; 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.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.length > index)
{ {
if (list[index] != FlxKey.ESCAPE) if (list[index] != FlxKey.ESCAPE || list[index] != FlxGamepadInputID.BACK)
input = list[index]; return list[index];
else if (list.length > 2)
if (list.length > ControlsMenu.COLUMNS)
// Escape isn't mappable, show a third option, instead. // Escape isn't mappable, show a third option, instead.
input = list[2]; return list[ControlsMenu.COLUMNS];
} }
; return -1;
super(x, y, getLabel(input), Default, callback);
} }
public function getLabel(input:Int) public function getLabel(input:Int)
{ {
return input == -1 ? "---" : InputFormatter.format(input, Keys); return input == -1 ? "---" : InputFormatter.format(input, device);
} }
} }

View File

@ -19,14 +19,26 @@ class MenuTypedList<T:MenuItem> extends FlxTypedGroup<T>
public var navControls:NavControls; public var navControls:NavControls;
/** Set to false to disable nav control */ /** Set to false to disable nav control */
public var enabled:Bool = true; public var enabled:Bool = true;
/** */
public var wrapMode:WrapMode = Both;
var byName = new Map<String, T>(); var byName = new Map<String, T>();
/** Set to true, internally to disable controls, without affecting vars like `enabled` */ /** Set to true, internally to disable controls, without affecting vars like `enabled` */
var busy:Bool = false; var busy:Bool = false;
public function new (navControls:NavControls = Vertical) public function new (navControls:NavControls = Vertical, ?wrapMode:WrapMode)
{ {
this.navControls = navControls; this.navControls = navControls;
if (wrapMode != null)
this.wrapMode = wrapMode;
else
this.wrapMode = switch (navControls)
{
case Horizontal: Horizontal;
case Vertical: Vertical;
default: Both;
}
super(); super();
} }
@ -64,14 +76,16 @@ class MenuTypedList<T:MenuItem> extends FlxTypedGroup<T>
{ {
var controls = PlayerSettings.player1.controls; var controls = PlayerSettings.player1.controls;
var wrapX = wrapMode.match(Horizontal | Both);
var wrapY = wrapMode.match(Vertical | Both);
var newIndex = switch(navControls) var newIndex = switch(navControls)
{ {
case Vertical : navList(controls.UI_UP_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); 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); 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 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 , controls.UI_LEFT_P, controls.UI_RIGHT_P); 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) if (newIndex != selectedIndex)
@ -114,7 +128,7 @@ class MenuTypedList<T:MenuItem> extends FlxTypedGroup<T>
* @param next * @param next
* @param allowWrap * @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); return navAxis(selectedIndex, length, prev, next, allowWrap);
} }
@ -128,7 +142,7 @@ class MenuTypedList<T:MenuItem> extends FlxTypedGroup<T>
* @param next Whether the 'next' key is pressed along the variable-lengthed axis. eg: "down" in Column mode * @param next Whether the 'next' key is pressed along the variable-lengthed axis. eg: "down" in Column mode
* @param allowWrap unused * @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 // The grid lenth along the variable-length axis
var size = Math.ceil(length / latSize); var size = Math.ceil(length / latSize);
@ -137,7 +151,7 @@ class MenuTypedList<T:MenuItem> extends FlxTypedGroup<T>
// The selected position along the fixed axis // The selected position along the fixed axis
var latIndex = selectedIndex % latSize; 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); index = navAxis(index, size, prev, next, allowWrap);
return Std.int(Math.min(length - 1, index * latSize + latIndex)); return Std.int(Math.min(length - 1, index * latSize + latIndex));
@ -341,4 +355,12 @@ enum NavControls
Both; Both;
Columns(num:Int); Columns(num:Int);
Rows(num:Int); Rows(num:Int);
}
enum WrapMode
{
Horizontal;
Vertical;
Both;
None;
} }

View File

@ -5,9 +5,9 @@ import ui.MenuList;
class TextMenuList extends MenuTypedList<TextMenuItem> class TextMenuList extends MenuTypedList<TextMenuItem>
{ {
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) public function createItem(x = 0.0, y = 0.0, name:String, font:AtlasFont = Bold, callback, fireInstantly = false)