Funkin/source/ui/MenuList.hx

366 lines
7.8 KiB
Haxe
Raw Normal View History

2021-02-15 12:43:51 -05:00
package ui;
import flixel.math.FlxPoint;
import flixel.FlxG;
import flixel.FlxSprite;
2021-02-15 12:43:51 -05:00
import flixel.effects.FlxFlicker;
import flixel.group.FlxGroup;
import flixel.util.FlxSignal;
class MenuTypedList<T:MenuItem> extends FlxTypedGroup<T>
2021-02-15 12:43:51 -05:00
{
2021-02-18 14:58:16 -05:00
public var selectedIndex(default, null) = 0;
2021-03-16 10:56:08 -04:00
public var selectedItem(get, never):T;
2021-02-18 14:58:16 -05:00
/** Called when a new item is highlighted */
2021-02-15 12:43:51 -05:00
public var onChange(default, null) = new FlxTypedSignal<T->Void>();
2021-02-18 14:58:16 -05:00
/** Called when an item is accepted */
2021-02-15 12:43:51 -05:00
public var onAcceptPress(default, null) = new FlxTypedSignal<T->Void>();
2021-02-18 14:58:16 -05:00
/** The navigation control scheme to use */
public var navControls:NavControls;
/** Set to false to disable nav control */
public var enabled:Bool = true;
2021-03-22 08:48:52 -04:00
/** */
public var wrapMode:WrapMode = Both;
2021-02-15 12:43:51 -05:00
2021-02-18 14:58:16 -05:00
var byName = new Map<String, T>();
/** Set to true, internally to disable controls, without affecting vars like `enabled` */
var busy:Bool = false;
2021-03-22 08:48:52 -04:00
public function new (navControls:NavControls = Vertical, ?wrapMode:WrapMode)
2021-02-15 12:43:51 -05:00
{
2021-02-18 14:58:16 -05:00
this.navControls = navControls;
2021-03-22 08:48:52 -04:00
if (wrapMode != null)
this.wrapMode = wrapMode;
else
this.wrapMode = switch (navControls)
{
case Horizontal: Horizontal;
case Vertical: Vertical;
default: Both;
}
2021-02-15 12:43:51 -05:00
super();
2021-02-18 14:58:16 -05:00
}
2021-02-23 19:59:09 -05:00
public function addItem(name:String, item:T):T
2021-02-18 14:58:16 -05:00
{
if (length == selectedIndex)
item.select();
2021-02-15 12:43:51 -05:00
2021-02-18 14:58:16 -05:00
byName[name] = item;
return add(item);
}
public function resetItem(oldName:String, newName:String, ?callback:Void->Void):T
{
if (!byName.exists(oldName))
throw "No item named:" + oldName;
var item = byName[oldName];
byName.remove(oldName);
byName[newName] = item;
item.setItem(newName, callback);
return item;
2021-02-15 12:43:51 -05:00
}
override function update(elapsed:Float)
{
super.update(elapsed);
2021-02-18 14:58:16 -05:00
if (enabled && !busy)
updateControls();
}
inline function updateControls()
{
2021-02-15 12:43:51 -05:00
var controls = PlayerSettings.player1.controls;
2021-03-22 08:48:52 -04:00
var wrapX = wrapMode.match(Horizontal | Both);
var wrapY = wrapMode.match(Vertical | Both);
2021-02-23 19:59:09 -05:00
var newIndex = switch(navControls)
2021-02-18 14:58:16 -05:00
{
2021-03-22 08:48:52 -04:00
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));
2021-02-23 19:59:09 -05:00
2021-03-22 08:48:52 -04:00
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);
2021-02-23 19:59:09 -05:00
}
if (newIndex != selectedIndex)
{
FlxG.sound.play(Paths.sound('scrollMenu'));
selectItem(newIndex);
2021-02-18 14:58:16 -05:00
}
2021-02-19 21:11:33 -05:00
//Todo: bypass popup blocker on firefox
2021-02-15 12:43:51 -05:00
if (controls.ACCEPT)
accept();
}
2021-02-23 19:59:09 -05:00
function navAxis(index:Int, size:Int, prev:Bool, next:Bool, allowWrap:Bool):Int
{
if (prev == next)
return index;
if (prev)
{
if (index > 0)
index--;
else if (allowWrap)
index = size - 1;
}
else
{
if (index < size - 1)
index++;
else if (allowWrap)
index = 0;
}
return index;
}
/**
* Controls navigation on a linear list of items such as Vertical.
* @param prev
* @param next
* @param allowWrap
*/
2021-03-22 08:48:52 -04:00
inline function navList(prev:Bool, next:Bool, allowWrap:Bool)
2021-02-23 19:59:09 -05:00
{
return navAxis(selectedIndex, length, prev, next, allowWrap);
}
/**
* Controls navigation on a grid
* @param latSize The size of the fixed axis of the grid, or the "lateral axis"
* @param latPrev Whether the 'prev' key is pressed along the fixed-lengthed axis. eg: "left" in Column mode
* @param latNext Whether the 'next' key is pressed along the fixed-lengthed axis. eg: "right" in Column mode
* @param prev Whether the 'prev' key is pressed along the variable-lengthed axis. eg: "up" in Column mode
* @param next Whether the 'next' key is pressed along the variable-lengthed axis. eg: "down" in Column mode
* @param allowWrap unused
*/
2021-03-22 08:48:52 -04:00
function navGrid(latSize:Int, latPrev:Bool, latNext:Bool, latAllowWrap:Bool, prev:Bool, next:Bool, allowWrap:Bool):Int
2021-02-23 19:59:09 -05:00
{
// The grid lenth along the variable-length axis
var size = Math.ceil(length / latSize);
// The selected position along the variable-length axis
var index = Math.floor(selectedIndex / latSize);
// The selected position along the fixed axis
var latIndex = selectedIndex % latSize;
2021-03-22 08:48:52 -04:00
latIndex = navAxis(latIndex, latSize, latPrev, latNext, latAllowWrap);
2021-02-23 19:59:09 -05:00
index = navAxis(index, size, prev, next, allowWrap);
return Std.int(Math.min(length - 1, index * latSize + latIndex));
}
2021-02-15 12:43:51 -05:00
public function accept()
{
var selected = members[selectedIndex];
2021-02-15 17:04:08 -05:00
onAcceptPress.dispatch(selected);
2021-02-15 12:43:51 -05:00
if (selected.fireInstantly)
selected.callback();
else
{
2021-02-18 14:58:16 -05:00
busy = true;
2021-02-15 12:43:51 -05:00
FlxG.sound.play(Paths.sound('confirmMenu'));
FlxFlicker.flicker(selected, 1, 0.06, true, false, function(_)
{
2021-02-18 14:58:16 -05:00
busy = false;
2021-02-15 12:43:51 -05:00
selected.callback();
});
}
}
2021-02-18 14:58:16 -05:00
public function selectItem(index:Int)
{
2021-02-15 12:43:51 -05:00
members[selectedIndex].idle();
2021-02-18 14:58:16 -05:00
selectedIndex = index;
2021-02-15 12:43:51 -05:00
var selected = members[selectedIndex];
selected.select();
onChange.dispatch(selected);
}
public function has(name:String)
{
return byName.exists(name);
}
2021-02-18 14:58:16 -05:00
public function getItem(name:String)
{
return byName[name];
}
2021-02-15 12:43:51 -05:00
override function destroy()
{
super.destroy();
2021-02-18 14:58:16 -05:00
byName.clear();
2021-02-23 19:59:09 -05:00
onChange.removeAll();
onAcceptPress.removeAll();
2021-02-15 12:43:51 -05:00
}
2021-03-16 10:56:08 -04:00
inline function get_selectedItem():T
{
return members[selectedIndex];
}
2021-02-15 12:43:51 -05:00
}
class MenuItem extends FlxSprite
{
2021-02-15 12:43:51 -05:00
public var callback:Void->Void;
public var name:String;
2021-02-15 12:43:51 -05:00
/**
* Set to true for things like opening URLs otherwise, it may it get blocked.
*/
public var fireInstantly = false;
public var selected(get, never):Bool;
function get_selected() return alpha == 1.0;
2021-02-15 12:43:51 -05:00
public function new (x = 0.0, y = 0.0, name:String, callback)
2021-02-15 12:43:51 -05:00
{
super(x, y);
2021-02-18 14:58:16 -05:00
antialiasing = true;
setData(name, callback);
idle();
2021-02-15 12:43:51 -05:00
}
function setData(name:String, ?callback:Void->Void)
2021-02-15 12:43:51 -05:00
{
this.name = name;
2021-02-18 14:58:16 -05:00
if (callback != null)
this.callback = callback;
}
/**
* Calls setData and resets/redraws the state of the item
2021-03-16 10:56:08 -04:00
* @param name the label.
* @param callback Unchanged if null.
*/
public function setItem(name:String, ?callback:Void->Void)
{
setData(name, callback);
2021-02-18 14:58:16 -05:00
if (selected)
select();
else
idle();
2021-02-15 12:43:51 -05:00
}
public function idle()
2021-02-15 12:43:51 -05:00
{
alpha = 0.6;
2021-02-15 12:43:51 -05:00
}
public function select()
2021-02-15 12:43:51 -05:00
{
alpha = 1.0;
2021-02-15 12:43:51 -05:00
}
}
class MenuTypedItem<T:FlxSprite> extends MenuItem
{
public var label(default, set):T;
2021-02-15 12:43:51 -05:00
public function new (x = 0.0, y = 0.0, label:T, name:String, callback)
{
super(x, y, name, callback);
// set label after super otherwise setters fuck up
this.label = label;
}
/**
* Use this when you only want to show the label
*/
function setEmptyBackground()
{
var oldWidth = width;
var oldHeight = height;
makeGraphic(1, 1, 0x0);
width = oldWidth;
height = oldHeight;
}
function set_label(value:T)
2021-02-15 12:43:51 -05:00
{
if (value != null)
{
value.x = x;
value.y = y;
value.alpha = alpha;
}
return this.label = value;
}
override function update(elapsed:Float)
{
super.update(elapsed);
if (label != null)
label.update(elapsed);
}
override function draw()
{
super.draw();
if (label != null)
{
label.cameras = cameras;
label.scrollFactor.copyFrom(scrollFactor);
label.draw();
}
}
override function set_alpha(value:Float):Float
{
super.set_alpha(value);
if (label != null)
label.alpha = alpha;
return alpha;
}
override function set_x(value:Float):Float
{
super.set_x(value);
if (label != null)
label.x = x;
return x;
}
override function set_y(Value:Float):Float
{
super.set_y(Value);
if (label != null)
label.y = y;
return y;
2021-02-15 12:43:51 -05:00
}
2021-02-18 14:58:16 -05:00
}
enum NavControls
{
Horizontal;
Vertical;
Both;
2021-02-23 19:59:09 -05:00
Columns(num:Int);
Rows(num:Int);
2021-03-22 08:48:52 -04:00
}
enum WrapMode
{
Horizontal;
Vertical;
Both;
None;
2021-02-15 12:43:51 -05:00
}