2025-11-05 17:34:40 +08:00

281 lines
9.3 KiB
C#

using System;
using System.Linq;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor
{
internal class Toast : EditorWindow
{
private const int DWMWA_WINDOW_CORNER_PREFERENCE = 33;
private enum DWM_WINDOW_CORNER_PREFERENCE
{
DWMWCP_DEFAULT = 0,
DWMWCP_DONOTROUND = 1,
DWMWCP_ROUND = 2,
DWMWCP_ROUNDSMALL = 3
}
public enum ToastSeverity
{
Info,
Warning,
Error
}
public enum ToastPosition
{
TopLeft,
TopRight,
TopCenter,
BottomLeft,
BottomRight,
BottomCenter
}
public struct ToastData
{
public float TimeCreated { get; set; }
public ToastArgs ToastArgs { get; set; }
}
public struct ToastArgs
{
public string Title { get; set; }
public string Message { get; set; }
public ToastPosition ToastPosition { get; set; }
public float LifeTime { get; set; }
public bool NoTimeOut { get; set; }
public Func<VisualElement> CustomContent { get; set; }
public ToastSeverity Severity { get; set; }
}
[DllImport("user32.dll")]
private static extern IntPtr GetActiveWindow();
[DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref DWM_WINDOW_CORNER_PREFERENCE pref, int attrLen);
private const float POSITION_SPEED = 10f;
private const float POSITION_THRESHOLD = 0.1f;
private const float ROOT_CORNER_RADIUS = 9.2f;
private const float ROOT_BORDER_WIDTH = 2f;
public ToastData Data { get; set; }
public ToastArgs Args;
public event Action<Toast> OnClose;
private Rect currentPosition;
private VisualElement titleBar;
private Label titleLabel;
private Label messageLabel;
private Button closeButton;
private VisualElement contentContainer;
private ProgressBar lifetimeBar;
public void SetupWindow(ToastData toastData)
{
Data = toastData;
Args = toastData.ToastArgs;
position = this.GetEditorWindowPosition(Args.ToastPosition);
currentPosition = position;
titleLabel.text = Args.Title;
messageLabel.text = Args.Message;
contentContainer.Add(Args.CustomContent?.Invoke());
var backgroundColor = Args.Severity switch
{
ToastSeverity.Info => new Color(0.27f, 0.38f, 0.49f),
ToastSeverity.Warning => new Color(0.69f, 0.5f, 0.02f),
_ => new Color(0.49f, 0f, 0f)
};
lifetimeBar.lowValue = 0;
var datatoastArgs = Data.ToastArgs;
lifetimeBar.value = lifetimeBar.highValue = datatoastArgs.NoTimeOut ? 1f : datatoastArgs.LifeTime;
var lifetimeBarContainer = lifetimeBar.Children().First();
var lifetimeBarBackground = lifetimeBarContainer.Children().First();
var lifetimeBarFill = lifetimeBarBackground.Children().First();
if (lifetimeBarFill != null)
{
lifetimeBarFill.style.backgroundColor = backgroundColor;
}
titleBar.style.backgroundColor = backgroundColor;
closeButton.RegisterCallback<PointerEnterEvent>(evt =>
{
closeButton.style.backgroundColor = backgroundColor;
});
closeButton.RegisterCallback<PointerLeaveEvent>(evt =>
{
closeButton.style.backgroundColor = new Color(0.31f, 0.31f, 0.31f);
});
EnableRoundedCorners();
}
private void EnableRoundedCorners()
{
IntPtr hwnd = GetActiveWindow();
if (hwnd != IntPtr.Zero)
{
DWM_WINDOW_CORNER_PREFERENCE pref = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND;
DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, ref pref, sizeof(int));
}
else
{
Debug.LogWarning("Failed to get window handle.");
}
}
private void CreateGUI()
{
rootVisualElement.style.borderTopWidth = ROOT_BORDER_WIDTH;
rootVisualElement.style.borderBottomWidth = ROOT_BORDER_WIDTH;
rootVisualElement.style.borderLeftWidth = ROOT_BORDER_WIDTH;
rootVisualElement.style.borderRightWidth = ROOT_BORDER_WIDTH;
rootVisualElement.style.borderTopColor = Color.black;
rootVisualElement.style.borderBottomColor = Color.black;
rootVisualElement.style.borderLeftColor = Color.black;
rootVisualElement.style.borderRightColor = Color.black;
rootVisualElement.style.borderTopLeftRadius = ROOT_CORNER_RADIUS;
rootVisualElement.style.borderTopRightRadius = ROOT_CORNER_RADIUS;
rootVisualElement.style.borderBottomLeftRadius = ROOT_CORNER_RADIUS;
rootVisualElement.style.borderBottomRightRadius = ROOT_CORNER_RADIUS;
titleBar = new VisualElement
{
style =
{
height = 20f,
minHeight = 20f,
width = new StyleLength(Length.Percent(100)),
maxWidth = new StyleLength(Length.Percent(100)),
flexDirection = FlexDirection.Row,
borderBottomWidth = 1f,
borderBottomColor = Color.black
}
};
rootVisualElement.Add(titleBar);
{
titleLabel = new Label()
{
style =
{
flexGrow = 1f,
unityFontStyleAndWeight = FontStyle.Bold,
fontSize = 13f,
unityTextAlign = TextAnchor.MiddleLeft
}
};
titleBar.Add(titleLabel);
closeButton = new Button(Close)
{
text = "X",
style =
{
flexGrow = 0f,
width = 20f,
minWidth = 20f,
borderBottomWidth = 0f,
borderLeftWidth = 1f,
borderRightWidth = 0f,
borderTopWidth = 0f,
borderTopLeftRadius = 0f,
borderTopRightRadius = 0f,
borderBottomRightRadius = 0f,
borderBottomLeftRadius = 0f,
marginRight = 0f,
marginLeft = 0f,
marginTop = 0f,
marginBottom = 0f
}
};
titleBar.Add(closeButton);
}
lifetimeBar = new ProgressBar
{
style =
{
marginBottom = 0f,
marginLeft = 0f,
marginRight = 0f,
marginTop = 0f,
height = 6f,
minHeight = 6f,
maxHeight = 6f
}
};
rootVisualElement.Add(lifetimeBar);
var messageContainer = new VisualElement();
rootVisualElement.Add(messageContainer);
{
messageLabel = new Label
{
style =
{
marginTop = 2f,
marginBottom = 2f,
marginLeft = 2f,
marginRight = 2f,
}
};
messageContainer.Add(messageLabel);
}
contentContainer = new VisualElement();
rootVisualElement.Add(contentContainer);
}
public float GetHeight() => position.height;
public void UpdatePosition(Rect positionRect, float currentHeightOffset, double deltaTime)
{
var targetPosition = new Rect(positionRect.x, positionRect.y + currentHeightOffset, positionRect.width, positionRect.height);
if (Mathf.Abs(currentPosition.y - targetPosition.y) < POSITION_THRESHOLD)
{
currentPosition.y = targetPosition.y;
position = currentPosition;
}
else
{
var newY = Mathf.Lerp(
currentPosition.y,
targetPosition.y,
(float)(POSITION_SPEED * deltaTime)
);
currentPosition.y = newY;
currentPosition.x = targetPosition.x;
position = currentPosition;
}
}
public bool IsLifetimeOver()
{
if (Args.NoTimeOut) return false;
var currentLifeTime = Time.time - Data.TimeCreated;
lifetimeBar.value = Args.LifeTime - currentLifeTime;
return currentLifeTime > Args.LifeTime;
}
private void OnDestroy()
{
OnClose?.Invoke(this);
}
}
}