/*
* Copyright 2022 Peter Han
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace PeterHan.PLib.UI {
///
/// A dialog root for UI components.
///
public sealed class PDialog : IUIComponent {
///
/// The margin around dialog buttons.
///
public static readonly RectOffset BUTTON_MARGIN = new RectOffset(13, 13, 13, 13);
///
/// The margin inside the dialog close button.
///
private static readonly RectOffset CLOSE_ICON_MARGIN = new RectOffset(4, 4, 4, 4);
///
/// The size of the dialog close button's icon.
///
private static readonly Vector2 CLOSE_ICON_SIZE = new Vector2f(16.0f, 16.0f);
///
/// The dialog key returned if the user closes the dialog with [ESC] or the X.
///
public const string DIALOG_KEY_CLOSE = "close";
///
/// Returns a suitable parent object for a dialog.
///
/// A game object that can be used as a dialog parent depending on the game
/// stage, or null if none is available.
public static GameObject GetParentObject() {
GameObject parent = null;
var fi = FrontEndManager.Instance;
if (fi != null)
parent = fi.gameObject;
else {
// Grr unity
var gi = GameScreenManager.Instance;
if (gi != null)
parent = gi.ssOverlayCanvas;
else
PUIUtils.LogUIWarning("No dialog parent found!");
}
return parent;
}
///
/// Rounds the size up to the nearest even integer.
///
/// The current size.
/// The maximum allowed size.
/// The rounded size.
private static float RoundUpSize(float size, float maxSize) {
int upOne = Mathf.CeilToInt(size);
if (upOne % 2 == 1) upOne++;
if (upOne > maxSize && maxSize > 0.0f)
upOne -= 2;
return upOne;
}
///
/// The dialog body panel. To add custom components to the dialog, use AddChild on
/// this panel. Its direction, margin, and spacing can also be customized.
///
public PPanel Body { get; }
///
/// The background color of the dialog itself (including button panel).
///
public Color DialogBackColor { get; set; }
///
/// The dialog's maximum size. If the dialog preferred size is bigger than this size,
/// the dialog will be decreased in size to fit. If either axis is zero, the dialog
/// gets its preferred size in that axis, at least the value in Size.
///
public Vector2 MaxSize { get; set; }
public string Name { get; }
///
/// The dialog's parent.
///
public GameObject Parent { get; set; }
///
/// If a dialog with an odd width/height is displayed, all offsets will end up on a
/// half pixel offset, which may cause unusual display artifacts as Banker's Rounding
/// will round values that are supposed to be 1.0 units apart into integer values 2
/// units apart. If set, this flag will cause Build to round the dialog's size up to
/// the nearest even integer. If the dialog is already at its maximum size and is still
/// an odd integer in size, it is rounded down one instead.
///
public bool RoundToNearestEven { get; set; }
///
/// The dialog's minimum size. If the dialog preferred size is bigger than this size,
/// the dialog will be increased in size to fit. If either axis is zero, the dialog
/// gets its preferred size in that axis, up until the value in MaxSize.
///
public Vector2 Size { get; set; }
///
/// The dialog sort order which determines which other dialogs this one is on top of.
///
public float SortKey { get; set; }
///
/// The dialog's title.
///
public string Title { get; set; }
///
/// The allowable button choices for the dialog.
///
private readonly ICollection buttons;
///
/// The events to invoke when the dialog is closed.
///
public PUIDelegates.OnDialogClosed DialogClosed { get; set; }
public event PUIDelegates.OnRealize OnRealize;
public PDialog(string name) {
Body = new PPanel("Body") {
Alignment = TextAnchor.UpperCenter, FlexSize = Vector2.one,
Margin = new RectOffset(6, 6, 6, 6)
};
DialogBackColor = PUITuning.Colors.ButtonBlueStyle.inactiveColor;
buttons = new List(4);
MaxSize = Vector2.zero;
Name = name ?? "Dialog";
// First try the front end manager (menu), then the in-game (game)
Parent = GetParentObject();
RoundToNearestEven = false;
Size = Vector2.zero;
SortKey = 0.0f;
Title = "Dialog";
}
///
/// Adds a button to the dialog. The button will use a blue background with white text
/// in the default UI font, except for the last button which will be pink.
///
/// The key to report if this button is selected.
/// The button text.
/// The tooltip to display on the button (optional)
/// This dialog for call chaining.
public PDialog AddButton(string key, string text, string tooltip = null) {
buttons.Add(new DialogButton(key, text, tooltip, null, null));
return this;
}
///
/// Adds a button to the dialog.
///
/// The key to report if this button is selected.
/// The button text.
/// The tooltip to display on the button (optional)
/// The background color to use for the button. If null or
/// omitted, the last button will be pink and all others will be blue.
/// The foreground color to use for the button. If null or
/// omitted, white text with the default game UI font will be used.
/// This dialog for call chaining.
public PDialog AddButton(string key, string text, string tooltip = null,
ColorStyleSetting backColor = null, TextStyleSetting foreColor = null) {
buttons.Add(new DialogButton(key, text, tooltip, backColor, foreColor));
return this;
}
///
/// Adds a handler when this dialog is realized.
///
/// The handler to invoke on realization.
/// This dialog for call chaining.
public PDialog AddOnRealize(PUIDelegates.OnRealize onRealize) {
OnRealize += onRealize;
return this;
}
public GameObject Build() {
if (Parent == null)
throw new InvalidOperationException("Parent for dialog may not be null");
var dialog = PUIElements.CreateUI(Parent, Name);
var dComponent = dialog.AddComponent();
// Background
dialog.AddComponent