Files
OpenRA/OpenRA.Mods.Common/Widgets/ViewportControllerWidget.cs
RoosterDragon d83e579dfe Fix CA1305
2023-08-07 21:38:09 +02:00

501 lines
16 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Lint;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets
{
public enum WorldTooltipType { None, Unexplored, Actor, FrozenActor, Resource }
public class ViewportControllerWidget : Widget
{
readonly ModData modData;
readonly IEnumerable<IResourceRenderer> resourceRenderers;
public readonly HotkeyReference ZoomInKey = new();
public readonly HotkeyReference ZoomOutKey = new();
public readonly HotkeyReference ScrollUpKey = new();
public readonly HotkeyReference ScrollDownKey = new();
public readonly HotkeyReference ScrollLeftKey = new();
public readonly HotkeyReference ScrollRightKey = new();
public readonly HotkeyReference JumpToTopEdgeKey = new();
public readonly HotkeyReference JumpToBottomEdgeKey = new();
public readonly HotkeyReference JumpToLeftEdgeKey = new();
public readonly HotkeyReference JumpToRightEdgeKey = new();
// Note: LinterHotkeyNames assumes that these are disabled by default
public readonly string BookmarkSaveKeyPrefix = null;
public readonly string BookmarkRestoreKeyPrefix = null;
public readonly int BookmarkKeyCount = 0;
public readonly string TooltipTemplate = "WORLD_TOOLTIP";
public readonly string TooltipContainer;
public WorldTooltipType TooltipType { get; private set; }
public ITooltip ActorTooltip { get; private set; }
public IProvideTooltipInfo[] ActorTooltipExtra { get; private set; }
public FrozenActor FrozenActorTooltip { get; private set; }
public string ResourceTooltip { get; private set; }
static readonly Dictionary<ScrollDirection, string> ScrollCursors = new()
{
{ ScrollDirection.Up | ScrollDirection.Left, "scroll-tl" },
{ ScrollDirection.Up | ScrollDirection.Right, "scroll-tr" },
{ ScrollDirection.Down | ScrollDirection.Left, "scroll-bl" },
{ ScrollDirection.Down | ScrollDirection.Right, "scroll-br" },
{ ScrollDirection.Up, "scroll-t" },
{ ScrollDirection.Down, "scroll-b" },
{ ScrollDirection.Left, "scroll-l" },
{ ScrollDirection.Right, "scroll-r" },
};
static readonly Dictionary<ScrollDirection, string> JoystickCursors = new()
{
{ ScrollDirection.Up | ScrollDirection.Left, "joystick-tl-blocked" },
{ ScrollDirection.Up | ScrollDirection.Right, "joystick-tr-blocked" },
{ ScrollDirection.Down | ScrollDirection.Left, "joystick-bl-blocked" },
{ ScrollDirection.Down | ScrollDirection.Right, "joystick-br-blocked" },
{ ScrollDirection.Up, "joystick-t-blocked" },
{ ScrollDirection.Down, "joystick-b-blocked" },
{ ScrollDirection.Left, "joystick-l-blocked" },
{ ScrollDirection.Right, "joystick-r-blocked" },
};
static readonly Dictionary<ScrollDirection, float2> ScrollOffsets = new()
{
{ ScrollDirection.Up, new float2(0, -1) },
{ ScrollDirection.Down, new float2(0, 1) },
{ ScrollDirection.Left, new float2(-1, 0) },
{ ScrollDirection.Right, new float2(1, 0) },
};
readonly Lazy<TooltipContainerWidget> tooltipContainer;
readonly World world;
readonly WorldRenderer worldRenderer;
int2? joystickScrollStart, joystickScrollEnd;
int2? standardScrollStart;
bool isStandardScrolling;
ScrollDirection keyboardDirections;
ScrollDirection edgeDirections;
HotkeyReference[] saveBookmarkHotkeys;
HotkeyReference[] restoreBookmarkHotkeys;
WPos?[] bookmarkPositions;
[CustomLintableHotkeyNames]
public static IEnumerable<string> LinterHotkeyNames(MiniYamlNode widgetNode, Action<string> emitError)
{
var savePrefix = "";
var savePrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "BookmarkSaveKeyPrefix");
if (savePrefixNode != null)
savePrefix = savePrefixNode.Value.Value;
var restorePrefix = "";
var restorePrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "BookmarkRestoreKeyPrefix");
if (restorePrefixNode != null)
restorePrefix = restorePrefixNode.Value.Value;
var count = 0;
var countNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "BookmarkKeyCount");
if (countNode != null)
count = FieldLoader.GetValue<int>("BookmarkKeyCount", countNode.Value.Value);
if (count == 0)
yield break;
if (string.IsNullOrEmpty(savePrefix))
emitError($"{widgetNode.Location} must define BookmarkSaveKeyPrefix if BookmarkKeyCount > 0.");
if (string.IsNullOrEmpty(restorePrefix))
emitError($"{widgetNode.Location} must define BookmarkRestoreKeyPrefix if BookmarkKeyCount > 0.");
for (var i = 0; i < count; i++)
{
var suffix = (i + 1).ToStringInvariant("D2");
yield return savePrefix + suffix;
yield return restorePrefix + suffix;
}
}
[ObjectCreator.UseCtor]
public ViewportControllerWidget(ModData modData, World world, WorldRenderer worldRenderer)
{
this.modData = modData;
this.world = world;
this.worldRenderer = worldRenderer;
tooltipContainer = Exts.Lazy(() =>
Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
resourceRenderers = world.WorldActor.TraitsImplementing<IResourceRenderer>().ToArray();
}
public override void Initialize(WidgetArgs args)
{
base.Initialize(args);
saveBookmarkHotkeys = Exts.MakeArray(BookmarkKeyCount,
i => modData.Hotkeys[BookmarkSaveKeyPrefix + (i + 1).ToStringInvariant("D2")]);
restoreBookmarkHotkeys = Exts.MakeArray(BookmarkKeyCount,
i => modData.Hotkeys[BookmarkRestoreKeyPrefix + (i + 1).ToStringInvariant("D2")]);
bookmarkPositions = new WPos?[BookmarkKeyCount];
}
public override void MouseEntered()
{
if (TooltipContainer == null)
return;
tooltipContainer.Value.SetTooltip(TooltipTemplate,
new WidgetArgs() { { "world", world }, { "viewport", this } });
}
public override void MouseExited()
{
if (TooltipContainer == null)
return;
tooltipContainer.Value.RemoveTooltip();
}
long lastScrollTime = 0;
public override void Draw()
{
if (IsJoystickScrolling)
{
// Base the JoystickScrolling speed on the Scroll Speed slider
var rate = 0.01f * Game.Settings.Game.ViewportEdgeScrollStep;
var scroll = (joystickScrollEnd.Value - joystickScrollStart.Value).ToFloat2() * rate;
worldRenderer.Viewport.Scroll(scroll, false);
}
else if (!isStandardScrolling)
{
edgeDirections = ScrollDirection.None;
if (Game.Settings.Game.ViewportEdgeScroll && Game.Renderer.WindowHasInputFocus)
edgeDirections = CheckForDirections();
if (keyboardDirections != ScrollDirection.None || edgeDirections != ScrollDirection.None)
{
var scroll = float2.Zero;
foreach (var kv in ScrollOffsets)
if (keyboardDirections.Includes(kv.Key) || edgeDirections.Includes(kv.Key))
scroll += kv.Value;
// Scroll rate is defined for a 40ms interval
var deltaScale = Math.Min(Game.RunTime - lastScrollTime, 25f);
var length = Math.Max(1, scroll.Length);
scroll *= deltaScale / (25 * length) * Game.Settings.Game.ViewportEdgeScrollStep;
worldRenderer.Viewport.Scroll(scroll, false);
lastScrollTime = Game.RunTime;
}
}
UpdateMouseover();
base.Draw();
}
public void UpdateMouseover()
{
TooltipType = WorldTooltipType.None;
ActorTooltipExtra = null;
var modifiers = Game.GetModifierKeys();
var cell = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos);
if (!world.Map.Contains(cell))
return;
if (world.ShroudObscures(cell))
{
TooltipType = WorldTooltipType.Unexplored;
return;
}
var worldPixel = worldRenderer.Viewport.ViewToWorldPx(Viewport.LastMousePos);
var underCursor = world.ScreenMap.ActorsAtMouse(worldPixel)
.Where(a => a.Actor.Info.HasTraitInfo<ITooltipInfo>() && !world.FogObscures(a.Actor))
.WithHighestSelectionPriority(worldPixel, modifiers);
if (underCursor != null)
{
ActorTooltip = underCursor.TraitsImplementing<ITooltip>().FirstEnabledTraitOrDefault();
if (ActorTooltip != null)
{
ActorTooltipExtra = underCursor.TraitsImplementing<IProvideTooltipInfo>().ToArray();
TooltipType = WorldTooltipType.Actor;
}
return;
}
var frozen = world.ScreenMap.FrozenActorsAtMouse(world.RenderPlayer, worldPixel)
.Where(a => a.TooltipInfo != null && a.IsValid && a.Visible && !a.Hidden)
.WithHighestSelectionPriority(worldPixel, modifiers);
if (frozen != null)
{
FrozenActorTooltip = frozen;
// HACK: This leaks the tooltip state through the fog
// This will cause issues for any downstream mods that use IProvideTooltipInfo on enemy actors
if (frozen.Actor != null)
ActorTooltipExtra = frozen.Actor.TraitsImplementing<IProvideTooltipInfo>().ToArray();
TooltipType = WorldTooltipType.FrozenActor;
return;
}
foreach (var resourceRenderer in resourceRenderers)
{
var resourceTooltip = resourceRenderer.GetRenderedResourceTooltip(cell);
if (resourceTooltip != null)
{
TooltipType = WorldTooltipType.Resource;
ResourceTooltip = resourceTooltip;
break;
}
}
}
public override string GetCursor(int2 pos)
{
if (!(IsJoystickScrolling || isStandardScrolling) &&
(!Game.Settings.Game.ViewportEdgeScroll || Ui.MouseOverWidget != this))
return null;
var blockedDirections = worldRenderer.Viewport.GetBlockedDirections();
if (IsJoystickScrolling || isStandardScrolling)
{
foreach (var dir in JoystickCursors)
if (blockedDirections.Includes(dir.Key))
return dir.Value;
return "joystick-all";
}
foreach (var dir in ScrollCursors)
if (edgeDirections.Includes(dir.Key))
return dir.Value + (blockedDirections.Includes(dir.Key) ? "-blocked" : "");
return null;
}
bool IsJoystickScrolling =>
joystickScrollStart.HasValue && joystickScrollEnd.HasValue &&
(joystickScrollStart.Value - joystickScrollEnd.Value).Length > Game.Settings.Game.MouseScrollDeadzone;
public override bool HandleMouseInput(MouseInput mi)
{
if (mi.Event == MouseInputEvent.Scroll && mi.Modifiers.HasModifier(Game.Settings.Game.ZoomModifier))
{
worldRenderer.Viewport.AdjustZoom(mi.Delta.Y * Game.Settings.Game.ZoomSpeed, mi.Location);
return true;
}
var gs = Game.Settings.Game;
var scrollButton = gs.UseClassicMouseStyle ^ gs.UseAlternateScrollButton ? MouseButton.Right : MouseButton.Middle;
var scrollType = mi.Button.HasFlag(scrollButton) ? gs.MouseScroll : MouseScrollType.Disabled;
if (scrollType == MouseScrollType.Disabled)
return IsJoystickScrolling || isStandardScrolling;
if (scrollType == MouseScrollType.Standard || scrollType == MouseScrollType.Inverted)
{
if (mi.Event == MouseInputEvent.Down && !isStandardScrolling)
{
if (!TakeMouseFocus(mi))
return false;
standardScrollStart = mi.Location;
}
else if (mi.Event == MouseInputEvent.Move && (isStandardScrolling ||
(standardScrollStart.HasValue && ((standardScrollStart.Value - mi.Location).Length > Game.Settings.Game.MouseScrollDeadzone))))
{
isStandardScrolling = true;
var d = scrollType == MouseScrollType.Inverted ? -1 : 1;
worldRenderer.Viewport.Scroll((Viewport.LastMousePos - mi.Location) * d, false);
return true;
}
else if (mi.Event == MouseInputEvent.Up)
{
var wasStandardScrolling = isStandardScrolling;
isStandardScrolling = false;
standardScrollStart = null;
YieldMouseFocus(mi);
if (wasStandardScrolling)
return true;
}
}
// Tiberian Sun style click-and-drag scrolling
if (scrollType == MouseScrollType.Joystick)
{
if (mi.Event == MouseInputEvent.Down)
{
if (!TakeMouseFocus(mi))
return false;
joystickScrollStart = mi.Location;
}
if (mi.Event == MouseInputEvent.Up)
{
var wasJoystickScrolling = IsJoystickScrolling;
joystickScrollStart = joystickScrollEnd = null;
YieldMouseFocus(mi);
if (wasJoystickScrolling)
return true;
}
if (mi.Event == MouseInputEvent.Move)
{
if (!joystickScrollStart.HasValue)
joystickScrollStart = mi.Location;
joystickScrollEnd = mi.Location;
}
}
return IsJoystickScrolling || isStandardScrolling;
}
public override bool YieldMouseFocus(MouseInput mi)
{
joystickScrollStart = joystickScrollEnd = null;
return base.YieldMouseFocus(mi);
}
public override bool YieldKeyboardFocus()
{
keyboardDirections = ScrollDirection.None;
return base.YieldKeyboardFocus();
}
public override bool HandleKeyPress(KeyInput e)
{
var key = Hotkey.FromKeyInput(e);
bool HandleMapScrollKey(HotkeyReference hotkey, ScrollDirection scrollDirection)
{
var isHotkey = false;
var keyValue = hotkey.GetValue();
if (key.Key == keyValue.Key)
{
isHotkey = key == keyValue;
keyboardDirections = keyboardDirections.Set(scrollDirection, e.Event == KeyInputEvent.Down && (isHotkey || keyValue.Modifiers == Modifiers.None));
}
return isHotkey;
}
if (HandleMapScrollKey(ScrollUpKey, ScrollDirection.Up) || HandleMapScrollKey(ScrollDownKey, ScrollDirection.Down)
|| HandleMapScrollKey(ScrollLeftKey, ScrollDirection.Left) || HandleMapScrollKey(ScrollRightKey, ScrollDirection.Right))
return true;
if (e.Event != KeyInputEvent.Down)
return false;
if (ZoomInKey.IsActivatedBy(e))
{
worldRenderer.Viewport.AdjustZoom(0.25f);
return true;
}
if (ZoomOutKey.IsActivatedBy(e))
{
worldRenderer.Viewport.AdjustZoom(-0.25f);
return true;
}
if (JumpToTopEdgeKey.IsActivatedBy(e))
{
worldRenderer.Viewport.Center(new WPos(worldRenderer.Viewport.CenterPosition.X, 0, 0));
return true;
}
if (JumpToBottomEdgeKey.IsActivatedBy(e))
{
worldRenderer.Viewport.Center(new WPos(worldRenderer.Viewport.CenterPosition.X, worldRenderer.World.Map.ProjectedBottomRight.Y, 0));
return true;
}
if (JumpToLeftEdgeKey.IsActivatedBy(e))
{
worldRenderer.Viewport.Center(new WPos(0, worldRenderer.Viewport.CenterPosition.Y, 0));
return true;
}
if (JumpToRightEdgeKey.IsActivatedBy(e))
{
worldRenderer.Viewport.Center(new WPos(worldRenderer.World.Map.ProjectedBottomRight.X, worldRenderer.Viewport.CenterPosition.Y, 0));
return true;
}
for (var i = 0; i < saveBookmarkHotkeys.Length; i++)
{
if (saveBookmarkHotkeys[i].IsActivatedBy(e))
{
bookmarkPositions[i] = worldRenderer.Viewport.CenterPosition;
return true;
}
}
for (var i = 0; i < restoreBookmarkHotkeys.Length; i++)
{
if (restoreBookmarkHotkeys[i].IsActivatedBy(e))
{
var bookmark = bookmarkPositions[i];
if (bookmark.HasValue)
{
worldRenderer.Viewport.Center(bookmark.Value);
return true;
}
}
}
return world.OrderGenerator.HandleKeyPress(e);
}
static ScrollDirection CheckForDirections()
{
var margin = Game.Settings.Game.ViewportEdgeScrollMargin;
var directions = ScrollDirection.None;
if (Viewport.LastMousePos.X < margin)
directions |= ScrollDirection.Left;
if (Viewport.LastMousePos.Y < margin)
directions |= ScrollDirection.Up;
if (Viewport.LastMousePos.X >= Game.Renderer.Resolution.Width - margin)
directions |= ScrollDirection.Right;
if (Viewport.LastMousePos.Y >= Game.Renderer.Resolution.Height - margin)
directions |= ScrollDirection.Down;
return directions;
}
}
}