using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using MLEM.Input;
using MLEM.Maths;
using MLEM.Misc;
using MLEM.Textures;
namespace MLEM.Ui.Elements {
///
/// This class contains a set of helper methods that aid in creating special kinds of compound types for use inside of a .
///
public static class ElementHelper {
///
/// Creates a button with an image on the left side of its text.
///
/// The button's anchor
/// The button's size
/// The texture of the image to render on the button
/// The text to display on the button
/// The text of the button's tooltip
/// The of the button's image
/// An image button
public static Button ImageButton(Anchor anchor, Vector2 size, TextureRegion texture, string text = null, string tooltipText = null, float imagePadding = 2) {
var button = new Button(anchor, size, text, tooltipText);
var image = new Image(Anchor.CenterLeft, Vector2.One, texture);
image.Padding = image.Padding.OrStyle(new Padding(imagePadding), 1);
button.OnAreaUpdated += e => image.Size = new Vector2(e.Area.Height, e.Area.Height) / e.Scale;
button.AddChild(image, 0);
return button;
}
///
/// Creates a panel that contains a paragraph of text and a button to close the panel.
/// The panel is part of a group, which causes elements in the background (behind and around the panel) to not be clickable, leaving only the "close" button.
///
/// The ui system to add the panel to, optional.
/// The anchor of the panel
/// The width of the panel
/// The text to display on the panel
/// The height of the "close" button
/// The text on the "close" button
/// An info box panel
public static Panel ShowInfoBox(UiSystem system, Anchor anchor, float width, string text, float buttonHeight = 10, string okText = "Okay") {
var group = new Group(Anchor.TopLeft, Vector2.One, false);
var box = group.AddChild(new Panel(anchor, new Vector2(width, 1), Vector2.Zero, true));
box.AddChild(new Paragraph(Anchor.AutoLeft, 1, text));
var button = box.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.5F, buttonHeight), okText) {
OnPressed = element => system.Remove("InfoBox"),
PositionOffset = new Vector2(0, 1)
});
var root = system.Add("InfoBox", group);
root.SelectElement(button);
return box;
}
///
/// Creates an array of groups with a fixed width that can be used to create a column structure
///
/// The element the groups should be added to, optional.
/// The total width of all of the groups combined
/// The amount of groups to split the total size into
/// Whether the groups should set their heights based on their children's heights
/// An array of columns
public static Group[] MakeColumns(Element parent, Vector2 totalSize, int amount, bool setHeightBasedOnChildren = true) {
var cols = new Group[amount];
for (var i = 0; i < amount; i++) {
var anchor = i == amount - 1 ? Anchor.AutoInlineIgnoreOverflow : Anchor.AutoInline;
cols[i] = new Group(anchor, new Vector2(totalSize.X / amount, totalSize.Y), setHeightBasedOnChildren);
parent?.AddChild(cols[i]);
}
return cols;
}
///
/// Creates an array of groups with a fixed width and height that can be used to create a grid with equally sized boxes.
///
/// The element the groups should be added to, can be .
/// The total size that the grid should take up.
/// The width of the grid, or the amount of columns it should have.
/// The height of the grid, or the amount of rows it should have.
/// The created grid.
public static Group[,] MakeGrid(Element parent, Vector2 totalSize, int width, int height) {
var grid = new Group[width, height];
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var anchor = x == 0 ? Anchor.AutoLeft : Anchor.AutoInlineIgnoreOverflow;
grid[x, y] = new Group(anchor, new Vector2(totalSize.X / width, totalSize.Y / height), false);
parent?.AddChild(grid[x, y]);
}
}
return grid;
}
///
/// Creates a with a + and a - button next to it, to allow for easy number input.
///
/// The text field's anchor
/// The size of the text field
/// The default content of the text field
/// The value that is added or removed to the text field's value when clicking the + or - buttons
/// The rule for text input. by default.
/// A callback that is invoked when the text field's text changes
/// A group that contains the number field
public static Group NumberField(Anchor anchor, Vector2 size, int defaultValue = 0, int stepPerClick = 1, TextField.Rule rule = null, TextField.TextChanged onTextChange = null) {
var group = new Group(anchor, size, false);
var field = new TextField(Anchor.TopLeft, Vector2.One, rule ?? TextField.OnlyNumbers);
field.OnTextChange = onTextChange;
field.SetText(defaultValue.ToString());
group.AddChild(field);
group.OnAreaUpdated += e => field.Size = new Vector2((e.Area.Width - e.Area.Height / 2) / e.Scale, 1);
var upButton = new Button(Anchor.TopRight, Vector2.One, "+") {
OnPressed = element => {
var text = field.Text;
if (int.TryParse(text, out var val))
field.SetText(val + stepPerClick);
}
};
group.AddChild(upButton);
group.OnAreaUpdated += e => upButton.Size = new Vector2(e.Area.Height / 2 / e.Scale);
var downButton = new Button(Anchor.BottomRight, Vector2.One, "-") {
OnPressed = element => {
var text = field.Text;
if (int.TryParse(text, out var val))
field.SetText(val - stepPerClick);
}
};
group.AddChild(downButton);
group.OnAreaUpdated += e => downButton.Size = new Vector2(e.Area.Height / 2 / e.Scale);
return group;
}
///
public static Button KeybindButton(Anchor anchor, Vector2 size, Keybind keybind, InputHandler inputHandler, string activePlaceholder, GenericInput unbindKey = default, string unboundPlaceholder = "", Func inputName = null, int index = 0, Func, bool> isKeybindAllowed = null) {
return ElementHelper.KeybindButton(anchor, size, keybind, inputHandler, activePlaceholder, new Keybind(unbindKey), unboundPlaceholder, inputName, index, isKeybindAllowed);
}
///
/// Creates a that acts as a way to input a custom value for a .
/// Note that only the at index of the given keybind is displayed and edited, all others are ignored.
/// Inputting custom keybinds using this element supports -based modifiers and any -based keys.
/// The keybind button's current state can be retrieved using the "Active" key, which stores a bool that indicates whether the button is currently waiting for a new value to be assigned.
///
/// The button's anchor
/// The button's size
/// The keybind that this button should represent
/// The input handler to query inputs with
/// A placeholder text that is displayed while the keybind is being edited
/// An optional keybind to press that allows the keybind value to be unbound, clearing the combination
/// A placeholder text that is displayed if the keybind is unbound
/// An optional function to give each input a display name that is easier to read. If this is null, is used.
/// The index in the that the button should display. Note that, if the index is greater than the amount of combinations, combinations entered using this button will be stored at an earlier index.
/// A function that can optionally determine whether a given input and modifier combination is allowed. If this is null, all combinations are allowed.
/// A keybind button with the given settings
public static Button KeybindButton(Anchor anchor, Vector2 size, Keybind keybind, InputHandler inputHandler, string activePlaceholder, Keybind unbind = default, string unboundPlaceholder = "", Func inputName = null, int index = 0, Func, bool> isKeybindAllowed = null) {
string GetCurrentName() {
return keybind.TryGetCombination(index, out var combination) ? combination.ToString(" + ", inputName) : unboundPlaceholder;
}
var button = new Button(anchor, size, GetCurrentName());
var activeNext = false;
button.OnPressed = e => {
button.Text.Text = activePlaceholder;
activeNext = true;
};
button.OnUpdated = (e, time) => {
if (activeNext) {
button.SetData("Active", true);
activeNext = false;
} else if (button.GetData("Active")) {
if (unbind != null && unbind.TryConsumePressed(inputHandler)) {
keybind.Remove((c, i) => i == index);
button.Text.Text = unboundPlaceholder;
button.SetData("Active", false);
} else if (inputHandler.InputsPressed.Length > 0) {
var key = inputHandler.InputsPressed.FirstOrDefault(i => !i.IsModifier());
if (key != default) {
var mods = inputHandler.InputsDown.Where(i => i.IsModifier()).ToArray();
if (isKeybindAllowed == null || isKeybindAllowed(key, mods)) {
keybind.Remove((c, i) => i == index).Insert(index, key, mods);
button.Text.Text = GetCurrentName();
button.SetData("Active", false);
}
}
}
} else {
button.Text.Text = GetCurrentName();
}
};
return button;
}
}
///
/// This class contains a set of extensions for dealing with objects
///
public static class ElementExtensions {
///
/// Adds a new to the given element using the constructor
///
/// The element to add the tooltip to
/// The text to display on the tooltip
/// The created tooltip instance
public static Tooltip AddTooltip(this Element element, Paragraph.TextCallback textCallback) {
return element.AddTooltip(new Tooltip(textCallback));
}
///
/// Adds a new to the given element using the constructor
///
/// The element to add the tooltip to
/// The text to display on the tooltip
/// The created tooltip instance
public static Tooltip AddTooltip(this Element element, string text) {
return element.AddTooltip(new Tooltip(text));
}
///
/// Adds the given to the given element
///
/// The element to add the tooltip to
/// The tooltip to add
/// The passed tooltip, for chaining
public static Tooltip AddTooltip(this Element element, Tooltip tooltip) {
tooltip.AddToElement(element);
return tooltip;
}
///
/// Returns whether the given is automatic. The anchors , , , and any anchor that will return true.
///
/// The anchor to query.
/// Whether the given anchor is automatic.
public static bool IsAuto(this Anchor anchor) {
return anchor == Anchor.AutoLeft || anchor == Anchor.AutoCenter || anchor == Anchor.AutoRight || anchor.IsInline();
}
///
/// Returns whether the given is inline. The anchors , , , and any anchor that will return true.
///
/// The anchor to query.
/// Whether the given anchor is inline.
public static bool IsInline(this Anchor anchor) {
return anchor == Anchor.AutoInline || anchor == Anchor.AutoInlineCenter || anchor == Anchor.AutoInlineBottom || anchor.IsIgnoreOverflow();
}
///
/// Returns whether the given ignores overflow. The anchors , , and will return true.
///
/// The anchor to query.
/// Whether the given anchor ignores overflow.
public static bool IsIgnoreOverflow(this Anchor anchor) {
return anchor == Anchor.AutoInlineIgnoreOverflow || anchor == Anchor.AutoInlineCenterIgnoreOverflow || anchor == Anchor.AutoInlineBottomIgnoreOverflow;
}
///
/// Returns whether the given is left-aligned for the purpose of . The anchors , , , , and any anchor that will return true.
///
/// The anchor to query.
/// Whether the given anchor is left-aligned.
public static bool IsLeftAligned(this Anchor anchor) {
return anchor == Anchor.TopLeft || anchor == Anchor.CenterLeft || anchor == Anchor.BottomLeft || anchor == Anchor.AutoLeft || anchor.IsInline();
}
///
/// Returns whether the given is top-aligned for the purpose of . The anchors , , , , , , and will return true.
///
/// The anchor to query.
/// Whether the given anchor is top-aligned.
public static bool IsTopAligned(this Anchor anchor) {
return anchor == Anchor.TopLeft || anchor == Anchor.TopCenter || anchor == Anchor.TopRight || anchor == Anchor.AutoLeft || anchor == Anchor.AutoCenter || anchor == Anchor.AutoRight || anchor == Anchor.AutoInline || anchor == Anchor.AutoInlineIgnoreOverflow;
}
}
}