Files
FlaxEngine/Source/Editor/Viewport/Previews/TexturePreview.cs
Ari Vuollet f09fd7ad34 Use exact value comparison in caching related functions
(cherry picked from commit 9d7c6b26422e127719836944d8d473910190e7d4)
2025-05-02 14:19:55 +03:00

679 lines
22 KiB
C#

// Copyright (c) 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 (_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);
}
}
}
}