547 lines
20 KiB
C#
547 lines
20 KiB
C#
#nullable enable
|
|
using System.Runtime.CompilerServices;
|
|
using ImGuiNET;
|
|
using T3.Core.DataTypes.Vector;
|
|
using T3.Core.Utils;
|
|
using T3.Editor.App;
|
|
using T3.Editor.Gui.Styling;
|
|
using T3.Editor.Gui.UiHelpers;
|
|
using T3.Editor.Gui.Windows.Layouts;
|
|
using T3.Editor.Gui.Windows.TimeLine;
|
|
|
|
namespace T3.Editor.Gui.Interaction;
|
|
|
|
/// <summary>
|
|
/// Implements transformations and interactions for a canvas that can be zoomed and panned.
|
|
/// </summary>
|
|
/// <todo>
|
|
/// This should be refactored into two parts:
|
|
/// - struct ViewTransform {Scroll, Zoom} -> For all transforms and conversion operations
|
|
/// - sealed class InteractiveCanvas -> For transitions, interactions, etc.
|
|
/// Other canvas should not inherit from this but use composition.
|
|
/// IScalableCanvas should be removed
|
|
/// </todo>
|
|
public partial class ScalableCanvas
|
|
{
|
|
public ScalableCanvas(Vector2? initialScale = null)
|
|
{
|
|
if (initialScale == null)
|
|
return;
|
|
|
|
Scale = ScaleTarget = initialScale.Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This needs to be called by the inherited class before drawing its interface.
|
|
/// </summary>
|
|
public void UpdateCanvas(out InteractionState interactionState, T3Ui.EditingFlags flags = T3Ui.EditingFlags.None)
|
|
{
|
|
UpdateWindowRect();
|
|
HandleRequestedTransitions();
|
|
|
|
var io = ImGui.GetIO();
|
|
var mouse = new MouseState(io.MousePos, io.MouseDelta, io.MouseWheel);
|
|
|
|
DampScaling(io.DeltaTime);
|
|
|
|
HandleInteraction(flags, mouse, out var zoomed, out var panned);
|
|
interactionState = new InteractionState(panned, zoomed, mouse);
|
|
}
|
|
|
|
private void UpdateWindowRect()
|
|
{
|
|
if (FillMode == FillModes.FillWindow)
|
|
{
|
|
var paddingForFocusBorder = LayoutHandling.FocusMode ? 0 : 1;
|
|
|
|
WindowPos = ImGui.GetWindowPos() + paddingForFocusBorder * Vector2.One;
|
|
WindowSize = ImGui.GetWindowSize() - paddingForFocusBorder * 2 * Vector2.One;
|
|
}
|
|
else
|
|
{
|
|
WindowSize = ImGui.GetContentRegionAvail();
|
|
WindowPos = ImGui.GetCursorScreenPos();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert canvas position (e.g. of an Operator) into screen position
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public virtual Vector2 TransformPositionFloat(Vector2 posOnCanvas)
|
|
{
|
|
return (posOnCanvas - Scroll) * Scale + WindowPos;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public Vector2 TransformPosition(Vector2 posOnCanvas)
|
|
{
|
|
var v = TransformPositionFloat(posOnCanvas);
|
|
return new Vector2((int)v.X, (int)v.Y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert canvas position (e.g. of an Operator) to screen position
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public float TransformX(float xOnCanvas)
|
|
{
|
|
return TransformPosition(new Vector2(xOnCanvas, 0)).X;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert canvas position (e.g. of an Operator) to screen position
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public float TransformY(float yOnCanvas)
|
|
{
|
|
return TransformPositionFloat(new Vector2(0, yOnCanvas)).Y;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a screen space position (e.g. from mouse) to canvas coordinates
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public virtual Vector2 InverseTransformPositionFloat(Vector2 screenPos)
|
|
{
|
|
return (screenPos - WindowPos) / (Scale) + Scroll;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert screen position to canvas position
|
|
/// </summary>
|
|
public virtual float InverseTransformX(float xOnScreen)
|
|
{
|
|
return InverseTransformPositionFloat(new Vector2(xOnScreen, 0)).X;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert screen position to canvas position
|
|
/// </summary>
|
|
public float InverseTransformY(float yOnScreen)
|
|
{
|
|
return InverseTransformPositionFloat(new Vector2(0, yOnScreen)).Y;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a direction (e.g. MouseDelta) from ScreenSpace to Canvas
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public Vector2 TransformDirection(Vector2 vectorInCanvas)
|
|
{
|
|
return TransformPositionFloat(vectorInCanvas) -
|
|
TransformPositionFloat(new Vector2(0, 0));
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal Vector2 TransformDirectionFloored(Vector2 vectorInCanvas)
|
|
{
|
|
var s = TransformDirection(vectorInCanvas);
|
|
return new Vector2((int)s.X, (int)s.Y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a direction (e.g. MouseDelta) from ScreenSpace to Canvas
|
|
/// </summary>
|
|
/// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public Vector2 InverseTransformDirection(Vector2 vectorInScreen)
|
|
{
|
|
return vectorInScreen / (Scale);
|
|
}
|
|
|
|
public ImRect TransformRect(ImRect canvasRect)
|
|
{
|
|
// NOTE: We have to floor the size instead to min max position to avoid jitter
|
|
var min = TransformPositionFloat(canvasRect.Min);
|
|
var max = TransformPositionFloat(canvasRect.Max);
|
|
var size = max - min;
|
|
min.X = (int)min.X;
|
|
min.Y = (int)min.Y;
|
|
size.X = (int)size.X;
|
|
size.Y = (int)size.Y;
|
|
return new ImRect(min, min + size);
|
|
}
|
|
|
|
public ImRect InverseTransformRect(ImRect screenRect)
|
|
{
|
|
return new ImRect(InverseTransformPositionFloat(screenRect.Min),
|
|
InverseTransformPositionFloat(screenRect.Max));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Transform a canvas position to relative position within ImGui-window (e.g. to set ImGui context)
|
|
/// </summary>
|
|
public Vector2 ChildPosFromCanvas(Vector2 posOnCanvas)
|
|
{
|
|
return TransformPositionFloat(posOnCanvas) - WindowPos;
|
|
}
|
|
|
|
public Vector2 WindowPos { get; private set; }
|
|
public Vector2 WindowSize { get; private set; } = new Vector2(200, 200);
|
|
|
|
public Vector2 Scale { get; protected set; } = Vector2.One;
|
|
protected Vector2 ScaleTarget = Vector2.One;
|
|
|
|
//Vector2 IScalableCanvas.ScaleTarget => ScaleTarget;
|
|
|
|
public Vector2 Scroll { get; protected set; } = new(0.0f, 0.0f);
|
|
protected Vector2 ScrollTarget = new(0.0f, 0.0f);
|
|
|
|
private void DampScaling(float deltaTime)
|
|
{
|
|
var completed = Scale.X > 1000 || Math.Abs(Scroll.X - ScrollTarget.X) < 1f
|
|
&& Math.Abs(Scroll.Y - ScrollTarget.Y) < 1f
|
|
&& Math.Abs(Scale.X - ScaleTarget.X) < 0.05f
|
|
&& Math.Abs(Scale.Y - ScaleTarget.Y) < 0.05f;
|
|
|
|
if (completed)
|
|
{
|
|
Scroll = ScrollTarget;
|
|
Scale = ScaleTarget;
|
|
return;
|
|
}
|
|
|
|
// Damp scaling
|
|
var minInCanvas = Scroll;
|
|
var maxInCanvas = Scroll + WindowSize / Scale;
|
|
var minTargetInCanvas = ScrollTarget;
|
|
var maxTargetInCanvas = ScrollTarget + WindowSize / ScaleTarget;
|
|
|
|
var f = Math.Min(deltaTime / UserSettings.Config.ScrollSmoothing.Clamp(0.01f, 0.99f), 1);
|
|
|
|
var min = Vector2.Lerp(minInCanvas, minTargetInCanvas, f);
|
|
var max = Vector2.Lerp(maxInCanvas, maxTargetInCanvas, f);
|
|
Scale = WindowSize / (max - min);
|
|
Scroll = min;
|
|
|
|
if (float.IsNaN(ScaleTarget.X))
|
|
ScaleTarget.X = 1;
|
|
|
|
if (float.IsNaN(ScaleTarget.Y))
|
|
ScaleTarget.Y = 1;
|
|
|
|
if (float.IsNaN(Scale.X) || float.IsNaN(Scale.Y) || MathF.Sign(ScaleTarget.Y) != MathF.Sign(Scale.Y))
|
|
Scale = ScaleTarget;
|
|
|
|
if (float.IsNaN(ScrollTarget.X))
|
|
ScrollTarget.X = 0;
|
|
|
|
if (float.IsNaN(ScrollTarget.Y))
|
|
ScrollTarget.Y = 0;
|
|
|
|
if (float.IsNaN(Scroll.X) || float.IsNaN(Scroll.Y))
|
|
Scroll = ScrollTarget;
|
|
}
|
|
|
|
private void HandleInteraction(T3Ui.EditingFlags flags, in MouseState mouseState, out bool zoomed, out bool panned)
|
|
{
|
|
zoomed = false;
|
|
panned = false;
|
|
|
|
if (_draggedCanvas == this && !ImGui.IsMouseDragging(ImGuiMouseButton.Right))
|
|
_draggedCanvas = null;
|
|
|
|
// ImGuiHoveredFlags.RectOnly includes AllowWhenOverlappedByItem/ByWindow which 1.91
|
|
// forbids for IsWindowHovered (item-level flags). Use only the window-level allowed bits.
|
|
var isDirectlyHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows
|
|
| ImGuiHoveredFlags.AllowWhenBlockedByPopup
|
|
| ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
|
|
var isInteractable = isDirectlyHovered && !FrameStats.Last.OpenedPopupHovered;
|
|
var isPanning = _draggedCanvas == this;
|
|
var isAnotherWindowPanning = _draggedCanvas != null && _draggedCanvas != this
|
|
|| CustomComponents.IsAnotherWindowDragScrolling(this);
|
|
|
|
//DrawCanvasDebugInfos(mouseState.Position, isDirectlyHovered, isInteractable, isPanning);
|
|
|
|
if ((flags & T3Ui.EditingFlags.PreventMouseInteractions) != 0)
|
|
return;
|
|
|
|
var isAnotherItemActive = FrameStats.Last.OpenedPopupCapturedMouse; //ImGui.IsAnyItemActive();
|
|
|
|
if (
|
|
!(isAnotherWindowPanning || isAnotherItemActive)
|
|
&& (isInteractable || isPanning)
|
|
&& (flags & T3Ui.EditingFlags.PreventPanningWithMouse) == 0
|
|
&& ((
|
|
ImGui.IsMouseDragging(ImGuiMouseButton.Left) && ImGui.GetIO().KeyAlt && this is not TimeLineCanvas)
|
|
|| (!UserSettings.Config.MiddleMouseButtonZooms && ImGui.IsMouseDragging(ImGuiMouseButton.Middle) && !ImGui.GetIO().KeyAlt)
|
|
|| (ImGui.IsMouseDragging(ImGuiMouseButton.Right) && !ImGui.GetIO().KeyAlt))
|
|
)
|
|
{
|
|
var parentScaleTarget = Parent?.ScaleTarget ?? Vector2.One;
|
|
ScrollTarget -= TransformPanDelta(mouseState.Delta) / (parentScaleTarget * ScaleTarget);
|
|
_draggedCanvas = this;
|
|
}
|
|
|
|
var preventZoom = (flags & T3Ui.EditingFlags.PreventZoomWithMouseWheel) != 0;
|
|
if (isInteractable && !preventZoom)
|
|
{
|
|
if (UserSettings.Config.UseTouchPadPanning)
|
|
{
|
|
if (isDirectlyHovered)
|
|
{
|
|
ScrollTarget += new Vector2(ImGui.GetIO().MouseWheelH * 220,
|
|
-ImGui.GetIO().MouseWheel * 220) / ScaleTarget;
|
|
//MouseWheelPanning.Delta * new Vector2(-1,1) / ScaleTarget;
|
|
var zoomFactor= ComputeZoomDeltaFromMouseWheel(MouseWheelPanning.PinchZoomDelta);
|
|
ApplyZoomDelta(mouseState.Position, zoomFactor, out zoomed);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ZoomWithMouseWheel(mouseState, out zoomed);
|
|
}
|
|
}
|
|
|
|
ZoomWithDrag();
|
|
ScaleTarget = ClampScaleToValidRange(ScaleTarget);
|
|
}
|
|
|
|
private static ScalableCanvas? _draggedCanvas;
|
|
internal static bool IsAnyCanvasDragged => _draggedCanvas != null;
|
|
|
|
internal protected virtual Vector2 ClampScaleToValidRange(Vector2 scale)
|
|
{
|
|
if (IsCurveCanvas)
|
|
return scale;
|
|
|
|
return this is TimeLineCanvas
|
|
? new Vector2(scale.X.Clamp(0.01f, 5000), scale.Y.Clamp(0.01f, 5000))
|
|
: new Vector2(scale.X.Clamp(0.02f, 40), scale.Y.Clamp(0.02f, 40));
|
|
}
|
|
|
|
public void ZoomWithMouseWheel(MouseState mouseState, out bool zoomed)
|
|
{
|
|
|
|
var ioMouseWheel = mouseState.ScrollWheel;
|
|
if (ioMouseWheel == 0)
|
|
{
|
|
zoomed = false;
|
|
return;
|
|
}
|
|
//return 1;
|
|
var zoomDelta = ComputeZoomDeltaFromMouseWheel(ioMouseWheel);
|
|
ApplyZoomDelta(mouseState.Position, zoomDelta, out zoomed);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hook that lets subclasses constrain which axes of a mouse pan actually apply.
|
|
/// Default: identity (both axes pan). Return Vector2(delta.X, 0) to disable Y pan, etc.
|
|
/// </summary>
|
|
protected virtual Vector2 TransformPanDelta(Vector2 delta) => delta;
|
|
|
|
/// <summary>
|
|
/// Copy the U (X) components of scale/scroll (and their damping targets) from another
|
|
/// canvas. Used so timeline panes stay time-aligned when the user zooms/pans U in either.
|
|
/// </summary>
|
|
internal void SyncXFrom(ScalableCanvas other)
|
|
{
|
|
Scale = new Vector2(other.Scale.X, Scale.Y);
|
|
Scroll = new Vector2(other.Scroll.X, Scroll.Y);
|
|
ScaleTarget = new Vector2(other.ScaleTarget.X, ScaleTarget.Y);
|
|
ScrollTarget = new Vector2(other.ScrollTarget.X, ScrollTarget.Y);
|
|
}
|
|
|
|
/// <summary>Inverse of <see cref="SyncXFrom"/>: push this canvas's U state back to another.</summary>
|
|
internal void SyncXTo(ScalableCanvas other)
|
|
{
|
|
other.Scale = new Vector2(Scale.X, other.Scale.Y);
|
|
other.Scroll = new Vector2(Scroll.X, other.Scroll.Y);
|
|
other.ScaleTarget = new Vector2(ScaleTarget.X, other.ScaleTarget.Y);
|
|
other.ScrollTarget = new Vector2(ScrollTarget.X, other.ScrollTarget.Y);
|
|
}
|
|
|
|
/// <summary>Current (damped) view scope — counterpart to <see cref="GetTargetScope"/>.</summary>
|
|
internal CanvasScope GetCurrentScope() => new() { Scale = Scale, Scroll = Scroll };
|
|
|
|
internal void SetCurrentScope(CanvasScope s)
|
|
{
|
|
Scale = s.Scale;
|
|
Scroll = s.Scroll;
|
|
}
|
|
|
|
internal void SetWindowRect(Vector2 pos, Vector2 size)
|
|
{
|
|
WindowPos = pos;
|
|
WindowSize = size;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies current scope, target scope, and viewport rect from another canvas.
|
|
/// Used to temporarily redirect the outer canvas's view to a sub-canvas's so downstream
|
|
/// code that reads <c>TimeLineCanvas.Current.*</c> for transforms lands in the sub's child.
|
|
/// Caller is responsible for capturing/restoring the previous scope/rect.
|
|
/// </summary>
|
|
internal void AdoptViewFrom(ScalableCanvas other)
|
|
{
|
|
SetCurrentScope(other.GetCurrentScope());
|
|
SetTargetScope(other.GetTargetScope());
|
|
SetWindowRect(other.WindowPos, other.WindowSize);
|
|
}
|
|
|
|
protected virtual void ApplyZoomDelta(Vector2 position, float zoomDelta, out bool zoomed)
|
|
{
|
|
zoomed = false;
|
|
if (Math.Abs(zoomDelta - 1) < 0.001f)
|
|
return;
|
|
|
|
var clamped = ClampScaleToValidRange(ScaleTarget * zoomDelta);
|
|
if (clamped == ScaleTarget)
|
|
return;
|
|
|
|
if (Math.Abs(zoomDelta - 1) < 0.001f)
|
|
return;
|
|
|
|
var zoom = zoomDelta * Vector2.One;
|
|
if (IsCurveCanvas)
|
|
{
|
|
if (ImGui.GetIO().KeyAlt)
|
|
{
|
|
zoom.X = 1;
|
|
}
|
|
else if (ImGui.GetIO().KeyShift)
|
|
{
|
|
zoom.Y = 1;
|
|
}
|
|
}
|
|
|
|
ScaleTarget *= zoom;
|
|
|
|
if (Math.Abs(zoomDelta) > 0.1f)
|
|
zoomed = true;
|
|
|
|
var focusCenterOnCanvas = InverseTransformPositionFloat(position);
|
|
ScrollTarget += (focusCenterOnCanvas - ScrollTarget) * (zoom - Vector2.One) / zoom;
|
|
}
|
|
|
|
private void DrawCanvasDebugInfos(Vector2 mousePos, bool directlyHovered, bool isInteractable, bool isAlreadyActive)
|
|
{
|
|
var focusCenterOnCanvas = InverseTransformPositionFloat(mousePos);
|
|
var dl = ImGui.GetForegroundDrawList();
|
|
|
|
var wp = ImGui.GetWindowPos();
|
|
if (directlyHovered)
|
|
{
|
|
var focusOnScreen = TransformPosition(focusCenterOnCanvas);
|
|
dl.AddCircle(focusOnScreen, 30, Color.Green.Fade(0.2f));
|
|
dl.AddText(focusOnScreen + new Vector2(10, -10), Color.Green, $"{focusCenterOnCanvas.X:0.0} {focusCenterOnCanvas.Y:0.0} ");
|
|
dl.AddRect(wp, wp + ImGui.GetWindowSize(), Color.Green.Fade(0.4f));
|
|
}
|
|
|
|
dl.AddRectFilled(wp, wp + new Vector2(200, 100), UiColors.WindowBackground.Fade(0.4f));
|
|
dl.AddText(wp + new Vector2(160, 0), Color.Green, $"hovered:{directlyHovered}\ninteractable:{isInteractable}\nactive:{isAlreadyActive}");
|
|
|
|
dl.AddText(wp + new Vector2(0, 0), Color.Green, $"Scale: {ScaleTarget.X:0.0} {ScaleTarget.Y:0.0} ");
|
|
dl.AddText(wp + new Vector2(0, 16), Color.Green, $"Scroll: {ScrollTarget.X:0.0} {ScrollTarget.Y:0.0} ");
|
|
dl.AddText(wp + new Vector2(0, 32), Color.Green, $"Canvas: {focusCenterOnCanvas.X:0.0} {focusCenterOnCanvas.Y:0.0} ");
|
|
var hovered = ImGui.IsWindowHovered() ? "hovered" : "";
|
|
var hoveredChild = ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows) ? "hoveredChildWindows" : "";
|
|
dl.AddText(wp + new Vector2(0, 48), Color.Green, $"{hovered} {hoveredChild}");
|
|
|
|
var focused = ImGui.IsWindowFocused() ? "focused" : "";
|
|
var focusedChild = ImGui.IsWindowFocused(ImGuiFocusedFlags.ChildWindows) ? "focusedChildWindows" : "";
|
|
dl.AddText(wp + new Vector2(0, 64), Color.Green, $"{focused} {focusedChild}");
|
|
|
|
// // Test window content region:
|
|
// var paddingForFocusBorder = 1;
|
|
// var pos = ImGui.GetCursorStartPos() + ImGui.GetWindowPos() + paddingForFocusBorder * Vector2.One;
|
|
// var size = (ImGui.GetWindowSize() - ImGui.GetStyle().WindowPadding) - ImGui.GetCursorStartPos() - paddingForFocusBorder * 2 * Vector2.One;
|
|
// dl.AddRect(pos, pos + size, Color.Red);
|
|
}
|
|
|
|
private bool IsCurveCanvas => Scale.Y < 0;
|
|
|
|
private static float ComputeZoomDeltaFromMouseWheel(float ioMouseWheel)
|
|
{
|
|
const float zoomSpeed = 1.2f;
|
|
var zoomSum = 1f;
|
|
|
|
if (ioMouseWheel < 0.0f)
|
|
{
|
|
for (var zoom = ioMouseWheel; zoom < 0.0f; zoom += 1.0f)
|
|
{
|
|
zoomSum /= zoomSpeed;
|
|
}
|
|
}
|
|
|
|
if (ioMouseWheel > 0.0f)
|
|
{
|
|
for (var zoom = ioMouseWheel; zoom > 0.0f; zoom -= 1.0f)
|
|
{
|
|
zoomSum *= zoomSpeed;
|
|
}
|
|
}
|
|
|
|
zoomSum = zoomSum.Clamp(0.02f, 100f);
|
|
return zoomSum;
|
|
}
|
|
|
|
private Vector2 _mousePosWhenDragZoomStarted;
|
|
private bool _isDragZooming;
|
|
private float _lastZoomDelta;
|
|
|
|
private void ZoomWithDrag()
|
|
{
|
|
var mouseButton = UserSettings.Config.MiddleMouseButtonZooms
|
|
? ImGuiMouseButton.Middle
|
|
: ImGuiMouseButton.Right;
|
|
|
|
var hotkeysMatch = UserSettings.Config.MiddleMouseButtonZooms || ImGui.GetIO().KeyShift;
|
|
|
|
if (ImGui.IsMouseClicked(mouseButton) && hotkeysMatch)
|
|
{
|
|
_isDragZooming = true;
|
|
_lastZoomDelta = 1;
|
|
_mousePosWhenDragZoomStarted = ImGui.GetMousePos();
|
|
}
|
|
|
|
if (ImGui.IsMouseReleased(mouseButton))
|
|
_isDragZooming = false;
|
|
|
|
if (!ImGui.IsMouseDragging(mouseButton, 0))
|
|
return;
|
|
|
|
var delta = ImGui.GetMousePos() - _mousePosWhenDragZoomStarted;
|
|
var deltaMax = Math.Abs(delta.X) > Math.Abs(delta.Y)
|
|
? -delta.X
|
|
: delta.Y;
|
|
|
|
if (!_isDragZooming)
|
|
return;
|
|
|
|
var f = (float)Math.Pow(1.13f, -deltaMax / 40f);
|
|
var delta2 = f / _lastZoomDelta;
|
|
ApplyZoomDelta(_mousePosWhenDragZoomStarted, delta2, out var zoomed); // FIXME: unclear what this does
|
|
_lastZoomDelta = f;
|
|
}
|
|
|
|
internal enum FillModes
|
|
{
|
|
FillWindow,
|
|
FillAvailableContentRegion,
|
|
}
|
|
|
|
internal FillModes FillMode = FillModes.FillWindow;
|
|
|
|
public bool EnableParentZoom { get; set; } = true;
|
|
|
|
protected virtual ScalableCanvas? Parent { get; }
|
|
|
|
public readonly record struct InteractionState(bool UserPannedCanvas, bool UserZoomedCanvas, MouseState MouseState);
|
|
|
|
// public static string PrintInteractionState(in InteractionState state)
|
|
// {
|
|
// var sb = new StringBuilder();
|
|
// sb.Append("Panned: ");
|
|
// sb.Append(state.UserPannedCanvas ? "Yes" : "No");
|
|
// sb.Append(" Zoomed: ");
|
|
// sb.Append(state.UserZoomedCanvas ? "Yes" : "No");
|
|
// sb.Append(" Mouse: ");
|
|
// sb.Append(state.MouseState.Position);
|
|
// var str = sb.ToString();
|
|
// Log.Debug(str);
|
|
// return str;
|
|
// }
|
|
}
|
|
|
|
public readonly record struct MouseState(Vector2 Position, Vector2 Delta, float ScrollWheel); |