From 2df9a088d7bcc992c4c2fac8e8a8466adb96f391 Mon Sep 17 00:00:00 2001 From: lodicolo Date: Fri, 25 Aug 2023 21:21:54 -0400 Subject: [PATCH] feat: zoomer (#1872) * feat: world zoom (no ui scaling) * control zoom from chatbox for debugging * fix: avoid overwriting position of windows * fix: minor cleanup EscapeMenu and SettingsWindow * ui changes for zoom * feat: zoom buttons, fix: corrected camera * correct zoom direction * fix: fade scale on main menu when zoomed in --- Intersect (Core)/Config/MapOptions.cs | 3 + .../Database/GameDatabase.cs | 10 +- .../Entities/IPlayer.cs | 2 +- .../GenericClasses/FloatRect.cs | 31 ++- .../Graphics/GameRenderer.cs | 19 ++ .../Gwen/Control/Base.cs | 53 ++-- .../Gwen/Control/Button.cs | 32 +-- .../Gwen/Control/CheckBox.cs | 26 +- .../Gwen/Control/ComboBox.cs | 18 +- .../Gwen/Control/HorizontalSlider.cs | 2 +- .../Gwen/Control/ImagePanel.cs | 6 +- .../Gwen/Control/Label.cs | 6 +- .../Gwen/Control/LabeledCheckBox.cs | 8 +- .../Gwen/Control/LabeledHorizontalSlider.cs | 153 +++++++++++ .../Gwen/Control/Layout/Table.cs | 8 +- .../Gwen/Control/ListBox.cs | 26 +- .../Gwen/Control/Menu.cs | 20 +- .../Gwen/Control/ResizableControl.cs | 8 +- .../Gwen/Control/RichLabel.cs | 15 +- .../Gwen/Control/ScrollBar.cs | 10 +- .../Gwen/Control/ScrollControl.cs | 20 +- .../Gwen/Control/Slider.cs | 57 +++- .../Gwen/Control/TextBox.cs | 20 +- .../Gwen/Control/VerticalSlider.cs | 2 +- .../Gwen/Control/WindowControl.cs | 20 +- .../Gwen/ControlInternal/Dragger.cs | 6 +- Intersect.Client.Framework/Gwen/Skin/Base.cs | 2 +- .../Gwen/Skin/Simple.cs | 2 +- .../Gwen/Skin/TexturedBase.cs | 33 ++- Intersect.Client/Core/Controls/ControlEnum.cs | 10 +- Intersect.Client/Core/Controls/Controls.cs | 12 +- Intersect.Client/Core/Graphics.cs | 257 ++++++++++-------- Intersect.Client/Core/Input.cs | 54 +++- Intersect.Client/Entities/Player.cs | 11 +- .../Interface/Game/Chat/Chatbox.cs | 14 +- Intersect.Client/Interface/Game/EscapeMenu.cs | 2 +- .../Interface/Shared/SettingsWindow.cs | 53 +++- Intersect.Client/Localization/Strings.cs | 12 + .../MonoGame/Database/MonoDatabase.cs | 2 +- .../MonoGame/Graphics/MonoRenderer.cs | 38 ++- Intersect.Client/MonoGame/IntersectGame.cs | 2 +- 41 files changed, 767 insertions(+), 318 deletions(-) create mode 100644 Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs diff --git a/Intersect (Core)/Config/MapOptions.cs b/Intersect (Core)/Config/MapOptions.cs index 2cd9a3bba0..1bfa643575 100644 --- a/Intersect (Core)/Config/MapOptions.cs +++ b/Intersect (Core)/Config/MapOptions.cs @@ -64,6 +64,9 @@ public bool EnableDiagonalMovement /// public int TileWidth { get; set; } = 32; + [JsonIgnore] + public float TileScale => 32f / Math.Min(TileWidth, TileHeight); + /// /// The time, in milliseconds, until the map is cleaned up. /// diff --git a/Intersect.Client.Framework/Database/GameDatabase.cs b/Intersect.Client.Framework/Database/GameDatabase.cs index 012cf8cbfd..3818e111cf 100644 --- a/Intersect.Client.Framework/Database/GameDatabase.cs +++ b/Intersect.Client.Framework/Database/GameDatabase.cs @@ -62,12 +62,16 @@ public abstract partial class GameDatabase public TypewriterBehavior TypewriterBehavior { get; set; } + public float UIScale { get; set; } = 1.0f; + + public float WorldZoom { get; set; } = 1.0f; + public abstract void DeletePreference(string key); public abstract bool HasPreference(string key); //Saving password, other stuff we don't want in the games directory - public abstract void SavePreference(string key, object value); + public abstract void SavePreference(string key, TValue value); public abstract string LoadPreference(string key); @@ -128,6 +132,8 @@ public virtual void LoadPreferences() ShowHealthAsPercentage = LoadPreference(nameof(ShowHealthAsPercentage), false); ShowManaAsPercentage = LoadPreference(nameof(ShowManaAsPercentage), false); TypewriterBehavior = LoadPreference(nameof(TypewriterBehavior), TypewriterBehavior.Word); + UIScale = LoadPreference(nameof(UIScale), 1.0f); + WorldZoom = LoadPreference(nameof(WorldZoom), 1.0f); } /// @@ -162,6 +168,8 @@ public virtual void SavePreferences() SavePreference(nameof(ShowHealthAsPercentage), ShowHealthAsPercentage); SavePreference(nameof(ShowManaAsPercentage), ShowManaAsPercentage); SavePreference(nameof(TypewriterBehavior), TypewriterBehavior); + SavePreference(nameof(UIScale), UIScale); + SavePreference(nameof(WorldZoom), WorldZoom); } public abstract bool LoadConfig(); diff --git a/Intersect.Client.Framework/Entities/IPlayer.cs b/Intersect.Client.Framework/Entities/IPlayer.cs index 069b4dcdcf..6386790771 100644 --- a/Intersect.Client.Framework/Entities/IPlayer.cs +++ b/Intersect.Client.Framework/Entities/IPlayer.cs @@ -33,7 +33,7 @@ public interface IPlayer : IEntity bool TryTarget(); bool TryTarget(IEntity entity, bool force = false); void AutoTarget(); - void ClearTarget(); + bool ClearTarget(); void AddToHotbar(int hotbarSlot, sbyte itemType, int itemSlot); int FindHotbarItem(IHotbarInstance hotbarInstance); int FindHotbarSpell(IHotbarInstance hotbarInstance); diff --git a/Intersect.Client.Framework/GenericClasses/FloatRect.cs b/Intersect.Client.Framework/GenericClasses/FloatRect.cs index fc09742b57..681548ad79 100644 --- a/Intersect.Client.Framework/GenericClasses/FloatRect.cs +++ b/Intersect.Client.Framework/GenericClasses/FloatRect.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace Intersect.Client.Framework.GenericClasses { @@ -116,6 +117,34 @@ public bool Contains(Point pt) return Contains(pt.X, pt.Y); } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FloatRect operator *(FloatRect lhs, float rhs) + { + return new FloatRect( + lhs.X * rhs, + lhs.Y * rhs, + lhs.Width * rhs, + lhs.Height * rhs + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FloatRect operator *(float lhs, FloatRect rhs) => rhs * lhs; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FloatRect operator /(FloatRect lhs, float rhs) + { + return new FloatRect( + lhs.X / rhs, + lhs.Y / rhs, + lhs.Width / rhs, + lhs.Height / rhs + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FloatRect operator /(float lhs, FloatRect rhs) => rhs / lhs; + + public override string ToString() => $"{Left},{Top} + {Width},{Height} -> {Right},{Bottom}"; + } } diff --git a/Intersect.Client.Framework/Graphics/GameRenderer.cs b/Intersect.Client.Framework/Graphics/GameRenderer.cs index 12360683af..620cdd3084 100644 --- a/Intersect.Client.Framework/Graphics/GameRenderer.cs +++ b/Intersect.Client.Framework/Graphics/GameRenderer.cs @@ -25,6 +25,23 @@ public GameRenderer() public Resolution PreferredResolution { get; set; } + protected float _scale = 1.0f; + + public float Scale + { + get => _scale; + set + { + if (Math.Abs(_scale - value) < 0.001) + { + return; + } + + _scale = value; + RecreateSpriteBatch(); + } + } + public abstract void Init(); /// @@ -35,6 +52,8 @@ public GameRenderer() public abstract bool BeginScreenshot(); + protected abstract bool RecreateSpriteBatch(); + /// /// Called when the frame is done being drawn, generally used to finally display the content to the screen. /// diff --git a/Intersect.Client.Framework/Gwen/Control/Base.cs b/Intersect.Client.Framework/Gwen/Control/Base.cs index 7953daf68a..a085cd1469 100644 --- a/Intersect.Client.Framework/Gwen/Control/Base.cs +++ b/Intersect.Client.Framework/Gwen/Control/Base.cs @@ -72,6 +72,7 @@ internal bool InheritParentEnablementProperties private Point mAlignmentTransform; private Rectangle mBounds; + private Rectangle mBoundsOnDisk; private bool mCacheTextureDirty; @@ -797,34 +798,12 @@ public void RemoveAlignments() mAlignments.Clear(); } - public virtual string GetJsonUI() + public virtual string GetJsonUI(bool isRoot = false) { - return JsonConvert.SerializeObject(GetJson(), Formatting.Indented); + return JsonConvert.SerializeObject(GetJson(isRoot), Formatting.Indented); } - public virtual void WriteBaseUIJson(string path, bool includeBounds = true, bool onlyChildren = false) - { - if (onlyChildren) - { - if (HasNamedChildren()) - { - foreach (var ctrl in mChildren) - { - if (!string.IsNullOrEmpty(ctrl.Name)) - { - ctrl.WriteBaseUIJson(path); - } - } - } - } - else - { - path = Path.Combine(path, Name + ".json"); - File.WriteAllText(path, JsonConvert.SerializeObject(GetJson(), Formatting.Indented)); - } - } - - public virtual JObject GetJson() + public virtual JObject GetJson(bool isRoot = default) { var alignments = new List(); foreach (var alignment in mAlignments) @@ -832,8 +811,13 @@ public virtual JObject GetJson() alignments.Add(alignment.ToString()); } + isRoot |= Parent == default; + + var boundsToWrite = isRoot + ? new Rectangle(mBoundsOnDisk.X, mBoundsOnDisk.Y, mBounds.Width, mBounds.Height) + : mBounds; var o = new JObject( - new JProperty("Bounds", Rectangle.ToString(mBounds)), + new JProperty("Bounds", Rectangle.ToString(boundsToWrite)), new JProperty("Padding", Padding.ToString(mPadding)), new JProperty("AlignmentEdgeDistances", Padding.ToString(mAlignmentDistance)), new JProperty("AlignmentTransform", Point.ToString(mAlignmentTransform)), @@ -896,7 +880,7 @@ public void LoadJsonUi(GameContentManager.UI stage, string resolution, bool save if (obj != null) { - LoadJson(obj); + LoadJson(obj, true); ProcessAlignments(); } } @@ -909,12 +893,12 @@ public void LoadJsonUi(GameContentManager.UI stage, string resolution, bool save if (!cacheHit && saveOutput) { - GameContentManager.Current?.SaveUIJson(stage, Name, GetJsonUI(), resolution); + GameContentManager.Current?.SaveUIJson(stage, Name, GetJsonUI(true), resolution); } }); } - public virtual void LoadJson(JToken obj) + public virtual void LoadJson(JToken obj, bool isRoot = default) { if (obj["Alignments"] != null) { @@ -964,7 +948,16 @@ public virtual void LoadJson(JToken obj) if (obj["Bounds"] != null) { - SetBounds(Rectangle.FromString((string) obj["Bounds"])); + mBoundsOnDisk = Rectangle.FromString((string)obj["Bounds"]); + isRoot = isRoot || Parent == default; + if (isRoot) + { + SetSize(mBoundsOnDisk.Width, mBoundsOnDisk.Height); + } + else + { + SetBounds(mBoundsOnDisk); + } } if (obj["Padding"] != null) diff --git a/Intersect.Client.Framework/Gwen/Control/Button.cs b/Intersect.Client.Framework/Gwen/Control/Button.cs index b8d160f88d..fd6d36458d 100644 --- a/Intersect.Client.Framework/Gwen/Control/Button.cs +++ b/Intersect.Client.Framework/Gwen/Control/Button.cs @@ -171,9 +171,9 @@ public bool ToggleState /// public event GwenEventHandler ToggledOff; - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); if (this.GetType() != typeof(CheckBox)) { obj.Add("NormalImage", GetImageFilename(ControlState.Normal)); @@ -194,15 +194,15 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["NormalImage"] != null) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["NormalImage"] - ), (string) obj["NormalImage"], ControlState.Normal + Framework.Content.TextureType.Gui, (string)obj["NormalImage"] + ), (string)obj["NormalImage"], ControlState.Normal ); } @@ -210,8 +210,8 @@ public override void LoadJson(JToken obj) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["HoveredImage"] - ), (string) obj["HoveredImage"], ControlState.Hovered + Framework.Content.TextureType.Gui, (string)obj["HoveredImage"] + ), (string)obj["HoveredImage"], ControlState.Hovered ); } @@ -219,8 +219,8 @@ public override void LoadJson(JToken obj) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["ClickedImage"] - ), (string) obj["ClickedImage"], ControlState.Clicked + Framework.Content.TextureType.Gui, (string)obj["ClickedImage"] + ), (string)obj["ClickedImage"], ControlState.Clicked ); } @@ -228,36 +228,36 @@ public override void LoadJson(JToken obj) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["DisabledImage"] - ), (string) obj["DisabledImage"], ControlState.Disabled + Framework.Content.TextureType.Gui, (string)obj["DisabledImage"] + ), (string)obj["DisabledImage"], ControlState.Disabled ); } if (obj["CenterImage"] != null) { - mCenterImage = (bool) obj["CenterImage"]; + mCenterImage = (bool)obj["CenterImage"]; } if (this.GetType() != typeof(ComboBox) && this.GetType() != typeof(CheckBox)) { if (obj["HoverSound"] != null) { - mHoverSound = (string) obj["HoverSound"]; + mHoverSound = (string)obj["HoverSound"]; } if (obj["MouseUpSound"] != null) { - mMouseUpSound = (string) obj["MouseUpSound"]; + mMouseUpSound = (string)obj["MouseUpSound"]; } if (obj["MouseDownSound"] != null) { - mMouseDownSound = (string) obj["MouseDownSound"]; + mMouseDownSound = (string)obj["MouseDownSound"]; } if (obj["ClickSound"] != null) { - mClickSound = (string) obj["ClickSound"]; + mClickSound = (string)obj["ClickSound"]; } } } diff --git a/Intersect.Client.Framework/Gwen/Control/CheckBox.cs b/Intersect.Client.Framework/Gwen/Control/CheckBox.cs index 3ee93fd507..38a438e741 100644 --- a/Intersect.Client.Framework/Gwen/Control/CheckBox.cs +++ b/Intersect.Client.Framework/Gwen/Control/CheckBox.cs @@ -83,9 +83,9 @@ public bool IsChecked /// protected virtual bool AllowUncheck => true; - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("NormalImage", GetImageFilename(ControlState.Normal)); obj.Add("CheckedImage", GetImageFilename(ControlState.CheckedNormal)); obj.Add("DisabledImage", GetImageFilename(ControlState.Disabled)); @@ -96,15 +96,15 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["NormalImage"] != null) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["NormalImage"] - ), (string) obj["NormalImage"], ControlState.Normal + Framework.Content.TextureType.Gui, (string)obj["NormalImage"] + ), (string)obj["NormalImage"], ControlState.Normal ); } @@ -112,8 +112,8 @@ public override void LoadJson(JToken obj) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["CheckedImage"] - ), (string) obj["CheckedImage"], ControlState.CheckedNormal + Framework.Content.TextureType.Gui, (string)obj["CheckedImage"] + ), (string)obj["CheckedImage"], ControlState.CheckedNormal ); } @@ -121,8 +121,8 @@ public override void LoadJson(JToken obj) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["DisabledImage"] - ), (string) obj["DisabledImage"], ControlState.Disabled + Framework.Content.TextureType.Gui, (string)obj["DisabledImage"] + ), (string)obj["DisabledImage"], ControlState.Disabled ); } @@ -130,19 +130,19 @@ public override void LoadJson(JToken obj) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["CheckedDisabledImage"] - ), (string) obj["CheckedDisabledImage"], ControlState.CheckedDisabled + Framework.Content.TextureType.Gui, (string)obj["CheckedDisabledImage"] + ), (string)obj["CheckedDisabledImage"], ControlState.CheckedDisabled ); } if (obj["CheckedSound"] != null) { - mCheckSound = (string) obj["CheckedSound"]; + mCheckSound = (string)obj["CheckedSound"]; } if (obj["UncheckedSound"] != null) { - mUncheckedSound = (string) obj["UncheckedSound"]; + mUncheckedSound = (string)obj["UncheckedSound"]; } } diff --git a/Intersect.Client.Framework/Gwen/Control/ComboBox.cs b/Intersect.Client.Framework/Gwen/Control/ComboBox.cs index 2f8278c748..27b69baae9 100644 --- a/Intersect.Client.Framework/Gwen/Control/ComboBox.cs +++ b/Intersect.Client.Framework/Gwen/Control/ComboBox.cs @@ -111,9 +111,9 @@ public void SetMenuBelow() } } - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("MenuAbove", mMenuAbove); obj.Add("Menu", mMenu.GetJson()); obj.Add("DropDownButton", mButton.GetJson()); @@ -126,12 +126,12 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["MenuAbove"] != null) { - mMenuAbove = (bool) obj["MenuAbove"]; + mMenuAbove = (bool)obj["MenuAbove"]; } if (obj["Menu"] != null) @@ -146,27 +146,27 @@ public override void LoadJson(JToken obj) if (obj["OpenMenuSound"] != null) { - mOpenMenuSound = (string) obj["OpenMenuSound"]; + mOpenMenuSound = (string)obj["OpenMenuSound"]; } if (obj["CloseMenuSound"] != null) { - mCloseMenuSound = (string) obj["CloseMenuSound"]; + mCloseMenuSound = (string)obj["CloseMenuSound"]; } if (obj["HoverMenuSound"] != null) { - mHoverMenuSound = (string) obj["HoverMenuSound"]; + mHoverMenuSound = (string)obj["HoverMenuSound"]; } if (obj["ItemHoverSound"] != null) { - mHoverItemSound = (string) obj["ItemHoverSound"]; + mHoverItemSound = (string)obj["ItemHoverSound"]; } if (obj["SelectItemSound"] != null) { - mSelectItemSound = (string) obj["SelectItemSound"]; + mSelectItemSound = (string)obj["SelectItemSound"]; } foreach (var child in Children) diff --git a/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs b/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs index a79f888d01..e990efff96 100644 --- a/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs +++ b/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs @@ -58,7 +58,7 @@ protected override void Layout(Skin.Base skin) /// Skin to use. protected override void Render(Skin.Base skin) { - skin.DrawSlider(this, true, mSnapToNotches ? mNotchCount : 0, mSliderBar.Width); + skin.DrawSlider(this, true, Notches, mSnapToNotches ? mNotchCount : 0, mSliderBar.Width); } } diff --git a/Intersect.Client.Framework/Gwen/Control/ImagePanel.cs b/Intersect.Client.Framework/Gwen/Control/ImagePanel.cs index 6440aff364..4941466247 100644 --- a/Intersect.Client.Framework/Gwen/Control/ImagePanel.cs +++ b/Intersect.Client.Framework/Gwen/Control/ImagePanel.cs @@ -89,9 +89,9 @@ public override void Dispose() base.Dispose(); } - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("Texture", TextureFilename); obj.Add("HoverSound", mHoverSound); obj.Add("LeftMouseClickSound", mLeftMouseClickSound); @@ -100,7 +100,7 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["Texture"] != null) diff --git a/Intersect.Client.Framework/Gwen/Control/Label.cs b/Intersect.Client.Framework/Gwen/Control/Label.cs index 632119ad16..96a79aa51a 100644 --- a/Intersect.Client.Framework/Gwen/Control/Label.cs +++ b/Intersect.Client.Framework/Gwen/Control/Label.cs @@ -211,9 +211,9 @@ public Padding TextPadding } } - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); if (typeof(Label) == GetType()) { obj.Add("BackgroundTemplate", mBackgroundTemplateFilename); @@ -232,7 +232,7 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (typeof(Label) == GetType() && obj["BackgroundTemplate"] != null) diff --git a/Intersect.Client.Framework/Gwen/Control/LabeledCheckBox.cs b/Intersect.Client.Framework/Gwen/Control/LabeledCheckBox.cs index 58d4a64bce..89ab6010a4 100644 --- a/Intersect.Client.Framework/Gwen/Control/LabeledCheckBox.cs +++ b/Intersect.Client.Framework/Gwen/Control/LabeledCheckBox.cs @@ -41,7 +41,7 @@ public LabeledCheckBox(Base parent, string name = "") : base(parent, name) Dock = Pos.Fill, IsTabable = false }; - mLabel.Clicked += delegate(Base control, ClickedEventArgs args) { mCheckBox.Press(control); }; + mLabel.Clicked += delegate (Base control, ClickedEventArgs args) { mCheckBox.Press(control); }; IsTabable = false; } @@ -79,16 +79,16 @@ public string Text /// public event GwenEventHandler CheckChanged; - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("Label", mLabel.GetJson()); obj.Add("Checkbox", mCheckBox.GetJson()); return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["Label"] != null) diff --git a/Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs b/Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs new file mode 100644 index 0000000000..2bd2793248 --- /dev/null +++ b/Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs @@ -0,0 +1,153 @@ +using System; + +using Intersect.Client.Framework.File_Management; +using Intersect.Client.Framework.GenericClasses; +using Intersect.Client.Framework.Graphics; +using Intersect.Client.Framework.Gwen.ControlInternal; +using Intersect.Client.Framework.Gwen.Input; + +using Newtonsoft.Json.Linq; + +namespace Intersect.Client.Framework.Gwen.Control +{ + + /// + /// Base slider. + /// + public partial class LabeledHorizontalSlider : Base + { + private readonly Label _label; + private readonly HorizontalSlider _slider; + private readonly ImagePanel _sliderBackground; + private readonly TextBoxNumeric _sliderValue; + private double _scale = 1.0; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public LabeledHorizontalSlider(Base parent, string name = "") : base(parent, name) + { + SetBounds(new Rectangle(0, 0, 32, 128)); + + _label = new Label(this, "SliderLabel"); + + _sliderBackground = new ImagePanel(this, "SliderBackground"); + _slider = new HorizontalSlider(_sliderBackground, "Slider"); + _sliderValue = new TextBoxNumeric(_sliderBackground, "SliderValue"); + + _slider.ValueChanged += (sender, arguments) => + { + if (sender == _sliderValue) + { + return; + } + + _sliderValue.Value = _slider.Value * _scale; + ValueChanged?.Invoke(sender, arguments); + }; + + _sliderValue.TextChanged += (sender, arguments) => + { + if (sender == _slider) + { + return; + } + + _slider.Value = _sliderValue.Value / _scale; + ValueChanged?.Invoke(sender, arguments); + }; + + KeyboardInputEnabled = true; + IsTabable = true; + } + + public string Label + { + get => _label.Text; + set => _label.Text = value; + } + + /// + /// Number of notches on the slider axis. + /// + public int NotchCount + { + get => _slider.NotchCount; + set => _slider.NotchCount = value; + } + + public double[] Notches + { + get => _slider.Notches; + set => _slider.Notches = value; + } + + /// + /// Determines whether the slider should snap to notches. + /// + public bool SnapToNotches + { + get => _slider.SnapToNotches; + set => _slider.SnapToNotches = value; + } + + /// + /// Minimum value. + /// + public double Min + { + get => _slider.Min; + set => _slider.Min = value; + } + + /// + /// Maximum value. + /// + public double Max + { + get => _slider.Max; + set => _slider.Max = value; + } + + public double Scale + { + get => _scale; + set + { + _scale = value; + _sliderValue.Value = _slider.Value * _scale; + } + } + + /// + /// Current value. + /// + public double Value + { + get => _slider.Value; + set + { + _slider.Value = value; + _sliderValue.Value = _slider.Value * _scale; + } + } + + /// + /// Invoked when the value has been changed. + /// + public event GwenEventHandler ValueChanged; + + public override JObject GetJson(bool isRoot = default) + { + var obj = base.GetJson(isRoot); + + return base.FixJson(obj); + } + + public override void LoadJson(JToken obj, bool isRoot = default) + { + base.LoadJson(obj); + } + } +} diff --git a/Intersect.Client.Framework/Gwen/Control/Layout/Table.cs b/Intersect.Client.Framework/Gwen/Control/Layout/Table.cs index a34271c357..8bf4b5d879 100644 --- a/Intersect.Client.Framework/Gwen/Control/Layout/Table.cs +++ b/Intersect.Client.Framework/Gwen/Control/Layout/Table.cs @@ -159,9 +159,9 @@ protected virtual void OnDataChanged(object sender, TableDataChangedEventArgs ar row.SetCellText(args.Column, args.NewValue?.ToString()); } - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("SizeToContents", mSizeToContents); obj.Add("DefaultRowHeight", mDefaultRowHeight); obj.Add(nameof(ColumnAlignments), new JArray(ColumnAlignments.Select(alignment => alignment.ToString() as object).ToArray())); @@ -172,7 +172,7 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj[nameof(SizeToContents)] != null) @@ -445,7 +445,7 @@ protected virtual (int width, int height) ComputeColumnWidths() height += row.Height; } - // sum all column widths + // sum all column widths width += mColumnWidths.Take(ColumnCount).Sum(); return (width, height); diff --git a/Intersect.Client.Framework/Gwen/Control/ListBox.cs b/Intersect.Client.Framework/Gwen/Control/ListBox.cs index 4fb647eb45..71d7bd1fdb 100644 --- a/Intersect.Client.Framework/Gwen/Control/ListBox.cs +++ b/Intersect.Client.Framework/Gwen/Control/ListBox.cs @@ -120,7 +120,7 @@ public IEnumerable SelectedRows var tmp = new List(); foreach (var row in mSelectedRows) { - tmp.Add((TableRow) row); + tmp.Add((TableRow)row); } return tmp; @@ -222,9 +222,9 @@ public Color TextColorOverride }); } - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("SizeToContents", mSizeToContents); obj.Add("MultiSelect", AllowMultiSelect); obj.Add("IsToggle", IsToggle); @@ -238,43 +238,43 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["SizeToContents"] != null) { - mSizeToContents = (bool) obj["SizeToContents"]; + mSizeToContents = (bool)obj["SizeToContents"]; } if (obj["MultiSelect"] != null) { - AllowMultiSelect = (bool) obj["MultiSelect"]; + AllowMultiSelect = (bool)obj["MultiSelect"]; } if (obj["IsToggle"] != null) { - IsToggle = (bool) obj["IsToggle"]; + IsToggle = (bool)obj["IsToggle"]; } if (obj["ItemHoverSound"] != null) { - mItemHoverSound = (string) obj["ItemHoverSound"]; + mItemHoverSound = (string)obj["ItemHoverSound"]; } if (obj["ItemClickSound"] != null) { - mItemClickSound = (string) obj["ItemClickSound"]; + mItemClickSound = (string)obj["ItemClickSound"]; } if (obj["ItemRightClickSound"] != null) { - mItemRightClickSound = (string) obj["ItemRightClickSound"]; + mItemRightClickSound = (string)obj["ItemRightClickSound"]; } if (obj["Font"] != null && obj["Font"].Type != JTokenType.Null) { - var fontArr = ((string) obj["Font"]).Split(','); - mFontInfo = (string) obj["Font"]; + var fontArr = ((string)obj["Font"]).Split(','); + mFontInfo = (string)obj["Font"]; mFont = GameContentManager.Current.GetFont(fontArr[0], int.Parse(fontArr[1])); } @@ -290,7 +290,7 @@ public override void LoadJson(JToken obj) foreach (var itm in mTable.Children) { - var row = (ListBoxRow) itm; + var row = (ListBoxRow)itm; row.HoverSound = mItemHoverSound; row.ClickSound = mItemClickSound; row.RightClickSound = mItemRightClickSound; diff --git a/Intersect.Client.Framework/Gwen/Control/Menu.cs b/Intersect.Client.Framework/Gwen/Control/Menu.cs index 43a8e42020..9c34cc5622 100644 --- a/Intersect.Client.Framework/Gwen/Control/Menu.cs +++ b/Intersect.Client.Framework/Gwen/Control/Menu.cs @@ -318,9 +318,9 @@ public override bool SizeToChildren(bool width = true, bool height = true) return true; } - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); if (!(this is CheckBox)) { obj.Add("BackgroundTemplate", mBackgroundTemplateFilename); @@ -332,32 +332,32 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["BackgroundTemplate"] != null) { SetBackgroundTemplate( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["BackgroundTemplate"] - ), (string) obj["BackgroundTemplate"] + Framework.Content.TextureType.Gui, (string)obj["BackgroundTemplate"] + ), (string)obj["BackgroundTemplate"] ); } if (obj["ItemTextColor"] != null) { - mItemNormalTextColor = Color.FromString((string) obj["ItemTextColor"]); + mItemNormalTextColor = Color.FromString((string)obj["ItemTextColor"]); } if (obj["ItemHoveredTextColor"] != null) { - mItemHoverTextColor = Color.FromString((string) obj["ItemHoveredTextColor"]); + mItemHoverTextColor = Color.FromString((string)obj["ItemHoveredTextColor"]); } if (obj["ItemFont"] != null && obj["ItemFont"].Type != JTokenType.Null) { - var fontArr = ((string) obj["ItemFont"]).Split(','); - mItemFontInfo = (string) obj["ItemFont"]; + var fontArr = ((string)obj["ItemFont"]).Split(','); + mItemFontInfo = (string)obj["ItemFont"]; mItemFont = GameContentManager.Current.GetFont(fontArr[0], int.Parse(fontArr[1])); } @@ -369,7 +369,7 @@ private void UpdateItemStyles() var menuItems = Children.Where(x => x is MenuItem).ToArray(); foreach (var item in menuItems) { - var itm = (MenuItem) item; + var itm = (MenuItem)item; if (mItemFont != null) { itm.Font = mItemFont; diff --git a/Intersect.Client.Framework/Gwen/Control/ResizableControl.cs b/Intersect.Client.Framework/Gwen/Control/ResizableControl.cs index 45f043df40..fb545ad3a1 100644 --- a/Intersect.Client.Framework/Gwen/Control/ResizableControl.cs +++ b/Intersect.Client.Framework/Gwen/Control/ResizableControl.cs @@ -90,20 +90,20 @@ public bool ClampMovement /// public event GwenEventHandler Resized; - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("ClampMovement", ClampMovement); return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["ClampMovement"] != null) { - ClampMovement = (bool) obj["ClampMovement"]; + ClampMovement = (bool)obj["ClampMovement"]; } } diff --git a/Intersect.Client.Framework/Gwen/Control/RichLabel.cs b/Intersect.Client.Framework/Gwen/Control/RichLabel.cs index bd67368ce6..618104206d 100644 --- a/Intersect.Client.Framework/Gwen/Control/RichLabel.cs +++ b/Intersect.Client.Framework/Gwen/Control/RichLabel.cs @@ -34,7 +34,7 @@ public partial class RichLabel : Base /// Parent control. public RichLabel(Base parent, string name = "") : base(parent, name) { - mNewline = new string[] {Environment.NewLine, "\n"}; + mNewline = new string[] { Environment.NewLine, "\n" }; mTextBlocks = new List(); } @@ -43,27 +43,28 @@ public RichLabel(Base parent, string name = "") : base(parent, name) /// public void AddLineBreak() { - var block = new TextBlock {Type = BlockType.NewLine}; + var block = new TextBlock { Type = BlockType.NewLine }; mTextBlocks.Add(block); } /// - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["Font"] != null && obj["Font"].Type != JTokenType.Null) { - var fontArr = ((string) obj["Font"]).Split(','); - mFontInfo = (string) obj["Font"]; + var fontArr = ((string)obj["Font"]).Split(','); + mFontInfo = (string)obj["Font"]; mFont = GameContentManager.Current.GetFont(fontArr[0], int.Parse(fontArr[1])); } } + /// /// - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("Font", mFontInfo); return base.FixJson(obj); diff --git a/Intersect.Client.Framework/Gwen/Control/ScrollBar.cs b/Intersect.Client.Framework/Gwen/Control/ScrollBar.cs index e1bf94ef33..8f71fa60c6 100644 --- a/Intersect.Client.Framework/Gwen/Control/ScrollBar.cs +++ b/Intersect.Client.Framework/Gwen/Control/ScrollBar.cs @@ -116,9 +116,9 @@ public float ViewableContentSize /// public event GwenEventHandler BarMoved; - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("BackgroundTemplate", mBackgroundTemplateFilename); obj.Add("UpOrLeftButton", mScrollButton[0].GetJson()); obj.Add("Bar", mBar.GetJson()); @@ -127,15 +127,15 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["BackgroundTemplate"] != null) { SetBackgroundTemplate( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["BackgroundTemplate"] - ), (string) obj["BackgroundTemplate"] + Framework.Content.TextureType.Gui, (string)obj["BackgroundTemplate"] + ), (string)obj["BackgroundTemplate"] ); } diff --git a/Intersect.Client.Framework/Gwen/Control/ScrollControl.cs b/Intersect.Client.Framework/Gwen/Control/ScrollControl.cs index f5c56b208e..d30acfcb53 100644 --- a/Intersect.Client.Framework/Gwen/Control/ScrollControl.cs +++ b/Intersect.Client.Framework/Gwen/Control/ScrollControl.cs @@ -129,9 +129,9 @@ protected bool VScrollRequired } } - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("CanScrollH", mCanScrollH); obj.Add("CanScrollV", mCanScrollV); obj.Add("AutoHideBars", mAutoHideBars); @@ -142,22 +142,22 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["CanScrollH"] != null) { - mCanScrollH = (bool) obj["CanScrollH"]; + mCanScrollH = (bool)obj["CanScrollH"]; } if (obj["CanScrollV"] != null) { - mCanScrollV = (bool) obj["CanScrollV"]; + mCanScrollV = (bool)obj["CanScrollV"]; } if (obj["AutoHideBars"] != null) { - mAutoHideBars = (bool) obj["AutoHideBars"]; + mAutoHideBars = (bool)obj["AutoHideBars"]; } if (obj["InnerPanel"] != null) @@ -322,10 +322,10 @@ public virtual void UpdateScrollBars() } var wPercent = Width / - (float) (childrenWidth + (mVerticalScrollBar.IsHidden ? 0 : mVerticalScrollBar.Width)); + (float)(childrenWidth + (mVerticalScrollBar.IsHidden ? 0 : mVerticalScrollBar.Width)); var hPercent = Height / - (float) (childrenHeight + + (float)(childrenHeight + (mHorizontalScrollBar.IsHidden ? 0 : mHorizontalScrollBar.Height)); if (mCanScrollV) @@ -360,7 +360,7 @@ public virtual void UpdateScrollBars() if (CanScrollV && !mVerticalScrollBar.IsHidden) { newInnerPanelPosY = - (int) (-(mInnerPanel.Height - + (int)(-(mInnerPanel.Height - Height + (mHorizontalScrollBar.IsHidden ? 0 : mHorizontalScrollBar.Height)) * mVerticalScrollBar.ScrollAmount); @@ -369,7 +369,7 @@ public virtual void UpdateScrollBars() if (CanScrollH && !mHorizontalScrollBar.IsHidden) { newInnerPanelPosX = - (int) (-(mInnerPanel.Width - + (int)(-(mInnerPanel.Width - Width + (mVerticalScrollBar.IsHidden ? 0 : mVerticalScrollBar.Width)) * mHorizontalScrollBar.ScrollAmount); diff --git a/Intersect.Client.Framework/Gwen/Control/Slider.cs b/Intersect.Client.Framework/Gwen/Control/Slider.cs index 52d223df2d..8e9cd51041 100644 --- a/Intersect.Client.Framework/Gwen/Control/Slider.cs +++ b/Intersect.Client.Framework/Gwen/Control/Slider.cs @@ -1,5 +1,5 @@ using System; - +using System.Linq; using Intersect.Client.Framework.File_Management; using Intersect.Client.Framework.GenericClasses; using Intersect.Client.Framework.Graphics; @@ -29,6 +29,8 @@ public partial class Slider : Base protected int mNotchCount; + protected double[] _notches; + protected bool mSnapToNotches; protected double mValue; @@ -64,6 +66,12 @@ public int NotchCount set => mNotchCount = value; } + public double[] Notches + { + get => _notches; + set => _notches = value; + } + /// /// Determines whether the slider should snap to notches. /// @@ -121,37 +129,55 @@ public double Value /// public event GwenEventHandler ValueChanged; - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("BackgroundImage", GetImageFilename()); obj.Add("SnapToNotches", mSnapToNotches); obj.Add("NotchCount", mNotchCount); + var notches = (Notches == default || Notches.Length < 1) + ? default + : new JArray(Notches.Cast().ToArray()); + obj.Add(nameof(Notches), notches); obj.Add("SliderBar", mSliderBar.GetJson()); return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["BackgroundImage"] != null) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["BackgroundImage"] - ), (string) obj["BackgroundImage"] + Framework.Content.TextureType.Gui, (string)obj["BackgroundImage"] + ), (string)obj["BackgroundImage"] ); } if (obj["SnapToNotches"] != null) { - mSnapToNotches = (bool) obj["SnapToNotches"]; + mSnapToNotches = (bool)obj["SnapToNotches"]; } if (obj["NotchCount"] != null) { - mNotchCount = (int) obj["NotchCount"]; + mNotchCount = (int)obj["NotchCount"]; + } + + var notches = obj[nameof(Notches)]; + if (notches != null && notches.Type != JTokenType.Null) + { + Notches = ((JArray)notches).Select(token => (double)token).ToArray(); + if (Notches.Length < 1) + { + Notches = default; + } + } + else + { + Notches = default; } if (obj["SliderBar"] != null) @@ -290,8 +316,19 @@ protected virtual void SetValueInternal(double val) { if (mSnapToNotches) { - val = (double) Math.Floor(val * mNotchCount + 0.5f); - val /= mNotchCount; + if (_notches == default || _notches.Length < 1) + { + val = Math.Floor(val * mNotchCount + 0.5f); + val /= mNotchCount; + } + else + { + var notchMin = _notches.Min(); + var notchMax = _notches.Max(); + var notchRange = notchMax - notchMin; + var sorted = _notches.OrderBy(notchValue => Math.Abs(notchMin + val * notchRange - notchValue)).ToArray(); + val = (sorted.First() - notchMin) / notchRange; + } } if (mValue != val) diff --git a/Intersect.Client.Framework/Gwen/Control/TextBox.cs b/Intersect.Client.Framework/Gwen/Control/TextBox.cs index 62c61c2d7f..b388fdf3a9 100644 --- a/Intersect.Client.Framework/Gwen/Control/TextBox.cs +++ b/Intersect.Client.Framework/Gwen/Control/TextBox.cs @@ -599,7 +599,7 @@ public virtual void ReplaceSelection(string replacement, bool playSound = true) ValidateCursor(); var start = Math.Min(mCursorPos, mCursorEnd); - + if (Text.Length > 0 && start < 0) { // Make sure that start is not more negative than the text length @@ -629,7 +629,7 @@ public virtual void ReplaceSelection(string replacement, bool playSound = true) // How long is the text we are inserting in place of the selection? var replacementLength = replacement?.Length ?? 0; - + // Bound it to the limit replacementLength = Math.Min(replacementLength, maximumReplacementLength); @@ -655,7 +655,7 @@ public virtual void EraseSelection(bool playSound = true) DeleteText(start, end - start, playSound); - // Move the cursor to the start of the selection, + // Move the cursor to the start of the selection, // since the end is probably outside of the string now. mCursorPos = start; mCursorEnd = start; @@ -735,7 +735,7 @@ protected virtual void MakeCaretVisible() } // The ideal position is for the caret to be right in the middle - var idealx = (int) (-caretPos + Width * 0.5f); + var idealx = (int)(-caretPos + Width * 0.5f); // Don't show too much whitespace to the right if (idealx + TextWidth < Width - TextPadding.Right - Padding.Right) @@ -780,9 +780,9 @@ public void SetMaxLength(int val) MaximumLength = val; } - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("AddTextSound", mAddTextSound); obj.Add("RemoveTextSound", mRemoveTextSound); obj.Add("SubmitSound", mSubmitSound); @@ -790,22 +790,22 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["AddTextSound"] != null) { - mAddTextSound = (string) obj["AddTextSound"]; + mAddTextSound = (string)obj["AddTextSound"]; } if (obj["RemoveTextSound"] != null) { - mRemoveTextSound = (string) obj["RemoveTextSound"]; + mRemoveTextSound = (string)obj["RemoveTextSound"]; } if (obj["SubmitSound"] != null) { - mSubmitSound = (string) obj["SubmitSound"]; + mSubmitSound = (string)obj["SubmitSound"]; } } diff --git a/Intersect.Client.Framework/Gwen/Control/VerticalSlider.cs b/Intersect.Client.Framework/Gwen/Control/VerticalSlider.cs index ea816f166c..ed132cbdf6 100644 --- a/Intersect.Client.Framework/Gwen/Control/VerticalSlider.cs +++ b/Intersect.Client.Framework/Gwen/Control/VerticalSlider.cs @@ -58,7 +58,7 @@ protected override void Layout(Skin.Base skin) /// Skin to use. protected override void Render(Skin.Base skin) { - skin.DrawSlider(this, false, mSnapToNotches ? mNotchCount : 0, mSliderBar.Height); + skin.DrawSlider(this, false, Notches, mSnapToNotches ? mNotchCount : 0, mSliderBar.Height); } } diff --git a/Intersect.Client.Framework/Gwen/Control/WindowControl.cs b/Intersect.Client.Framework/Gwen/Control/WindowControl.cs index f392076161..72620007b8 100644 --- a/Intersect.Client.Framework/Gwen/Control/WindowControl.cs +++ b/Intersect.Client.Framework/Gwen/Control/WindowControl.cs @@ -146,9 +146,9 @@ public override bool IsOnTop get { return Parent.Children.Where(x => x is WindowControl).Last() == this; } } - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("ActiveImage", GetImageFilename(ControlState.Active)); obj.Add("InactiveImage", GetImageFilename(ControlState.Inactive)); obj.Add("ActiveColor", Color.ToString(mActiveColor)); @@ -162,15 +162,15 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["ActiveImage"] != null) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["ActiveImage"] - ), (string) obj["ActiveImage"], ControlState.Active + Framework.Content.TextureType.Gui, (string)obj["ActiveImage"] + ), (string)obj["ActiveImage"], ControlState.Active ); } @@ -178,24 +178,24 @@ public override void LoadJson(JToken obj) { SetImage( GameContentManager.Current.GetTexture( - Framework.Content.TextureType.Gui, (string) obj["InactiveImage"] - ), (string) obj["InactiveImage"], ControlState.Inactive + Framework.Content.TextureType.Gui, (string)obj["InactiveImage"] + ), (string)obj["InactiveImage"], ControlState.Inactive ); } if (obj["ActiveColor"] != null) { - mActiveColor = Color.FromString((string) obj["ActiveColor"]); + mActiveColor = Color.FromString((string)obj["ActiveColor"]); } if (obj["InactiveColor"] != null) { - mInactiveColor = Color.FromString((string) obj["InactiveColor"]); + mInactiveColor = Color.FromString((string)obj["InactiveColor"]); } if (obj["Closable"] != null) { - IsClosable = (bool) obj["Closable"]; + IsClosable = (bool)obj["Closable"]; } if (obj["Titlebar"] != null) diff --git a/Intersect.Client.Framework/Gwen/ControlInternal/Dragger.cs b/Intersect.Client.Framework/Gwen/ControlInternal/Dragger.cs index 95431425a3..cfc37f3c16 100644 --- a/Intersect.Client.Framework/Gwen/ControlInternal/Dragger.cs +++ b/Intersect.Client.Framework/Gwen/ControlInternal/Dragger.cs @@ -163,9 +163,9 @@ protected override void Render(Skin.Base skin) { } - public override JObject GetJson() + public override JObject GetJson(bool isRoot = default) { - var obj = base.GetJson(); + var obj = base.GetJson(isRoot); obj.Add("NormalImage", GetImageFilename(ControlState.Normal)); obj.Add("HoveredImage", GetImageFilename(ControlState.Hovered)); obj.Add("ClickedImage", GetImageFilename(ControlState.Clicked)); @@ -177,7 +177,7 @@ public override JObject GetJson() return base.FixJson(obj); } - public override void LoadJson(JToken obj) + public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); if (obj["NormalImage"] != null) diff --git a/Intersect.Client.Framework/Gwen/Skin/Base.cs b/Intersect.Client.Framework/Gwen/Skin/Base.cs index 370ce4549f..ce4edc1a35 100644 --- a/Intersect.Client.Framework/Gwen/Skin/Base.cs +++ b/Intersect.Client.Framework/Gwen/Skin/Base.cs @@ -167,7 +167,7 @@ public virtual void DrawListBoxLine(Control.Base control, bool selected, bool ev { } - public virtual void DrawSlider(Control.Base control, bool horizontal, int numNotches, int barSize) + public virtual void DrawSlider(Control.Base control, bool horizontal, double[] notches, int numNotches, int barSize) { } diff --git a/Intersect.Client.Framework/Gwen/Skin/Simple.cs b/Intersect.Client.Framework/Gwen/Skin/Simple.cs index c59e5a5062..30a20cb782 100644 --- a/Intersect.Client.Framework/Gwen/Skin/Simple.cs +++ b/Intersect.Client.Framework/Gwen/Skin/Simple.cs @@ -589,7 +589,7 @@ public override void DrawListBoxLine(Control.Base control, bool selected, bool e } } - public override void DrawSlider(Control.Base control, bool horizontal, int numNotches, int barSize) + public override void DrawSlider(Control.Base control, bool horizontal, double[] notches, int numNotches, int barSize) { var rect = control.RenderBounds; var notchRect = rect; diff --git a/Intersect.Client.Framework/Gwen/Skin/TexturedBase.cs b/Intersect.Client.Framework/Gwen/Skin/TexturedBase.cs index 361e4891e2..fbdcbf3c0d 100644 --- a/Intersect.Client.Framework/Gwen/Skin/TexturedBase.cs +++ b/Intersect.Client.Framework/Gwen/Skin/TexturedBase.cs @@ -1567,17 +1567,36 @@ public override void DrawListBoxLine(Control.Base control, bool selected, bool e mTextures.Input.ListBox.OddLine.Draw(Renderer, control.RenderBounds, control.RenderColor); } - public void DrawSliderNotchesH(Rectangle rect, int numNotches, float dist) + public void DrawSliderNotchesH(Rectangle rect, double[] notches, int numNotches, float dist) { if (numNotches == 0) { return; } - var iSpacing = rect.Width / (float) numNotches; - for (var i = 0; i < numNotches + 1; i++) + var notchMin = notches?.Min(); + var notchMax = notches?.Max(); + var notchRange = notchMax - notchMin; + if (notchRange == 0) + { + notchRange = 1; + } + var notchPositions = notches?.Select(notch => (rect.Width * (notch - notchMin) / notchRange).Value).ToArray(); + + if (notchPositions != null) + { + foreach (var notchPosition in notchPositions) + { + Renderer.DrawFilledRect(Util.FloatRect(rect.X + (float)notchPosition, rect.Y + dist - 2, 1, 5)); + } + } + else { - Renderer.DrawFilledRect(Util.FloatRect(rect.X + iSpacing * i, rect.Y + dist - 2, 1, 5)); + var iSpacing = rect.Width / (float) numNotches; + for (var i = 0; i < numNotches + 1; i++) + { + Renderer.DrawFilledRect(Util.FloatRect(rect.X + iSpacing * i, rect.Y + dist - 2, 1, 5)); + } } } @@ -1595,7 +1614,7 @@ public void DrawSliderNotchesV(Rectangle rect, int numNotches, float dist) } } - public override void DrawSlider(Control.Base control, bool horizontal, int numNotches, int barSize) + public override void DrawSlider(Control.Base control, bool horizontal, double[] notches, int numNotches, int barSize) { if (((Slider) control).GetImage() != null) { @@ -1628,7 +1647,7 @@ public override void DrawSlider(Control.Base control, bool horizontal, int numNo else { var rect = control.RenderBounds; - Renderer.DrawColor = Color.FromArgb(100, 0, 0, 0); + Renderer.DrawColor = control.RenderColor; if (horizontal) { @@ -1636,7 +1655,7 @@ public override void DrawSlider(Control.Base control, bool horizontal, int numNo rect.Width -= barSize; rect.Y += (int) (rect.Height * 0.5 - 1); rect.Height = 1; - DrawSliderNotchesH(rect, numNotches, barSize * 0.5f); + DrawSliderNotchesH(rect, notches, numNotches, barSize * 0.5f); Renderer.DrawFilledRect(rect); return; diff --git a/Intersect.Client/Core/Controls/ControlEnum.cs b/Intersect.Client/Core/Controls/ControlEnum.cs index 4193bce6e9..11fbdc3a50 100644 --- a/Intersect.Client/Core/Controls/ControlEnum.cs +++ b/Intersect.Client/Core/Controls/ControlEnum.cs @@ -67,9 +67,17 @@ public enum Control OpenAdminPanel, ToggleGui, - + TurnAround, + ToggleZoomIn, + + HoldToZoomIn, + + ToggleZoomOut, + + HoldToZoomOut, + } } diff --git a/Intersect.Client/Core/Controls/Controls.cs b/Intersect.Client/Core/Controls/Controls.cs index 5e1a7b0531..274cf70268 100644 --- a/Intersect.Client/Core/Controls/Controls.cs +++ b/Intersect.Client/Core/Controls/Controls.cs @@ -33,7 +33,13 @@ public Controls(Controls gameControls = null) MigrateControlBindings(control); var name = Enum.GetName(typeof(Control), control); - var bindings = ControlMapping[control].Bindings; + + if (!ControlMapping.TryGetValue(control, out var controlMapping)) + { + continue; + } + + var bindings = controlMapping.Bindings; for (var bindingIndex = 0; bindingIndex < bindings.Count; bindingIndex++) { var preferenceKey = $"{name}_binding{bindingIndex}"; @@ -88,6 +94,10 @@ public void ResetDefaults() CreateControlMap(Control.OpenAdminPanel, new ControlValue(Keys.None, Keys.Insert), ControlValue.Default); CreateControlMap(Control.ToggleGui, new ControlValue(Keys.None, Keys.F11), ControlValue.Default); CreateControlMap(Control.TurnAround, new ControlValue(Keys.None, Keys.Control), ControlValue.Default); + CreateControlMap(Control.ToggleZoomIn, ControlValue.Default, ControlValue.Default); + CreateControlMap(Control.ToggleZoomOut, ControlValue.Default, ControlValue.Default); + CreateControlMap(Control.HoldToZoomIn, ControlValue.Default, ControlValue.Default); + CreateControlMap(Control.HoldToZoomOut, ControlValue.Default, ControlValue.Default); } private static void MigrateControlBindings(Control control) diff --git a/Intersect.Client/Core/Graphics.cs b/Intersect.Client/Core/Graphics.cs index a71fcb60b6..8d0a0da86e 100644 --- a/Intersect.Client/Core/Graphics.cs +++ b/Intersect.Client/Core/Graphics.cs @@ -13,6 +13,7 @@ using Intersect.Enums; using Intersect.GameObjects; using Intersect.Utilities; +using Newtonsoft.Json; namespace Intersect.Client.Core { @@ -29,7 +30,17 @@ public static partial class Graphics public static GameFont ChatBubbleFont; - public static FloatRect CurrentView; + private static FloatRect _currentView; + + public static FloatRect CurrentView + { + get => _currentView; + set + { + _currentView = value; + Renderer.SetView(_currentView); + } + } public static GameShader DefaultShader; @@ -98,6 +109,8 @@ public static partial class Graphics public static GameFont UIFont; + public static float BaseWorldScale => Options.Instance?.MapOpts?.TileScale ?? 1; + //Init Functions public static void InitGraphics() { @@ -242,7 +255,7 @@ public static void DrawInGame(TimeSpan deltaTime) if (GridSwitched) { //Brightness - var brightnessTarget = (byte) (currentMap.Brightness / 100f * 255); + var brightnessTarget = (byte)(currentMap.Brightness / 100f * 255); BrightnessLevel = brightnessTarget; PlayerLightColor.R = currentMap.PlayerLightColor.R; PlayerLightColor.G = currentMap.PlayerLightColor.G; @@ -252,10 +265,10 @@ public static void DrawInGame(TimeSpan deltaTime) sPlayerLightExpand = currentMap.PlayerLightExpand; //Overlay - OverlayColor.A = (byte) currentMap.AHue; - OverlayColor.R = (byte) currentMap.RHue; - OverlayColor.G = (byte) currentMap.GHue; - OverlayColor.B = (byte) currentMap.BHue; + OverlayColor.A = (byte)currentMap.AHue; + OverlayColor.R = (byte)currentMap.RHue; + OverlayColor.G = (byte)currentMap.GHue; + OverlayColor.B = (byte)currentMap.BHue; //Fog && Panorama currentMap.GridSwitched(); @@ -273,7 +286,7 @@ public static void DrawInGame(TimeSpan deltaTime) // Clear our previous darkness texture. ClearDarknessTexture(); - + var gridX = currentMap.GridX; var gridY = currentMap.GridY; @@ -340,7 +353,7 @@ public static void DrawInGame(TimeSpan deltaTime) { for (var x1 = gridX - 1; x1 <= gridX + 1; x1++) { - var y1 = gridY - 2 + (int) Math.Floor(y / (float) Options.MapHeight); + var y1 = gridY - 2 + (int)Math.Floor(y / (float)Options.MapHeight); if (x1 >= 0 && x1 < Globals.MapGridWidth && y1 >= 0 && @@ -422,7 +435,7 @@ public static void DrawInGame(TimeSpan deltaTime) { animInstance.Draw(true); } - + for (var x = gridX - 1; x <= gridX + 1; x++) { @@ -454,7 +467,7 @@ public static void DrawInGame(TimeSpan deltaTime) // Draw lighting effects. GenerateLightMap(); DrawDarkness(); - + for (var y = 0; y < Options.MapHeight * 5; y++) { for (var x = 0; x < 3; x++) @@ -517,7 +530,7 @@ public static void DrawInGame(TimeSpan deltaTime) } //Game Rendering - public static void Render(TimeSpan deltaTime) + public static void Render(TimeSpan deltaTime, TimeSpan totalTime) { var takingScreenshot = false; if (Renderer?.ScreenshotRequests.Count > 0) @@ -525,7 +538,14 @@ public static void Render(TimeSpan deltaTime) takingScreenshot = Renderer.BeginScreenshot(); } - if (!(Renderer?.Begin() ?? false)) + if (Renderer == default) + { + return; + } + + Renderer.Scale = Globals.GameState == GameStates.InGame ? Globals.Database.WorldZoom : 1.0f; + + if (!Renderer.Begin()) { return; } @@ -571,11 +591,13 @@ public static void Render(TimeSpan deltaTime) throw new ArgumentOutOfRangeException(); } + Renderer.Scale = Globals.Database.UIScale; + Interface.Interface.DrawGui(); DrawGameTexture( Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1), CurrentView, - new Color((int) Fade.GetFade(), 0, 0, 0), null, GameBlendModes.None + new Color((int)Fade.GetFade(), 0, 0, 0), null, GameBlendModes.None ); // Draw our mousecursor at the very end, but not when taking screenshots. @@ -647,97 +669,97 @@ public static void DrawOverlay() { if (OverlayColor.A < map.AHue) { - if (OverlayColor.A + (int) (255 * ecTime / 2000f) > map.AHue) + if (OverlayColor.A + (int)(255 * ecTime / 2000f) > map.AHue) { - OverlayColor.A = (byte) map.AHue; + OverlayColor.A = (byte)map.AHue; } else { - OverlayColor.A += (byte) (255 * ecTime / 2000f); + OverlayColor.A += (byte)(255 * ecTime / 2000f); } } if (OverlayColor.A > map.AHue) { - if (OverlayColor.A - (int) (255 * ecTime / 2000f) < map.AHue) + if (OverlayColor.A - (int)(255 * ecTime / 2000f) < map.AHue) { - OverlayColor.A = (byte) map.AHue; + OverlayColor.A = (byte)map.AHue; } else { - OverlayColor.A -= (byte) (255 * ecTime / 2000f); + OverlayColor.A -= (byte)(255 * ecTime / 2000f); } } if (OverlayColor.R < map.RHue) { - if (OverlayColor.R + (int) (255 * ecTime / 2000f) > map.RHue) + if (OverlayColor.R + (int)(255 * ecTime / 2000f) > map.RHue) { - OverlayColor.R = (byte) map.RHue; + OverlayColor.R = (byte)map.RHue; } else { - OverlayColor.R += (byte) (255 * ecTime / 2000f); + OverlayColor.R += (byte)(255 * ecTime / 2000f); } } if (OverlayColor.R > map.RHue) { - if (OverlayColor.R - (int) (255 * ecTime / 2000f) < map.RHue) + if (OverlayColor.R - (int)(255 * ecTime / 2000f) < map.RHue) { - OverlayColor.R = (byte) map.RHue; + OverlayColor.R = (byte)map.RHue; } else { - OverlayColor.R -= (byte) (255 * ecTime / 2000f); + OverlayColor.R -= (byte)(255 * ecTime / 2000f); } } if (OverlayColor.G < map.GHue) { - if (OverlayColor.G + (int) (255 * ecTime / 2000f) > map.GHue) + if (OverlayColor.G + (int)(255 * ecTime / 2000f) > map.GHue) { - OverlayColor.G = (byte) map.GHue; + OverlayColor.G = (byte)map.GHue; } else { - OverlayColor.G += (byte) (255 * ecTime / 2000f); + OverlayColor.G += (byte)(255 * ecTime / 2000f); } } if (OverlayColor.G > map.GHue) { - if (OverlayColor.G - (int) (255 * ecTime / 2000f) < map.GHue) + if (OverlayColor.G - (int)(255 * ecTime / 2000f) < map.GHue) { - OverlayColor.G = (byte) map.GHue; + OverlayColor.G = (byte)map.GHue; } else { - OverlayColor.G -= (byte) (255 * ecTime / 2000f); + OverlayColor.G -= (byte)(255 * ecTime / 2000f); } } if (OverlayColor.B < map.BHue) { - if (OverlayColor.B + (int) (255 * ecTime / 2000f) > map.BHue) + if (OverlayColor.B + (int)(255 * ecTime / 2000f) > map.BHue) { - OverlayColor.B = (byte) map.BHue; + OverlayColor.B = (byte)map.BHue; } else { - OverlayColor.B += (byte) (255 * ecTime / 2000f); + OverlayColor.B += (byte)(255 * ecTime / 2000f); } } if (OverlayColor.B > map.BHue) { - if (OverlayColor.B - (int) (255 * ecTime / 2000f) < map.BHue) + if (OverlayColor.B - (int)(255 * ecTime / 2000f) < map.BHue) { - OverlayColor.B = (byte) map.BHue; + OverlayColor.B = (byte)map.BHue; } else { - OverlayColor.B -= (byte) (255 * ecTime / 2000f); + OverlayColor.B -= (byte)(255 * ecTime / 2000f); } } } @@ -778,7 +800,7 @@ public static void DrawFullScreenTexture(GameTexture tex, float alpha = 1f) DrawGameTexture( tex, GetSourceRect(tex), new FloatRect(bgx + Renderer.GetView().X, bgy + Renderer.GetView().Y, bgw, bgh), - new Color((int) (alpha * 255f), 255, 255, 255) + new Color((int)(alpha * 255f), 255, 255, 255) ); } @@ -792,7 +814,7 @@ public static void DrawFullScreenTextureCentered(GameTexture tex, float alpha = DrawGameTexture( tex, GetSourceRect(tex), new FloatRect(bgx + Renderer.GetView().X, bgy + Renderer.GetView().Y, bgw, bgh), - new Color((int) (alpha * 255f), 255, 255, 255) + new Color((int)(alpha * 255f), 255, 255, 255) ); } @@ -808,7 +830,7 @@ public static void DrawFullScreenTextureStretched(GameTexture tex) public static void DrawFullScreenTextureFitWidth(GameTexture tex) { - var scale = Renderer.GetScreenWidth() / (float) tex.GetWidth(); + var scale = Renderer.GetScreenWidth() / (float)tex.GetWidth(); var scaledHeight = tex.GetHeight() * scale; var offsetY = (Renderer.GetScreenHeight() - tex.GetHeight()) / 2f; DrawGameTexture( @@ -821,7 +843,7 @@ public static void DrawFullScreenTextureFitWidth(GameTexture tex) public static void DrawFullScreenTextureFitHeight(GameTexture tex) { - var scale = Renderer.GetScreenHeight() / (float) tex.GetHeight(); + var scale = Renderer.GetScreenHeight() / (float)tex.GetHeight(); var scaledWidth = tex.GetWidth() * scale; var offsetX = (Renderer.GetScreenWidth() - scaledWidth) / 2f; DrawGameTexture( @@ -858,86 +880,99 @@ public static void DrawFullScreenTextureFitMaximum(GameTexture tex) private static void UpdateView() { - if (Globals.Me == null) - { - CurrentView = new FloatRect(0, 0, Renderer.GetScreenWidth(), Renderer.GetScreenHeight()); - Renderer.SetView(CurrentView); + var scale = Renderer.Scale; + if (Globals.GameState != GameStates.InGame || !MapInstance.TryGet(Globals.Me?.MapId ?? Guid.Empty, out var map)) + { + var sw = Renderer.GetScreenWidth(); + var sh = Renderer.GetScreenHeight(); + var sx = 0;//sw - (sw / scale); + var sy = 0;//sh - (sh / scale); + CurrentView = new FloatRect(sx, sy, sw / scale, sh / scale); return; } - var map = MapInstance.Get(Globals.Me.MapId); - if (Globals.GameState == GameStates.InGame && map != null) - { - var en = Globals.Me; - var x = map.GetX() - Options.MapWidth * Options.TileWidth; - var y = map.GetY() - Options.MapHeight * Options.TileHeight; - var x1 = map.GetX() + Options.MapWidth * Options.TileWidth * 2; - var y1 = map.GetY() + Options.MapHeight * Options.TileHeight * 2; - if (map.CameraHolds[(int) Direction.Up]) - { - y += Options.MapHeight * Options.TileHeight; - } + var mapWidth = Options.MapWidth * Options.TileWidth; + var mapHeight = Options.MapHeight * Options.TileHeight; - if (map.CameraHolds[(int) Direction.Left]) - { - x += Options.MapWidth * Options.TileWidth; - } + var en = Globals.Me; + float x = mapWidth; + float y = mapHeight; + float x1 = mapWidth * 2; + float y1 = mapHeight * 2; + + if (map.CameraHolds[(int)Direction.Left]) + { + x -= mapWidth; + } + + if (map.CameraHolds[(int)Direction.Up]) + { + y -= mapHeight; + } - if (map.CameraHolds[(int) Direction.Right]) + if (map.CameraHolds[(int)Direction.Right]) + { + x1 -= mapWidth; + } + + if (map.CameraHolds[(int)Direction.Down]) + { + y1 -= mapHeight; + } + + x = map.GetX() - x; + y = map.GetY() - y; + x1 = map.GetX() + x1; + y1 = map.GetY() + y1; + + var w = x1 - x; + var h = y1 - y; + var restrictView = new FloatRect( + x, + y, + w, + h + ); + var newView = new FloatRect( + (int)Math.Ceiling(en.Center.X - Renderer.ScreenWidth / scale / 2f), + (int)Math.Ceiling(en.Center.Y - Renderer.ScreenHeight / scale / 2f), + Renderer.ScreenWidth / scale, + Renderer.ScreenHeight / scale + ); + + if (restrictView.Width >= newView.Width) + { + if (newView.Left < restrictView.Left) { - x1 -= Options.MapWidth * Options.TileWidth; + newView.X = restrictView.Left; } - if (map.CameraHolds[(int) Direction.Down]) + if (newView.Right > restrictView.Right) { - y1 -= Options.MapHeight * Options.TileHeight; + newView.X = restrictView.Right - newView.Width; } + } - var w = x1 - x; - var h = y1 - y; - var restrictView = new FloatRect(x, y, w, h); - CurrentView = new FloatRect( - (int) Math.Ceiling(en.Center.X - Renderer.ScreenWidth / 2f), - (int) Math.Ceiling(en.Center.Y - Renderer.ScreenHeight / 2f), - Renderer.ScreenWidth, - Renderer.ScreenHeight - ); - - if (restrictView.Width >= CurrentView.Width) + if (restrictView.Height >= newView.Height) + { + if (newView.Top < restrictView.Top) { - if (CurrentView.Left < restrictView.Left) - { - CurrentView.X = restrictView.Left; - } - - if (CurrentView.Left + CurrentView.Width > restrictView.Left + restrictView.Width) - { - CurrentView.X -= - CurrentView.Left + CurrentView.Width - (restrictView.Left + restrictView.Width); - } + newView.Y = restrictView.Top; } - if (restrictView.Height >= CurrentView.Height) + if (newView.Bottom > restrictView.Bottom) { - if (CurrentView.Top < restrictView.Top) - { - CurrentView.Y = restrictView.Top; - } - - if (CurrentView.Top + CurrentView.Height > restrictView.Top + restrictView.Height) - { - CurrentView.Y -= - CurrentView.Top + CurrentView.Height - (restrictView.Top + restrictView.Height); - } + newView.Y = restrictView.Bottom - newView.Height; } } - else - { - CurrentView = new FloatRect(0, 0, Renderer.GetScreenWidth(), Renderer.GetScreenHeight()); - } - Renderer.SetView(CurrentView); + CurrentView = new FloatRect( + newView.X, + newView.Y, + newView.Width * scale, + newView.Height * scale + ); } //Lighting @@ -981,7 +1016,7 @@ private static void GenerateLightMap() DrawGameTexture( Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1), new FloatRect(0, 0, sDarknessTexture.GetWidth(), sDarknessTexture.GetHeight()), - new Color((byte) BrightnessLevel, 255, 255, 255), sDarknessTexture, GameBlendModes.Add + new Color((byte)BrightnessLevel, 255, 255, 255), sDarknessTexture, GameBlendModes.Add ); } else @@ -996,18 +1031,18 @@ private static void GenerateLightMap() Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1), new FloatRect(0, 0, sDarknessTexture.GetWidth(), sDarknessTexture.GetHeight()), new Color( - (int) Time.GetTintColor().A, (int) Time.GetTintColor().R, (int) Time.GetTintColor().G, - (int) Time.GetTintColor().B + (int)Time.GetTintColor().A, (int)Time.GetTintColor().R, (int)Time.GetTintColor().G, + (int)Time.GetTintColor().B ), sDarknessTexture, GameBlendModes.None ); } AddLight( - (int) Math.Ceiling(Globals.Me.Center.X), (int) Math.Ceiling(Globals.Me.Center.Y), - (int) sPlayerLightSize, (byte) sPlayerLightIntensity, sPlayerLightExpand, + (int)Math.Ceiling(Globals.Me.Center.X), (int)Math.Ceiling(Globals.Me.Center.Y), + (int)sPlayerLightSize, (byte)sPlayerLightIntensity, sPlayerLightExpand, Color.FromArgb( - (int) PlayerLightColor.A, (int) PlayerLightColor.R, (int) PlayerLightColor.G, - (int) PlayerLightColor.B + (int)PlayerLightColor.A, (int)PlayerLightColor.R, (int)PlayerLightColor.G, + (int)PlayerLightColor.B ) ); @@ -1094,7 +1129,7 @@ public static void UpdatePlayerLight() { float ecTime = Timing.Global.MillisecondsUtc - sLightUpdate; var valChange = 255 * ecTime / 2000f; - var brightnessTarget = (byte) (map.Brightness / 100f * 255); + var brightnessTarget = (byte)(map.Brightness / 100f * 255); if (BrightnessLevel < brightnessTarget) { if (BrightnessLevel + valChange > brightnessTarget) @@ -1272,7 +1307,7 @@ public static void UpdatePlayerLight() } // Cap instensity between 0 and 255 so as not to overflow (as it is an alpha value) - sPlayerLightIntensity = (float) MathHelper.Clamp(sPlayerLightIntensity, 0f, 255f); + sPlayerLightIntensity = (float)MathHelper.Clamp(sPlayerLightIntensity, 0f, 255f); sLightUpdate = Timing.Global.MillisecondsUtc; } } diff --git a/Intersect.Client/Core/Input.cs b/Intersect.Client/Core/Input.cs index a6deceda15..d9417906a4 100644 --- a/Intersect.Client/Core/Input.cs +++ b/Intersect.Client/Core/Input.cs @@ -25,6 +25,24 @@ public static partial class Input public static HandleKeyEvent MouseUp; + private static void HandleZoomOut() + { + Globals.Database.WorldZoom /= 2; + if (Globals.Database.WorldZoom < Graphics.BaseWorldScale) + { + Globals.Database.WorldZoom = Graphics.BaseWorldScale * 4; + } + } + + private static void HandleZoomIn() + { + Globals.Database.WorldZoom *= 2; + if (Globals.Database.WorldZoom > Graphics.BaseWorldScale * 4) + { + Globals.Database.WorldZoom = Graphics.BaseWorldScale; + } + } + public static void OnKeyPressed(Keys modifier, Keys key) { if (key == Keys.None) @@ -144,6 +162,20 @@ public static void OnKeyPressed(Keys modifier, Keys key) break; + case Control.HoldToZoomIn: + case Control.ToggleZoomIn: + { + HandleZoomIn(); + break; + } + + case Control.HoldToZoomOut: + case Control.ToggleZoomOut: + { + HandleZoomOut(); + break; + } + case Control.OpenDebugger: MutableInterface.ToggleDebug(); break; @@ -282,6 +314,16 @@ public static void OnKeyReleased(Keys modifier, Keys key) return; } + if (Controls.Controls.ControlHasKey(Control.HoldToZoomIn, modifier, key)) + { + HandleZoomOut(); + } + + if (Controls.Controls.ControlHasKey(Control.HoldToZoomOut, modifier, key)) + { + HandleZoomIn(); + } + if (Globals.Me == null) { return; @@ -338,7 +380,7 @@ public static void OnMouseDown(Keys modifier, MouseButtons btn) return; } - if (Globals.Me.TryTarget()) + if (modifier == Keys.None && btn == MouseButtons.Left && Globals.Me.TryTarget()) { return; } @@ -400,6 +442,16 @@ public static void OnMouseUp(Keys modifier, MouseButtons btn) return; } + if (Controls.Controls.ControlHasKey(Control.HoldToZoomIn, modifier, key)) + { + HandleZoomOut(); + } + + if (Controls.Controls.ControlHasKey(Control.HoldToZoomOut, modifier, key)) + { + HandleZoomIn(); + } + if (Globals.Me == null) { return; diff --git a/Intersect.Client/Entities/Player.cs b/Intersect.Client/Entities/Player.cs index 1f8e778471..59578646ec 100644 --- a/Intersect.Client/Entities/Player.cs +++ b/Intersect.Client/Entities/Player.cs @@ -1787,8 +1787,7 @@ public bool TryTarget() else if (!Globals.Database.StickyTarget) { // We've clicked off of our target and are allowed to clear it! - ClearTarget(); - return true; + return ClearTarget(); } } @@ -1844,12 +1843,18 @@ public bool TryTarget(IEntity entity, bool force = false) } - public void ClearTarget() + public bool ClearTarget() { SetTargetBox(null); + if (TargetIndex == default && TargetType == -1) + { + return false; + } + TargetIndex = Guid.Empty; TargetType = -1; + return true; } /// diff --git a/Intersect.Client/Interface/Game/Chat/Chatbox.cs b/Intersect.Client/Interface/Game/Chat/Chatbox.cs index 87478eff27..bd51e505d4 100644 --- a/Intersect.Client/Interface/Game/Chat/Chatbox.cs +++ b/Intersect.Client/Interface/Game/Chat/Chatbox.cs @@ -14,6 +14,7 @@ using Intersect.Client.Networking; using Intersect.Configuration; using Intersect.Enums; +using Intersect.Extensions; using Intersect.Localization; using Intersect.Utilities; @@ -246,7 +247,7 @@ public void OpenContextMenu(string name) private void MGuildInviteContextItem_Clicked(Base sender, ClickedEventArgs arguments) { - var name = (string) sender.Parent.UserData; + var name = (string)sender.Parent.UserData; PacketSender.SendInviteGuild(name); } @@ -321,7 +322,7 @@ private void SetChannelToTab(ChatboxTab tab) { case ChatboxTab.System: case ChatboxTab.All: - mChannelCombobox.SelectByUserData(mLastChatChannel[tab]); + mChannelCombobox.SelectByUserData(mLastChatChannel[tab]); break; case ChatboxTab.Local: @@ -420,7 +421,7 @@ private void ChatboxRow_RightClicked(Base sender, ClickedEventArgs arguments) if (ClientConfiguration.Instance.EnableContextMenus) { OpenContextMenu(target); - } + } else { SetChatboxText($"/pm {target} "); @@ -439,8 +440,8 @@ public void SetChatboxText(string msg) private void ChatboxRow_Clicked(Base sender, ClickedEventArgs arguments) { - var rw = (ListBoxRow) sender; - var target = (string) rw.UserData; + var rw = (ListBoxRow)sender; + var target = (string)rw.UserData; if (!string.IsNullOrWhiteSpace(target)) { if (mGameUi.AdminWindowOpen()) @@ -549,13 +550,14 @@ private void HideChatLog() void TrySendMessage() { var msg = mChatboxInput.Text.Trim(); + if (string.IsNullOrWhiteSpace(msg) || string.Equals(msg, GetDefaultInputText(), StringComparison.Ordinal)) { mChatboxInput.Text = GetDefaultInputText(); return; } - + if (mLastChatTime > Timing.Global.MillisecondsUtc) { ChatboxMsg.AddMessage(new ChatboxMsg(Strings.Chatbox.toofast, Color.Red, ChatMessageType.Error)); diff --git a/Intersect.Client/Interface/Game/EscapeMenu.cs b/Intersect.Client/Interface/Game/EscapeMenu.cs index 969e25fd88..7514aea3f7 100644 --- a/Intersect.Client/Interface/Game/EscapeMenu.cs +++ b/Intersect.Client/Interface/Game/EscapeMenu.cs @@ -134,7 +134,7 @@ public void OpenSettingsWindow() /// public override void ToggleHidden() { - if (mSettingsWindow.IsVisible()) + if (mSettingsWindow.IsVisible) { return; } diff --git a/Intersect.Client/Interface/Shared/SettingsWindow.cs b/Intersect.Client/Interface/Shared/SettingsWindow.cs index d4bdbd909f..baa6125f1e 100644 --- a/Intersect.Client/Interface/Shared/SettingsWindow.cs +++ b/Intersect.Client/Interface/Shared/SettingsWindow.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Intersect.Client.Core; using Intersect.Client.Core.Controls; using Intersect.Client.Framework.File_Management; @@ -14,6 +15,7 @@ using Intersect.Client.Localization; using Intersect.Utilities; using static Intersect.Client.Framework.File_Management.GameContentManager; +using MathHelper = Intersect.Client.Utilities.MathHelper; namespace Intersect.Client.Interface.Shared { @@ -107,6 +109,10 @@ public partial class SettingsWindow private readonly ComboBox mFpsList; + private readonly LabeledHorizontalSlider _worldScale; + + private readonly LabeledHorizontalSlider _uiScale; + private readonly LabeledCheckBox mFullscreenCheckbox; private readonly LabeledCheckBox mLightingEnabledCheckbox; @@ -319,13 +325,36 @@ public SettingsWindow(Base parent, MainMenu mainMenu, EscapeMenu escapeMenu) } ); + Globals.Database.WorldZoom = MathHelper.Clamp(Globals.Database.WorldZoom, 1, 4); + + var worldScaleNotches = new double[] { 1, 2, 4 }.Select(n => n * Graphics.BaseWorldScale).ToArray(); + _worldScale = new LabeledHorizontalSlider(mVideoSettingsContainer, "WorldScale") + { + Label = Strings.Settings.WorldScale, + Min = worldScaleNotches.Min(), + Max = worldScaleNotches.Max(), + Notches = worldScaleNotches, + SnapToNotches = false, + Value = Globals.Database.WorldZoom, + }; + + // _uiScale = new LabeledHorizontalSlider(mVideoSettingsContainer, "UIScale") + // { + // Label = Strings.Settings.UIScale, + // Min = 0.5f, + // Max = 4f, + // Notches = new double[] { 0.5, 1, 2, 4 }, + // SnapToNotches = true, + // Value = 1f, + // }; + // Video Settings - FPS Background. var fpsBackground = new ImagePanel(mVideoSettingsContainer, "FPSPanel"); // Video Settings - FPS Label. var fpsLabel = new Label(fpsBackground, "FPSLabel"); fpsLabel.SetText(Strings.Settings.TargetFps); - + // Video Settings - FPS List. mFpsList = new ComboBox(fpsBackground, "FPSCombobox"); mFpsList.AddItem(Strings.Settings.Vsync); @@ -594,6 +623,17 @@ private void LoadSettingsWindow() mSettingsApplyBtn.Show(); mSettingsCancelBtn.Show(); mKeybindingRestoreBtn.Hide(); + + var worldScaleNotches = new double[] { 1, 2, 4 }.Select(n => n * Graphics.BaseWorldScale).ToArray(); + + Globals.Database.WorldZoom = (float)MathHelper.Clamp( + Globals.Database.WorldZoom, + worldScaleNotches.Min(), + worldScaleNotches.Max() + ); + _worldScale.Min = worldScaleNotches.Min(); + _worldScale.Max = worldScaleNotches.Max(); + _worldScale.Value = Globals.Database.WorldZoom; } private readonly HashSet _keysDown = new HashSet(); @@ -659,7 +699,7 @@ private void OnKeyUp(Keys modifier, Keys key) // Methods. public void Update() { - if (mSettingsPanel.IsVisible && + if (IsVisible && mKeybindingEditBtn != null && mKeybindingListeningTimer < Timing.Global.MillisecondsUtc) { @@ -701,6 +741,9 @@ public void Show(bool returnToMenu = false) mFullscreenCheckbox.IsChecked = Globals.Database.FullScreen; mLightingEnabledCheckbox.IsChecked = Globals.Database.EnableLighting; + // _uiScale.Value = Globals.Database.UIScale; + _worldScale.Value = Globals.Database.WorldZoom; + if (Graphics.Renderer.GetValidVideoModes().Count > 0) { string resolutionLabel; @@ -776,7 +819,7 @@ public void Show(bool returnToMenu = false) mReturnToMenu = returnToMenu; } - public bool IsVisible() => !mSettingsPanel.IsHidden; + public bool IsVisible => !mSettingsPanel.IsHidden; public void Hide() { @@ -879,6 +922,10 @@ private void SettingsApplyBtn_Clicked(Base sender, ClickedEventArgs arguments) Globals.Database.TypewriterBehavior = mTypewriterCheckbox.IsChecked ? Enums.TypewriterBehavior.Word : Enums.TypewriterBehavior.Off; // Video Settings. + + // Globals.Database.UIScale = (float)_uiScale.Value; + Globals.Database.WorldZoom = (float)_worldScale.Value; + var resolution = mResolutionList.SelectedItem; var validVideoModes = Graphics.Renderer.GetValidVideoModes(); var targetResolution = validVideoModes?.FindIndex(videoMode => diff --git a/Intersect.Client/Localization/Strings.cs b/Intersect.Client/Localization/Strings.cs index c1b68a9c4d..7906de7a10 100644 --- a/Intersect.Client/Localization/Strings.cs +++ b/Intersect.Client/Localization/Strings.cs @@ -406,6 +406,8 @@ public partial struct Admin public static LocalizedString warpme2 = @"Warp Me To"; + public static LocalizedString Zoom = @"Zoom"; + } public partial struct Bags @@ -718,6 +720,10 @@ public partial struct Controls {"openadminpanel", @"Open Admin Panel:"}, {"togglegui", @"Toggle Interface:"}, {"turnaround", @"Hold to turn around:"}, + {"togglezoomin", "Toggle Zoom In:"}, + {"holdtozoomin", "Hold to Zoom In:"}, + {"togglezoomout", "Toggle Zoom Out:"}, + {"holdtozoomout", "Hold to Zoom Out:"}, }; public static LocalizedString listening = @"Listening"; @@ -1825,6 +1831,9 @@ public partial struct Settings [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString TypewriterText = @"Typewriter Text"; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString UIScale = @"UI Scale"; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString UnlimitedFps = @"No Limit"; @@ -1833,6 +1842,9 @@ public partial struct Settings [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString Vsync = @"V-Sync"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString WorldScale = @"World Scale"; } public partial struct Parties diff --git a/Intersect.Client/MonoGame/Database/MonoDatabase.cs b/Intersect.Client/MonoGame/Database/MonoDatabase.cs index d6d0f42e49..251bb8f552 100644 --- a/Intersect.Client/MonoGame/Database/MonoDatabase.cs +++ b/Intersect.Client/MonoGame/Database/MonoDatabase.cs @@ -39,7 +39,7 @@ public override void DeletePreference(string key) public override bool HasPreference(string key) => GetInstanceKey()?.GetValue(key) != default; - public override void SavePreference(string key, object value) + public override void SavePreference(string key, TValue value) { try { diff --git a/Intersect.Client/MonoGame/Graphics/MonoRenderer.cs b/Intersect.Client/MonoGame/Graphics/MonoRenderer.cs index 95452667a4..b57ec65b30 100644 --- a/Intersect.Client/MonoGame/Graphics/MonoRenderer.cs +++ b/Intersect.Client/MonoGame/Graphics/MonoRenderer.cs @@ -199,7 +199,7 @@ public void UpdateGraphicsState(int width, int height, bool initial = false) var displayBounds = Sdl2.GetDisplayBounds(); var currentDisplayBounds = displayBounds[0]; mGameWindow.Position = new Microsoft.Xna.Framework.Point( - currentDisplayBounds.x + (currentDisplayBounds.w - mScreenWidth) / 2, + currentDisplayBounds.x + (currentDisplayBounds.w - mScreenWidth) / 2, currentDisplayBounds.y + (currentDisplayBounds.h - mScreenHeight) / 2 ); } @@ -248,8 +248,17 @@ public override bool Begin() UpdateGraphicsState(mScreenWidth, mScreenHeight); } - StartSpritebatch(mCurrentView, GameBlendModes.None, null, null, true, null); + return RecreateSpriteBatch(); + } + + protected override bool RecreateSpriteBatch() + { + if (mSpriteBatchBegan) + { + EndSpriteBatch(); + } + StartSpritebatch(mCurrentView, GameBlendModes.None, null, null, true, null); return true; } @@ -342,11 +351,13 @@ private void StartSpritebatch( } mSpriteBatch.Begin( - drawImmediate ? SpriteSortMode.Immediate : SpriteSortMode.Deferred, blend, SamplerState.PointClamp, - null, rs, useEffect, - Matrix.CreateRotationZ(0f) * - Matrix.CreateScale(new Vector3(1, 1, 1)) * - Matrix.CreateTranslation(-view.X, -view.Y, 0) + drawImmediate ? SpriteSortMode.Immediate : SpriteSortMode.Deferred, + blend, + SamplerState.PointClamp, + null, + rs, + useEffect, + CreateViewMatrix(view) ); mCurrentSpriteView = view; @@ -396,7 +407,7 @@ public override void DrawTileBuffer(GameTileBuffer buffer) mGraphicsDevice.SetRenderTarget(mScreenshotRenderTarget); mGraphicsDevice.BlendState = mNormalState; mGraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise; - mGraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp; + mGraphicsDevice.SamplerStates[0] = SamplerState.PointClamp; mGraphicsDevice.DepthStencilState = DepthStencilState.None; ((MonoTileBuffer)buffer).Draw(mBasicEffect, mCurrentView); } @@ -893,6 +904,13 @@ public override Pointf MeasureText(string text, GameFont gameFont, float fontSca return new Pointf(size.X * fontScale, size.Y * fontScale); } + private Matrix CreateViewMatrix(FloatRect view) + { + return Matrix.CreateRotationZ(0f) * + Matrix.CreateTranslation(-view.X, -view.Y, 0) * + Matrix.CreateScale(new Vector3(_scale)); + } + public override void SetView(FloatRect view) { mCurrentView = view; @@ -901,9 +919,7 @@ public override void SetView(FloatRect view) projection.M41 += -0.5f * projection.M11; projection.M42 += -0.5f * projection.M22; mBasicEffect.Projection = projection; - mBasicEffect.View = Matrix.CreateRotationZ(0f) * - Matrix.CreateScale(new Vector3(1, 1, 1)) * - Matrix.CreateTranslation(-view.X, -view.Y, 0); + mBasicEffect.View = CreateViewMatrix(view); } public override bool BeginScreenshot() diff --git a/Intersect.Client/MonoGame/IntersectGame.cs b/Intersect.Client/MonoGame/IntersectGame.cs index f62ddeb325..a832b3fcbd 100644 --- a/Intersect.Client/MonoGame/IntersectGame.cs +++ b/Intersect.Client/MonoGame/IntersectGame.cs @@ -298,7 +298,7 @@ protected override void Draw(GameTime gameTime) { lock (Globals.GameLock) { - Core.Graphics.Render(gameTime.ElapsedGameTime); + Core.Graphics.Render(gameTime.ElapsedGameTime, gameTime.TotalGameTime); } } }