using HavenSoft.HexManiac.Core.Models;
using HavenSoft.HexManiac.Core.ViewModels.Images;
using IronPython.Modules;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;
namespace HavenSoft.HexManiac.Core.ViewModels.Map {
///
/// Represents the entire map editor tab, with all visible controls, maps, edit boxes, etc
///
public class MapEditorViewModel : ViewModelCore, ITabContent {
private readonly IEditableViewPort viewPort;
private readonly IDataModel model;
private readonly ChangeHistory history;
private readonly Singletons singletons;
private BlockMapViewModel primaryMap;
public BlockMapViewModel PrimaryMap {
get => primaryMap;
set {
if (primaryMap == value) return;
primaryMap = value;
NotifyPropertyChanged();
}
}
public ObservableCollection VisibleMaps { get; } = new();
public ObservableCollection MapButtons { get; } = new();
#region Block Picker
public IPixelViewModel Blocks => primaryMap?.BlockPixels;
private int drawBlockIndex = -10;
public int DrawBlockIndex {
get => drawBlockIndex;
set {
Set(ref drawBlockIndex, value, old => {
NotifyPropertyChanged(nameof(HighlightBlockX));
NotifyPropertyChanged(nameof(HighlightBlockY));
NotifyPropertyChanged(nameof(HighlightBlockSize));
});
}
}
private int collisionIndex;
public int CollisionIndex { get => collisionIndex; set => Set(ref collisionIndex, value); }
public ObservableCollection CollisionOptions { get; } = new();
public double HighlightBlockX => (drawBlockIndex % BlockMapViewModel.BlocksPerRow) * Blocks.SpriteScale * 16;
public double HighlightBlockY => (drawBlockIndex / BlockMapViewModel.BlocksPerRow) * Blocks.SpriteScale * 16;
public double HighlightBlockSize => 16 * Blocks.SpriteScale + 2;
#endregion
#region ITabContent
private StubCommand close, undo, redo;
public string Name => (primaryMap?.Name ?? "Map") + (history.HasDataChange ? "*" : string.Empty);
public string FullFileName => viewPort.FullFileName;
public bool IsMetadataOnlyChange => false;
public ICommand Save => viewPort.Save;
public ICommand SaveAs => viewPort.SaveAs;
public ICommand ExportBackup => null;
public ICommand Undo => StubCommand(ref undo,
() => {
history.Undo.Execute();
Refresh();
},
() => history.Undo.CanExecute(default));
public ICommand Redo => StubCommand(ref redo, () => history.Redo.Execute(), () => history.Redo.CanExecute(default));
public ICommand Copy => null;
public ICommand DeepCopy => null;
public ICommand Clear => null;
public ICommand SelectAll => null;
public ICommand Goto => null;
public ICommand ResetAlignment => null;
public ICommand Back => null;
public ICommand Forward => null;
public ICommand Close => StubCommand(ref close, () => Closed.Raise(this));
public ICommand Diff => null;
public ICommand DiffLeft => null;
public ICommand DiffRight => null;
public bool CanDuplicate => false;
public event EventHandler OnError;
public event EventHandler OnMessage;
public event EventHandler ClearMessage;
public event EventHandler Closed;
public event EventHandler RequestTabChange;
public event EventHandler RequestDelayedWork;
public event EventHandler RequestMenuClose;
public event EventHandler RequestDiff;
public event EventHandler RequestCanDiff;
public event EventHandler RequestCanCreatePatch;
public event EventHandler RequestCreatePatch;
public void Duplicate() { }
public void Refresh() {
VisibleMaps.Clear();
primaryMap.ClearCaches();
UpdatePrimaryMap(primaryMap);
}
public bool TryImport(LoadedFile file, IFileSystem fileSystem) => false;
#endregion
public MapEditorViewModel(IFileSystem fileSystem, IEditableViewPort viewPort, Singletons singletons) {
(this.viewPort, this.model, this.history, this.singletons) = (viewPort, viewPort.Model, viewPort.ChangeHistory, singletons);
history.Undo.CanExecuteChanged += (sender, e) => undo.RaiseCanExecuteChanged();
history.Redo.CanExecuteChanged += (sender, e) => redo.RaiseCanExecuteChanged();
history.Bind(nameof(history.HasDataChange), (sender, e) => NotifyPropertyChanged(nameof(Name)));
var map = new BlockMapViewModel(fileSystem, model, () => history.CurrentChange, 3, 19) { IncludeBorders = true, SpriteScale = .5 };
UpdatePrimaryMap(map);
for (int i = 0; i < 0x40; i++) CollisionOptions.Add(i.ToString("X2"));
}
private void UpdatePrimaryMap(BlockMapViewModel map) {
// update the primary map
if (primaryMap != map) {
if (primaryMap != null) {
primaryMap.NeighborsChanged -= PrimaryMapNeighborsChanged;
}
PrimaryMap = map;
if (primaryMap != null) {
primaryMap.NeighborsChanged += PrimaryMapNeighborsChanged;
}
NotifyPropertyChanged(nameof(Blocks));
NotifyPropertyChanged(nameof(Name));
}
// update the neighbor maps
var oldMaps = VisibleMaps.ToList();
var newMaps = GetMapNeighbors(map, map.SpriteScale < .5 ? 2 : 1).ToList();
newMaps.Add(map);
var mapDict = new Dictionary();
newMaps.ForEach(m => mapDict[m.MapID] = m);
newMaps = mapDict.Values.ToList();
foreach (var oldM in oldMaps) if (!newMaps.Any(newM => oldM.MapID == newM.MapID)) VisibleMaps.Remove(oldM);
foreach (var newM in newMaps) {
bool match = false;
foreach (var existingM in VisibleMaps) {
if (existingM.MapID == newM.MapID) {
match = true;
existingM.IncludeBorders = newM.IncludeBorders;
existingM.SpriteScale = newM.SpriteScale;
existingM.LeftEdge = newM.LeftEdge;
existingM.TopEdge = newM.TopEdge;
break;
}
}
if (!match) VisibleMaps.Add(newM);
}
// refresh connection buttons
var newButtons = primaryMap.GetMapSliders().ToList();
for (int i = 0; i < MapButtons.Count && i < newButtons.Count; i++) {
if (!MapButtons[i].TryUpdate(newButtons[i])) MapButtons[i] = newButtons[i];
}
for (int i = MapButtons.Count; i < newButtons.Count; i++) MapButtons.Add(newButtons[i]);
while (MapButtons.Count > newButtons.Count) MapButtons.RemoveAt(MapButtons.Count - 1);
}
private void PrimaryMapNeighborsChanged(object? sender, EventArgs e) {
UpdatePrimaryMap(primaryMap);
}
private IEnumerable GetMapNeighbors(BlockMapViewModel map, int recursionLevel) {
if (recursionLevel < 1) yield break;
var directions = new List {
MapDirection.Up, MapDirection.Down, MapDirection.Left, MapDirection.Right
};
var newMaps = new List(directions.SelectMany(map.GetNeighbors));
foreach (var m in newMaps) {
yield return m;
if (recursionLevel > 1) {
foreach (var mm in GetMapNeighbors(m, recursionLevel - 1)) yield return mm;
}
}
}
#region Map Interaction
private double cursorX, cursorY, deltaX, deltaY;
public event EventHandler AutoscrollBlocks;
private double highlightCursorX, highlightCursorY, highlightCursorSize;
public double HighlightCursorX { get => highlightCursorX; set => Set(ref highlightCursorX, value); }
public double HighlightCursorY { get => highlightCursorY; set => Set(ref highlightCursorY, value); }
public double HighlightCursorSize { get => highlightCursorSize; set => Set(ref highlightCursorSize, value); }
public void Hover(double x, double y) {
var map = MapUnderCursor(x, y);
if (map == null) return;
var dx = (int)((x - map.LeftEdge) / 16 / map.SpriteScale) + .5;
var dy = (int)((y - map.TopEdge) / 16 / map.SpriteScale) + .5;
(HighlightCursorX, HighlightCursorY) = (map.LeftEdge + dx * 16 * map.SpriteScale, map.TopEdge + dy * 16 * map.SpriteScale);
HighlightCursorSize = 16 * map.SpriteScale + 4;
}
public void DragDown(double x, double y) {
(cursorX, cursorY) = (x, y);
(deltaX, deltaY) = (0, 0);
var map = MapUnderCursor(x, y);
if (map != null) UpdatePrimaryMap(map);
}
public void DragMove(double x, double y) {
deltaX += x - cursorX;
deltaY += y - cursorY;
var (intX, intY) = ((int)deltaX, (int)deltaY);
deltaX -= intX;
deltaY -= intY;
(cursorX, cursorY) = (x, y);
Pan(intX, intY);
Hover(x, y);
}
public void DragUp(double x, double y) { }
#region Primary Interaction (left-click)
private PrimaryInteractionType interactionType;
public void PrimaryDown(double x, double y) {
var map = MapUnderCursor(x, y);
if (map == null) {
interactionType = PrimaryInteractionType.None;
return;
}
var ev = map.EventUnderCursor(x, y);
if (ev != null) {
EventDown(x, y, ev);
return;
}
DrawDown(x, y);
}
public void PrimaryMove(double x, double y) {
if (interactionType == PrimaryInteractionType.Draw) DrawMove(x, y);
if (interactionType == PrimaryInteractionType.Event) EventMove(x, y);
}
public void PrimaryUp(double x, double y) {
if (interactionType == PrimaryInteractionType.Draw) DrawUp(x, y);
if (interactionType == PrimaryInteractionType.Event) EventUp(x, y);
}
private void DrawDown(double x, double y) {
interactionType = PrimaryInteractionType.Draw;
DrawMove(x, y);
}
private void DrawMove(double x, double y) {
var map = MapUnderCursor(x, y);
if (map != null) map.DrawBlock(history.CurrentChange, drawBlockIndex, collisionIndex, x, y);
Hover(x, y);
}
private void DrawUp(double x, double y) => history.ChangeCompleted();
private ObjectEventModel interactionEvent;
private void EventDown(double x, double y, ObjectEventModel ev) {
interactionType = PrimaryInteractionType.Event;
interactionEvent = ev;
}
private void EventMove(double x,double y) {
var map = MapUnderCursor(x, y);
if (map != null) map.UpdateEventLocation(interactionEvent, x, y);
Hover(x, y);
}
private void EventUp(double x, double y) => history.ChangeCompleted();
#endregion
public void SelectDown(double x, double y) {
var map = MapUnderCursor(x, y);
if (map == null) return;
var (blockIndex, collisionIndex) = map.GetBlock(x, y);
if (blockIndex >= 0) {
DrawBlockIndex = blockIndex;
AutoscrollBlocks.Raise(this);
}
if (collisionIndex >= 0) CollisionIndex = collisionIndex;
}
public void SelectMove(double x, double y) { }
public void SelectUp(double x, double y) { }
public void Zoom(double x, double y, bool enlarge) {
var map = MapUnderCursor(x, y);
if (map == null) map = primaryMap;
map.Scale(x, y, enlarge);
map.IncludeBorders = map.SpriteScale <= 1;
UpdatePrimaryMap(map);
}
public void SelectBlock(int x, int y) {
DrawBlockIndex = y * BlockMapViewModel.BlocksPerRow + x;
}
private StubCommand panCommand, zoomCommand;
public ICommand PanCommand => StubCommand(ref panCommand, Pan);
public ICommand ZoomCommand => StubCommand(ref zoomCommand, Zoom);
public void Pan(MapDirection direction) {
int intX = 0, intY = 0;
if (direction == MapDirection.Left) intX = -1;
if (direction == MapDirection.Right) intX = 1;
if (direction == MapDirection.Up) intY = -1;
if (direction == MapDirection.Down) intY = 1;
Pan(intX * -32, intY * -32);
var map = MapUnderCursor(0, 0);
if (map != null) UpdatePrimaryMap(map);
}
private void Pan(int intX, int intY) {
foreach (var map in VisibleMaps) {
map.LeftEdge += intX;
map.TopEdge += intY;
}
foreach (var button in MapButtons) {
button.AnchorPositionX += button.AnchorLeftEdge ? intX : -intX;
button.AnchorPositionY += button.AnchorTopEdge ? intY : -intY;
}
}
public void Zoom(ZoomDirection direction) => Zoom(0, 0, direction == ZoomDirection.Enlarge);
private BlockMapViewModel MapUnderCursor(double x, double y) {
foreach (var map in VisibleMaps) {
if (map.LeftEdge < x && x < map.LeftEdge + map.PixelWidth * map.SpriteScale) {
if (map.TopEdge < y && y < map.TopEdge + map.PixelHeight * map.SpriteScale) {
return map;
}
}
}
return null;
}
#endregion
#region ShiftInteraction
private MapSlider shiftButton;
public void ShiftDown(double x, double y) {
(cursorX, cursorY) = (x, y);
(deltaX, deltaY) = (0, 0);
shiftButton = ButtonUnderCursor(x, y);
HighlightCursorSize = 0;
}
public void ShiftMove(double x, double y) {
if (shiftButton == null) return;
deltaX += x - cursorX;
deltaY += y - cursorY;
var blockSize = (int)(16 * primaryMap.SpriteScale);
var (intX, intY) = ((int)deltaX / blockSize, (int)deltaY / blockSize);
deltaX -= intX * blockSize;
deltaY -= intY * blockSize;
(cursorX, cursorY) = (x, y);
shiftButton.Move(intX, intY);
}
public void ShiftUp(double x, double y) => history.ChangeCompleted();
private MapSlider ButtonUnderCursor(double x, double y) {
int left, right, top, bottom;
foreach (var button in MapButtons) {
if (button.AnchorLeftEdge) {
left = button.AnchorPositionX;
right = left + MapSlider.SliderSize;
} else {
right = -button.AnchorPositionX;
left = right - MapSlider.SliderSize;
}
if (button.AnchorTopEdge) {
top = button.AnchorPositionY;
bottom = top + MapSlider.SliderSize;
} else {
bottom = -button.AnchorPositionY;
top = bottom - MapSlider.SliderSize;
}
if (left <= x && x < right && top <= y && y < bottom) return button;
}
return null;
}
#endregion
}
public enum PrimaryInteractionType { None, Draw, Event }
}