The null-conditional operator checks for reference equality of the Object, but doesn't check the validity of the unmanaged pointer. This check is corrected in cases where the object was not immediately returned from the bindings layer and may have been destroyed earlier.
679 lines
22 KiB
C#
679 lines
22 KiB
C#
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
|
|
|
using System;
|
|
using FlaxEditor.GUI.ContextMenu;
|
|
using FlaxEditor.Viewport.Widgets;
|
|
using FlaxEngine;
|
|
using FlaxEngine.GUI;
|
|
using Object = FlaxEngine.Object;
|
|
|
|
namespace FlaxEditor.Viewport.Previews
|
|
{
|
|
/// <summary>
|
|
/// Base class for texture previews. Draws a surface in the UI and supports view moving/zooming.
|
|
/// </summary>
|
|
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
|
[HideInEditor]
|
|
public abstract class TexturePreviewBase : ContainerControl
|
|
{
|
|
private Rectangle _textureRect;
|
|
private Float2 _lastMousePos, _viewPos;
|
|
private float _viewScale = 1.0f;
|
|
private bool _isMouseDown;
|
|
|
|
/// <inheritdoc />
|
|
protected TexturePreviewBase()
|
|
{
|
|
AnchorPreset = AnchorPresets.StretchAll;
|
|
Offsets = Margin.Zero;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves the view to the center.
|
|
/// </summary>
|
|
public void CenterView()
|
|
{
|
|
_viewScale = 1.0f;
|
|
_viewPos = Float2.Zero;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the texture rectangle.
|
|
/// </summary>
|
|
protected void UpdateTextureRect()
|
|
{
|
|
CalculateTextureRect(out _textureRect);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the texture rectangle.
|
|
/// </summary>
|
|
/// <param name="rect">The rectangle.</param>
|
|
protected abstract void CalculateTextureRect(out Rectangle rect);
|
|
|
|
/// <summary>
|
|
/// Calculates the texture rect fr the given texture and the view size.
|
|
/// </summary>
|
|
/// <param name="textureSize">Size of the texture.</param>
|
|
/// <param name="viewSize">Size of the view.</param>
|
|
/// <param name="result">The result.</param>
|
|
protected static void CalculateTextureRect(Float2 textureSize, Float2 viewSize, out Rectangle result)
|
|
{
|
|
Float2 size = Float2.Max(textureSize, Float2.One);
|
|
float aspectRatio = size.X / size.Y;
|
|
float h = viewSize.X / aspectRatio;
|
|
float w = viewSize.Y * aspectRatio;
|
|
if (w > h)
|
|
{
|
|
float diff = (viewSize.Y - h) * 0.5f;
|
|
result = new Rectangle(0, diff, viewSize.X, h);
|
|
}
|
|
else
|
|
{
|
|
float diff = (viewSize.X - w) * 0.5f;
|
|
result = new Rectangle(diff, 0, w, viewSize.Y);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws the texture.
|
|
/// </summary>
|
|
/// <param name="rect">The target texture view rectangle.</param>
|
|
protected abstract void DrawTexture(ref Rectangle rect);
|
|
|
|
/// <summary>
|
|
/// Gets the texture view rect (scaled and offseted).
|
|
/// </summary>
|
|
protected Rectangle TextureViewRect => (_textureRect + _viewPos) * _viewScale;
|
|
|
|
/// <inheritdoc />
|
|
public override void Draw()
|
|
{
|
|
Render2D.PushClip(new Rectangle(Float2.Zero, Size));
|
|
|
|
// Calculate texture view rectangle
|
|
UpdateTextureRect();
|
|
var textureRect = TextureViewRect;
|
|
|
|
// Call drawing
|
|
DrawTexture(ref textureRect);
|
|
|
|
// Add overlay during debugger breakpoint hang
|
|
if (Editor.Instance.Simulation.IsDuringBreakpointHang)
|
|
{
|
|
var bounds = new Rectangle(Float2.Zero, Size);
|
|
Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f));
|
|
Render2D.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center);
|
|
}
|
|
|
|
Render2D.PopClip();
|
|
|
|
base.Draw();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnMouseEnter(Float2 location)
|
|
{
|
|
// Store mouse position
|
|
_lastMousePos = location;
|
|
|
|
base.OnMouseEnter(location);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnMouseMove(Float2 location)
|
|
{
|
|
// Check if mouse is down
|
|
if (_isMouseDown)
|
|
{
|
|
// Calculate mouse delta
|
|
var delta = location - _lastMousePos;
|
|
|
|
// Move view
|
|
_viewPos += delta;
|
|
}
|
|
|
|
// Store mouse position
|
|
_lastMousePos = location;
|
|
|
|
base.OnMouseMove(location);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnMouseLeave()
|
|
{
|
|
// Clear flag
|
|
_isMouseDown = false;
|
|
Cursor = CursorType.Default;
|
|
|
|
base.OnMouseLeave();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool OnMouseWheel(Float2 location, float delta)
|
|
{
|
|
if (base.OnMouseWheel(location, delta))
|
|
return true;
|
|
|
|
// Zoom
|
|
float prevScale = _viewScale;
|
|
_viewScale = Mathf.Clamp(_viewScale + delta * 0.24f, 0.001f, 20.0f);
|
|
|
|
// Compensate for the Rectangle.MakeScaled
|
|
var sizeDelta = (_viewScale - prevScale) * _textureRect.Size * 0.5f;
|
|
_viewPos += sizeDelta * 0.5f;
|
|
|
|
// Move to zoom position
|
|
var locationOnTexture = (location - _textureRect.Location) / _textureRect.Size;
|
|
_viewPos -= sizeDelta * locationOnTexture;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool OnMouseDown(Float2 location, MouseButton button)
|
|
{
|
|
if (base.OnMouseDown(location, button))
|
|
return true;
|
|
|
|
// Set flag
|
|
_isMouseDown = true;
|
|
_lastMousePos = location;
|
|
Cursor = CursorType.SizeAll;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool OnMouseUp(Float2 location, MouseButton button)
|
|
{
|
|
if (base.OnMouseUp(location, button))
|
|
return true;
|
|
|
|
// Clear flag
|
|
_isMouseDown = false;
|
|
Cursor = CursorType.Default;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void OnSizeChanged()
|
|
{
|
|
base.OnSizeChanged();
|
|
|
|
// Update texture rectangle and move view to the center
|
|
UpdateTextureRect();
|
|
CenterView();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Texture channel flags.
|
|
/// </summary>
|
|
[Flags, HideInEditor]
|
|
public enum ChannelFlags
|
|
{
|
|
/// <summary>
|
|
/// The none.
|
|
/// </summary>
|
|
None = 0,
|
|
|
|
/// <summary>
|
|
/// The red channel.
|
|
/// </summary>
|
|
Red = 1,
|
|
|
|
/// <summary>
|
|
/// The green channel.
|
|
/// </summary>
|
|
Green = 2,
|
|
|
|
/// <summary>
|
|
/// The blue channel.
|
|
/// </summary>
|
|
Blue = 4,
|
|
|
|
/// <summary>
|
|
/// The alpha channel.
|
|
/// </summary>
|
|
Alpha = 8,
|
|
|
|
/// <summary>
|
|
/// All texture channels.
|
|
/// </summary>
|
|
All = Red | Green | Blue | Alpha
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base class for texture previews with custom drawing features. Uses in-build postFx material to render a texture.
|
|
/// </summary>
|
|
/// <seealso cref="TexturePreviewBase" />
|
|
public abstract class TexturePreviewCustomBase : TexturePreviewBase
|
|
{
|
|
private ChannelFlags _channelFlags = ChannelFlags.All;
|
|
private bool _usePointSampler = false;
|
|
private float _mipLevel = -1;
|
|
private ContextMenu _mipWidgetMenu;
|
|
private ContextMenuButton _filterWidgetPointButton;
|
|
private ContextMenuButton _filterWidgetLinearButton;
|
|
|
|
/// <summary>
|
|
/// The preview material instance used to draw texture.
|
|
/// </summary>
|
|
protected MaterialInstance _previewMaterial;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the view channels to show.
|
|
/// </summary>
|
|
public ChannelFlags ViewChannels
|
|
{
|
|
get => _channelFlags;
|
|
set
|
|
{
|
|
if (_channelFlags != value)
|
|
{
|
|
_channelFlags = value;
|
|
UpdateMask();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether use point sampler when drawing the texture. The default value is false.
|
|
/// </summary>
|
|
public bool UsePointSampler
|
|
{
|
|
get => _usePointSampler;
|
|
set
|
|
{
|
|
if (_usePointSampler != value)
|
|
{
|
|
_usePointSampler = value;
|
|
_previewMaterial.SetParameterValue("PointSampler", value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the mip level to show. The default value is -1.
|
|
/// </summary>
|
|
public float MipLevel
|
|
{
|
|
get => _mipLevel;
|
|
set
|
|
{
|
|
if (!Mathf.NearEqual(_mipLevel, value))
|
|
{
|
|
_mipLevel = value;
|
|
_previewMaterial.SetParameterValue("Mip", value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
/// <param name="useWidgets">True if show viewport widgets.</param>
|
|
protected TexturePreviewCustomBase(bool useWidgets)
|
|
{
|
|
// Create preview material (virtual)
|
|
var baseMaterial = FlaxEngine.Content.LoadAsyncInternal<Material>("Editor/TexturePreviewMaterial");
|
|
if (baseMaterial == null)
|
|
throw new Exception("Cannot load texture preview material.");
|
|
_previewMaterial = baseMaterial.CreateVirtualInstance();
|
|
if (_previewMaterial == null)
|
|
throw new Exception("Failed to create virtual material instance for preview material.");
|
|
|
|
// Add widgets
|
|
if (useWidgets)
|
|
{
|
|
// Channels widget
|
|
var channelsWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
|
|
//
|
|
var channelR = new ViewportWidgetButton("R", SpriteHandle.Invalid, null, true)
|
|
{
|
|
Checked = true,
|
|
TooltipText = "Show/hide texture red channel",
|
|
Parent = channelsWidget
|
|
};
|
|
channelR.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Red : (ViewChannels & ~ChannelFlags.Red);
|
|
var channelG = new ViewportWidgetButton("G", SpriteHandle.Invalid, null, true)
|
|
{
|
|
Checked = true,
|
|
TooltipText = "Show/hide texture green channel",
|
|
Parent = channelsWidget
|
|
};
|
|
channelG.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Green : (ViewChannels & ~ChannelFlags.Green);
|
|
var channelB = new ViewportWidgetButton("B", SpriteHandle.Invalid, null, true)
|
|
{
|
|
Checked = true,
|
|
TooltipText = "Show/hide texture blue channel",
|
|
Parent = channelsWidget
|
|
};
|
|
channelB.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Blue : (ViewChannels & ~ChannelFlags.Blue);
|
|
var channelA = new ViewportWidgetButton("A", SpriteHandle.Invalid, null, true)
|
|
{
|
|
Checked = true,
|
|
TooltipText = "Show/hide texture alpha channel",
|
|
Parent = channelsWidget
|
|
};
|
|
channelA.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Alpha : (ViewChannels & ~ChannelFlags.Alpha);
|
|
//
|
|
channelsWidget.Parent = this;
|
|
|
|
// Mip widget
|
|
var mipWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
|
|
_mipWidgetMenu = new ContextMenu();
|
|
_mipWidgetMenu.VisibleChanged += OnMipWidgetMenuOnVisibleChanged;
|
|
var mipWidgetButton = new ViewportWidgetButton("Mip", SpriteHandle.Invalid, _mipWidgetMenu)
|
|
{
|
|
TooltipText = "The mip level to show. The default is -1.",
|
|
Parent = mipWidget
|
|
};
|
|
//
|
|
mipWidget.Parent = this;
|
|
|
|
// Filter widget
|
|
var filterWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
|
|
var filterWidgetMenu = new ContextMenu();
|
|
filterWidgetMenu.VisibleChanged += OnFilterWidgetMenuVisibleChanged;
|
|
_filterWidgetPointButton = filterWidgetMenu.AddButton("Point", () => UsePointSampler = true);
|
|
_filterWidgetLinearButton = filterWidgetMenu.AddButton("Linear", () => UsePointSampler = false);
|
|
var filterWidgetButton = new ViewportWidgetButton("Filter", SpriteHandle.Invalid, filterWidgetMenu)
|
|
{
|
|
TooltipText = "The texture preview filtering mode. The default is Linear.",
|
|
Parent = filterWidget
|
|
};
|
|
//
|
|
filterWidget.Parent = this;
|
|
}
|
|
|
|
// Wait for base (don't want to async material parameters set due to async loading)
|
|
baseMaterial.WaitForLoaded();
|
|
}
|
|
|
|
private void OnFilterWidgetMenuVisibleChanged(Control control)
|
|
{
|
|
if (!control.Visible)
|
|
return;
|
|
|
|
_filterWidgetPointButton.Checked = UsePointSampler;
|
|
_filterWidgetLinearButton.Checked = !UsePointSampler;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the texture to draw (material parameter).
|
|
/// </summary>
|
|
/// <param name="value">The value.</param>
|
|
protected void SetTexture(object value)
|
|
{
|
|
_previewMaterial.SetParameterValue("Texture", value);
|
|
UpdateTextureRect();
|
|
}
|
|
|
|
private void OnMipWidgetMenuOnVisibleChanged(Control control)
|
|
{
|
|
if (!control.Visible)
|
|
return;
|
|
|
|
var textureObj = _previewMaterial.GetParameterValue("Texture");
|
|
|
|
if (textureObj is TextureBase texture && !texture.WaitForLoaded())
|
|
{
|
|
_mipWidgetMenu.ItemsContainer.DisposeChildren();
|
|
var mipLevels = texture.MipLevels;
|
|
for (int i = -1; i < mipLevels; i++)
|
|
{
|
|
var button = _mipWidgetMenu.AddButton(i.ToString(), OnMipWidgetClicked);
|
|
button.Tag = i;
|
|
if (i == -1)
|
|
button.TooltipText = "Default mip.";
|
|
button.Checked = Mathf.Abs(MipLevel - i) < 0.9f;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnMipWidgetClicked(ContextMenuButton button)
|
|
{
|
|
MipLevel = (int)button.Tag;
|
|
}
|
|
|
|
private void UpdateMask()
|
|
{
|
|
Vector4 mask = Vector4.One;
|
|
if ((_channelFlags & ChannelFlags.Red) == 0)
|
|
mask.X = 0;
|
|
if ((_channelFlags & ChannelFlags.Green) == 0)
|
|
mask.Y = 0;
|
|
if ((_channelFlags & ChannelFlags.Blue) == 0)
|
|
mask.Z = 0;
|
|
if ((_channelFlags & ChannelFlags.Alpha) == 0)
|
|
mask.W = 0;
|
|
_previewMaterial.SetParameterValue("Mask", mask);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void PerformLayoutBeforeChildren()
|
|
{
|
|
base.PerformLayoutBeforeChildren();
|
|
|
|
ViewportWidgetsContainer.ArrangeWidgets(this);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnDestroy()
|
|
{
|
|
Object.Destroy(ref _previewMaterial);
|
|
|
|
base.OnDestroy();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Texture preview GUI control. Draws <see cref="FlaxEngine.Texture"/> in the UI and supports view moving/zomming.
|
|
/// </summary>
|
|
/// <seealso cref="TexturePreviewBase" />
|
|
public class SimpleTexturePreview : TexturePreviewBase
|
|
{
|
|
private Texture _asset;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the asset to preview.
|
|
/// </summary>
|
|
public Texture Asset
|
|
{
|
|
get => _asset;
|
|
set
|
|
{
|
|
if (_asset != value)
|
|
{
|
|
_asset = value;
|
|
UpdateTextureRect();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the color used to multiply texture colors.
|
|
/// </summary>
|
|
public Color Color { get; set; } = Color.White;
|
|
|
|
/// <inheritdoc />
|
|
protected override void CalculateTextureRect(out Rectangle rect)
|
|
{
|
|
CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void DrawTexture(ref Rectangle rect)
|
|
{
|
|
// Background
|
|
Render2D.FillRectangle(rect, Color.Gray);
|
|
|
|
// Check if has loaded asset
|
|
if (_asset && _asset.IsLoaded)
|
|
{
|
|
Render2D.DrawTexture(_asset, rect, Color);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sprite atlas preview GUI control. Draws <see cref="SpriteAtlas"/> in the UI and supports view moving/zomming.
|
|
/// </summary>
|
|
/// <seealso cref="TexturePreviewBase" />
|
|
public class SimpleSpriteAtlasPreview : TexturePreviewBase
|
|
{
|
|
private SpriteAtlas _asset;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the asset to preview.
|
|
/// </summary>
|
|
public SpriteAtlas Asset
|
|
{
|
|
get => _asset;
|
|
set
|
|
{
|
|
if (_asset != value)
|
|
{
|
|
_asset = value;
|
|
UpdateTextureRect();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the color used to multiply texture colors.
|
|
/// </summary>
|
|
public Color Color { get; set; } = Color.White;
|
|
|
|
/// <inheritdoc />
|
|
protected override void CalculateTextureRect(out Rectangle rect)
|
|
{
|
|
CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void DrawTexture(ref Rectangle rect)
|
|
{
|
|
// Background
|
|
Render2D.FillRectangle(rect, Color.Gray);
|
|
|
|
// Check if has loaded asset
|
|
if (_asset && _asset.IsLoaded)
|
|
{
|
|
Render2D.DrawTexture(_asset, rect, Color);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Texture preview GUI control. Draws <see cref="FlaxEngine.Texture"/> in the UI and supports view moving/zooming.
|
|
/// Supports texture channels masking and color transformations.
|
|
/// </summary>
|
|
/// <seealso cref="TexturePreviewCustomBase" />
|
|
public class TexturePreview : TexturePreviewCustomBase
|
|
{
|
|
private TextureBase _asset;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the texture to preview.
|
|
/// </summary>
|
|
public TextureBase Asset
|
|
{
|
|
get => _asset;
|
|
set
|
|
{
|
|
if (_asset != value)
|
|
{
|
|
_asset = value;
|
|
SetTexture(_asset);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="TexturePreview"/> class.
|
|
/// </summary>
|
|
/// <param name="useWidgets">True if show viewport widgets.</param>
|
|
/// <inheritdoc />
|
|
public TexturePreview(bool useWidgets)
|
|
: base(useWidgets)
|
|
{
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void CalculateTextureRect(out Rectangle rect)
|
|
{
|
|
CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void DrawTexture(ref Rectangle rect)
|
|
{
|
|
// Background
|
|
Render2D.FillRectangle(rect, Color.Gray);
|
|
|
|
// Check if has loaded asset
|
|
if (_asset && _asset.IsLoaded)
|
|
{
|
|
Render2D.DrawMaterial(_previewMaterial, rect);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sprite atlas preview GUI control. Draws <see cref="FlaxEngine.SpriteAtlas"/> in the UI and supports view moving/zomming.
|
|
/// Supports texture channels masking and color transformations.
|
|
/// </summary>
|
|
/// <seealso cref="TexturePreviewCustomBase" />
|
|
public class SpriteAtlasPreview : TexturePreviewCustomBase
|
|
{
|
|
private SpriteAtlas _asset;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the sprite atlas to preview.
|
|
/// </summary>
|
|
public SpriteAtlas Asset
|
|
{
|
|
get => _asset;
|
|
set
|
|
{
|
|
if (_asset != value)
|
|
{
|
|
_asset = value;
|
|
SetTexture(_asset);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SpriteAtlasPreview"/> class.
|
|
/// </summary>
|
|
/// <param name="useWidgets">True if show viewport widgets.</param>
|
|
/// <inheritdoc />
|
|
public SpriteAtlasPreview(bool useWidgets)
|
|
: base(useWidgets)
|
|
{
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void CalculateTextureRect(out Rectangle rect)
|
|
{
|
|
CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void DrawTexture(ref Rectangle rect)
|
|
{
|
|
// Background
|
|
Render2D.FillRectangle(rect, Color.Gray);
|
|
|
|
// Check if has loaded asset
|
|
if (_asset && _asset.IsLoaded)
|
|
{
|
|
Render2D.DrawMaterial(_previewMaterial, rect);
|
|
}
|
|
}
|
|
}
|
|
}
|