diff --git a/ACViewer/Config/BackgroundColors.cs b/ACViewer/Config/BackgroundColors.cs new file mode 100644 index 0000000..1604572 --- /dev/null +++ b/ACViewer/Config/BackgroundColors.cs @@ -0,0 +1,62 @@ +using System; +using System.Globalization; +using Microsoft.Xna.Framework; +using Newtonsoft.Json; + +namespace ACViewer.Config +{ + public class BackgroundColors + { + [JsonConverter(typeof(JsonConverter_Color))] + public Color ModelViewer { get; set; } + + [JsonConverter(typeof(JsonConverter_Color))] + public Color ParticleViewer { get; set; } + + [JsonConverter(typeof(JsonConverter_Color))] + public Color TextureViewer { get; set; } + + [JsonConverter(typeof(JsonConverter_Color))] + public Color WorldViewer { get; set; } + + public BackgroundColors() + { + // defaults + ModelViewer = Color.Black; + ParticleViewer = Color.Black; + TextureViewer = Color.Black; + WorldViewer = new Color(32, 32, 32); + } + } + + public sealed class JsonConverter_Color : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return typeof(Color).Equals(objectType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var color = (Color)value; + + writer.WriteValue($"#{color.R:X2}{color.G:X2}{color.B:X2}"); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var str = reader.Value.ToString(); + + if (str == null || str.Length != 7 || str[0] != '#') + return Color.Black; + + if (!byte.TryParse(str.Substring(1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var r) || + !byte.TryParse(str.Substring(3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var g) || + !byte.TryParse(str.Substring(5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var b)) + { + return Color.Black; + } + return new Color(r, g, b); + } + } +} diff --git a/ACViewer/Config/Config.cs b/ACViewer/Config/Config.cs index 3fa4f21..c58c608 100644 --- a/ACViewer/Config/Config.cs +++ b/ACViewer/Config/Config.cs @@ -7,5 +7,6 @@ public class Config public Database Database { get; set; } = new Database(); public Toggles Toggles { get; set; } = new Toggles(); public MapViewerOptions MapViewer { get; set; } = new MapViewerOptions(); + public BackgroundColors BackgroundColors { get; set; } = new BackgroundColors(); } } diff --git a/ACViewer/Extensions/ColorDialogEx.cs b/ACViewer/Extensions/ColorDialogEx.cs new file mode 100644 index 0000000..8dbbd5e --- /dev/null +++ b/ACViewer/Extensions/ColorDialogEx.cs @@ -0,0 +1,133 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace ACViewer.Extensions +{ + public class ColorDialogEx : ColorDialog + { + #region private const + //Windows Message Constants + private const int WM_INITDIALOG = 0x0110; + private const int WM_CTLCOLOREDIT = 0x0133; + private const int WM_CTLCOLORSTATIC = 0x0138; + + //Window Controls + private const int COLOR_RED = 706; + private const int COLOR_GREEN = 707; + private const int COLOR_BLUE = 708; + + //uFlag Constants + private const uint SWP_NOSIZE = 0x0001; + private const uint SWP_SHOWWINDOW = 0x0040; + private const uint SWP_NOZORDER = 0x0004; + private const uint UFLAGS = SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW; + #endregion + + #region private readonly + //Windows Handle Constants + private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); + private static readonly IntPtr HWND_TOP = new IntPtr(0); + private static readonly IntPtr HWND_BOTTOM = new IntPtr(1); + #endregion + + #region private vars + //Module vars + private int _x; + private int _y; + private string _title = null; + #endregion + + #region private static methods imports + //WinAPI definitions + + /// + /// Sets the window text. + /// + /// The h WND. + /// The text. + /// + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern bool SetWindowText(IntPtr hWnd, string text); + + /// + /// Sets the window pos. + /// + /// The h WND. + /// The h WND insert after. + /// The x. + /// The y. + /// The cx. + /// The cy. + /// The u flags. + /// + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] + private static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem); + + [DllImport("user32.dll")] + private static extern int GetDlgItemInt(IntPtr hDlg, int nIDDlgItem, IntPtr lpTranslated, bool bSigned); + #endregion + + #region public constructor + /// + /// Initializes a new instance of the class. + /// + /// The X position + /// The Y position + /// The title of the windows. If set to null(by default), the title will not be changed + public ColorDialogEx(int x, int y, String title = null) + { + _x = x; + _y = y; + _title = title; + } + #endregion + + #region protected override methods + /// + /// Defines the common dialog box hook procedure that is overridden to add specific functionality to a common dialog box. + /// + /// The handle to the dialog box window. + /// The message being received. + /// Additional information about the message. + /// Additional information about the message. + /// + /// A zero value if the default dialog box procedure processes the message; a nonzero value if the default dialog box procedure ignores the message. + /// + protected override IntPtr HookProc(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) + { + //We do the base initialization + IntPtr hookProc = base.HookProc(hWnd, msg, wparam, lparam); + //When we init the dialog + if (msg == WM_INITDIALOG) + { + //We change the title + if (!string.IsNullOrEmpty(_title)) + { + SetWindowText(hWnd, _title); + } + //We move the position + SetWindowPos(hWnd, HWND_TOP, _x, _y, 0, 0, UFLAGS); + } + else if (msg == WM_CTLCOLOREDIT) + { + if (ColorEditCallback != null) + { + var r = GetDlgItemInt(hWnd, COLOR_RED, IntPtr.Zero, false); + var g = GetDlgItemInt(hWnd, COLOR_GREEN, IntPtr.Zero, false); + var b = GetDlgItemInt(hWnd, COLOR_BLUE, IntPtr.Zero, false); + ColorEditCallback(r, g, b); + } + } + return hookProc; + } + #endregion + + public Action ColorEditCallback { get; set; } + } +} diff --git a/ACViewer/Extensions/ColorExtensions.cs b/ACViewer/Extensions/ColorExtensions.cs new file mode 100644 index 0000000..0c36cf9 --- /dev/null +++ b/ACViewer/Extensions/ColorExtensions.cs @@ -0,0 +1,27 @@ +using System.Windows.Media; + +namespace ACViewer.Extensions +{ + public static class ColorExtensions + { + public static SolidColorBrush ToSolidColorBrush(this System.Drawing.Color c) + { + return new SolidColorBrush(Color.FromArgb(c.A, c.R, c.G, c.B)); + } + + public static SolidColorBrush ToSolidColorBrush(this Microsoft.Xna.Framework.Color c) + { + return new SolidColorBrush(Color.FromArgb(c.A, c.R, c.G, c.B)); + } + + public static System.Drawing.Color ToColor(this SolidColorBrush brush) + { + return System.Drawing.Color.FromArgb(brush.Color.A, brush.Color.R, brush.Color.G, brush.Color.B); + } + + public static Microsoft.Xna.Framework.Color ToXNAColor(this SolidColorBrush brush) + { + return new Microsoft.Xna.Framework.Color(brush.Color.R, brush.Color.G, brush.Color.B, brush.Color.A); + } + } +} diff --git a/ACViewer/Extensions/CommandHandler.cs b/ACViewer/Extensions/CommandHandler.cs new file mode 100644 index 0000000..2ab31e4 --- /dev/null +++ b/ACViewer/Extensions/CommandHandler.cs @@ -0,0 +1,54 @@ +using System; +using System.Windows.Input; + +namespace ACViewer.Extensions +{ + public class CommandHandler : ICommand + { + private Action _action; + private Func _canExecute; + + /// + /// Creates instance of the command handler + /// + /// Action to be executed by the command + /// A bolean property to containing current permissions to execute the command + public CommandHandler(Action action, Func canExecute) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + _action = action; + _canExecute = canExecute ?? (x => true); + } + public CommandHandler(Action action) : this(action, null) + { + } + + /// + /// Wires CanExecuteChanged event + /// + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + /// + /// Forcess checking if execute is allowed + /// + /// + /// + public bool CanExecute(object parameter) + { + return _canExecute(parameter); + } + + public void Execute(object parameter) + { + _action(parameter); + } + public void Refresh() + { + CommandManager.InvalidateRequerySuggested(); + } + } +} diff --git a/ACViewer/GameView.cs b/ACViewer/GameView.cs index dfa9539..a2e2f12 100644 --- a/ACViewer/GameView.cs +++ b/ACViewer/GameView.cs @@ -180,12 +180,8 @@ protected override void Update(GameTime time) base.Update(time); } - private static readonly Color BackgroundColor = new Color(0, 0, 0); - protected override void Draw(GameTime time) { - GraphicsDevice.Clear(BackgroundColor); - switch (ViewMode) { case ViewMode.Texture: diff --git a/ACViewer/MapViewer.cs b/ACViewer/MapViewer.cs index 4a8fe18..7333b46 100644 --- a/ACViewer/MapViewer.cs +++ b/ACViewer/MapViewer.cs @@ -48,6 +48,7 @@ public MouseState PrevMouseState public Matrix Translate { get; set; } = Matrix.Identity; public Vector2 ImagePos { get; set; } + public Matrix BlockTranslate { get; set; } public Texture2D Highlight { get; set; } @@ -226,7 +227,7 @@ public void Update(GameTime gameTime) WorldViewer.Instance.LoadLandblocks(startBlock, endBlock); } - if (mouseState.RightButton == ButtonState.Pressed) + if (mouseState.RightButton == ButtonState.Pressed && PrevMouseState.RightButton == ButtonState.Pressed) { var delta = PrevMouseState.Position - mouseState.Position; Pos -= new Vector2(delta.X, delta.Y); @@ -337,7 +338,7 @@ public void OnZoom(float scrollWheel) /// Provides a snapshot of timing values. public void Draw(GameTime gameTime) { - GraphicsDevice.Clear(Color.Black); + GraphicsDevice.Clear(ConfigManager.Config.BackgroundColors.TextureViewer); if (WorldMap == null) return; diff --git a/ACViewer/ModelViewer.cs b/ACViewer/ModelViewer.cs index 331d2f7..2898fa6 100644 --- a/ACViewer/ModelViewer.cs +++ b/ACViewer/ModelViewer.cs @@ -12,6 +12,7 @@ using ACE.Server.Physics.Animation; +using ACViewer.Config; using ACViewer.Enum; using ACViewer.Model; using ACViewer.Render; @@ -220,6 +221,8 @@ public void DrawModel() { if (Setup == null) return; + GraphicsDevice.Clear(ConfigManager.Config.BackgroundColors.ModelViewer); + Setup.Draw(PolyIdx, PartIdx); if (ViewObject.PhysicsObj.ParticleManager != null) diff --git a/ACViewer/ParticleViewer.cs b/ACViewer/ParticleViewer.cs index dc8adb4..7ab1c29 100644 --- a/ACViewer/ParticleViewer.cs +++ b/ACViewer/ParticleViewer.cs @@ -13,6 +13,7 @@ using ACE.Server.Physics; using ACE.Server.Physics.Animation; +using ACViewer.Config; using ACViewer.Enum; using ACViewer.Model; using ACViewer.Render; @@ -133,7 +134,7 @@ public void Update(GameTime gameTime) public void Draw(GameTime gameTime) { - GraphicsDevice.Clear(new Color(0, 0, 0)); + GraphicsDevice.Clear(ConfigManager.Config.BackgroundColors.ParticleViewer); Effect.CurrentTechnique = Effect.Techniques["ColoredNoShading"]; Effect.Parameters["xWorld"].SetValue(Matrix.Identity); diff --git a/ACViewer/Render/Camera.cs b/ACViewer/Render/Camera.cs index b441d41..9006399 100644 --- a/ACViewer/Render/Camera.cs +++ b/ACViewer/Render/Camera.cs @@ -340,7 +340,7 @@ public void Update(GameTime gameTime) } else if (PrevMouseState.RightButton == ButtonState.Pressed) { - System.Windows.Input.Mouse.OverrideCursor = Cursors.Arrow; + System.Windows.Input.Mouse.OverrideCursor = null; } PrevMouseState = mouseState; diff --git a/ACViewer/Render/Render.cs b/ACViewer/Render/Render.cs index 48da02e..110a17a 100644 --- a/ACViewer/Render/Render.cs +++ b/ACViewer/Render/Render.cs @@ -11,6 +11,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using ACViewer.Config; using ACViewer.Enum; using ACViewer.View; @@ -57,7 +58,7 @@ public void SetRasterizerState(bool wireframe = true) var rs = new RasterizerState(); //rs.CullMode = CullMode.CullClockwiseFace; - rs.CullMode = Microsoft.Xna.Framework.Graphics.CullMode.None; + rs.CullMode = CullMode.None; if (wireframe) rs.FillMode = FillMode.WireFrame; @@ -67,11 +68,9 @@ public void SetRasterizerState(bool wireframe = true) GraphicsDevice.RasterizerState = rs; } - private static readonly Color BackgroundColor = new Color(32, 32, 32); - public void Draw() { - GraphicsDevice.Clear(BackgroundColor); + GraphicsDevice.Clear(ConfigManager.Config.BackgroundColors.WorldViewer); SetRasterizerState(false); diff --git a/ACViewer/TextureViewer.cs b/ACViewer/TextureViewer.cs index 49ba3d8..bdae5f6 100644 --- a/ACViewer/TextureViewer.cs +++ b/ACViewer/TextureViewer.cs @@ -6,6 +6,7 @@ using MonoGame.Framework.WpfInterop.Input; +using ACViewer.Config; using ACViewer.Render; namespace ACViewer @@ -86,7 +87,9 @@ public void Update(GameTime gameTime) if (mouseState.Position != PrevMouseState.Position) OnMouseMove(mouseState); - if (mouseState.LeftButton == ButtonState.Pressed || mouseState.RightButton == ButtonState.Pressed) + // PrevMouseState check prevents image from jumping around drastically if window is unfocused, and then refocused with a click + if (mouseState.LeftButton == ButtonState.Pressed && PrevMouseState.LeftButton == ButtonState.Pressed || + mouseState.RightButton == ButtonState.Pressed && PrevMouseState.RightButton == ButtonState.Pressed) { var delta = PrevMouseState.Position - mouseState.Position; Pos -= new Vector2(delta.X, delta.Y); @@ -161,7 +164,7 @@ public void Draw(GameTime time) { if (Texture == null) return; - GraphicsDevice.Clear(Color.Black); + GraphicsDevice.Clear(ConfigManager.Config.BackgroundColors.TextureViewer); var samplerState = FileID >> 24 == 0x04 || FileID >> 24 == 0x0F ? SamplerState.PointClamp : SamplerState.LinearClamp; diff --git a/ACViewer/View/Options.xaml b/ACViewer/View/Options.xaml index ed5b595..c0f100e 100644 --- a/ACViewer/View/Options.xaml +++ b/ACViewer/View/Options.xaml @@ -31,5 +31,50 @@ +