Skip to content

Commit

Permalink
Improved rotation controls
Browse files Browse the repository at this point in the history
- Adds support for rotation shortcuts (#17)
  - CTRL + ArrowKeys rotate around X and Y axis
- Adds option to rotate less than 90° (#10)
  - ALT + Button click rotates for 10°
  - CTRL + ALT + ArrowKey does the same
  • Loading branch information
rubenwe committed Apr 20, 2020
1 parent 5408a0c commit eba299a
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 31 deletions.
1 change: 1 addition & 0 deletions Assets/Scripts/Services/ILibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ internal interface ILibrary : ITagIndex
IEnumerable<ItemPreviewModel> GetAllItems();
Task<Mesh> GetMeshAsync(ItemPreviewModel model);
bool TryGetLocalPath(ItemPreviewModel model, out string localPath);
Vector3 GetImportRotation(ItemPreviewModel previewModel);
}
}
12 changes: 12 additions & 0 deletions Assets/Scripts/Services/Library.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ public bool TryGetLocalPath(ItemPreviewModel previewModel, out string localPath)
return false;
}

public Vector3 GetImportRotation(ItemPreviewModel previewModel)
{
var rotation = Vector3.zero;
ReadLocked(() =>
{
var (source, _) = _previewModels.TryGetFileSource(previewModel);
rotation = source.Config.Rotation ?? Vector3.zero;
});

return rotation;
}

private enum TagAction
{
Add,
Expand Down
20 changes: 11 additions & 9 deletions Assets/Scripts/Util/Commands/DelegateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void OnCanExecuteChanged()
}
}

public class DelegateCommand<T> : ICommand, ICanExecuteChange
public class DelegateCommand<T> : ICommand<T>, ICanExecuteChange
{
public event EventHandler CanExecuteChanged;

Expand All @@ -56,21 +56,23 @@ public DelegateCommand(Func<T, bool> canExecuteFunc, Action<T> executeAction)
_canExecuteFunc = canExecuteFunc;
}

public bool CanExecute(object parameter)
public bool CanExecute(object parameter) => CanExecute((T) parameter);
public void Execute(object parameter) => Execute((T) parameter);

public void OnCanExecuteChanged()
{
return _canExecuteFunc?.Invoke((T) parameter) ?? true;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

public void Execute(object parameter)
public bool CanExecute(T parameter)
{
Debug.Assert(CanExecute(parameter));

_executeAction((T) parameter);
return _canExecuteFunc?.Invoke(parameter) ?? true;
}

public void OnCanExecuteChanged()
public void Execute(T parameter)
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
Debug.Assert(CanExecute(parameter));
_executeAction(parameter);
}
}
}
10 changes: 10 additions & 0 deletions Assets/Scripts/Util/Commands/ICommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Windows.Input;

namespace StlVault.Util.Commands
{
public interface ICommand<in T> : ICommand
{
bool CanExecute(T param);
void Execute(T param);
}
}
3 changes: 3 additions & 0 deletions Assets/Scripts/Util/Commands/ICommand.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 62 additions & 15 deletions Assets/Scripts/ViewModels/RotateModel.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using System;
using System.Linq;
using System.Windows.Input;
using System.Threading.Tasks;
using System.Windows.Input;
using JetBrains.Annotations;
using StlVault.Services;
using StlVault.Util;
using StlVault.Util.Commands;
using UnityEngine;
using static StlVault.ViewModels.RotationDirection;

namespace StlVault.ViewModels
namespace StlVault.ViewModels
{
internal class RotateModel
{
Expand All @@ -16,7 +18,8 @@ internal class RotateModel
private readonly ILibrary _library;
private SelectionMode Mode => _detailMenu.Mode;

public ICommand RotateCommand { get; }
public ICommand<Rotation> RotateCommand { get; }
public ICommand ResetRotationCommand { get; }

public RotateModel([NotNull] DetailMenuModel detailMenu, [NotNull] ILibrary library)
{
Expand All @@ -26,21 +29,51 @@ public RotateModel([NotNull] DetailMenuModel detailMenu, [NotNull] ILibrary libr
RotateCommand = new DelegateCommand<Rotation>(CanRotate, Rotate)
.UpdateOn(_detailMenu.AnythingSelected)
.UpdateOn(_running);

ResetRotationCommand = new DelegateCommand(() => CanRotate(null), ResetRotation)
.UpdateOn(_detailMenu.AnythingSelected)
.UpdateOn(_running);
}

private async void ResetRotation()
{
if (!CanRotate(null)) return;
_running.Value = true;

async Task ResetModel(ItemPreviewModel itemPreviewModel)
{
var rotation = _library.GetImportRotation(itemPreviewModel);
await _library.RotateAsync(itemPreviewModel, rotation);
}

if (Mode == SelectionMode.Current)
{
await ResetModel(_detailMenu.Current.Value);
}
else if (Mode == SelectionMode.Selection)
{
foreach (var previewInfo in _detailMenu.Selection.ToList())
{
await ResetModel(previewInfo);
}
}

_running.Value = false;
}

private bool CanRotate(Rotation rot) => _detailMenu.AnythingSelected.Value && !_running;

private async void Rotate(Rotation rotation)
private async void Rotate(Rotation rotationDirection)
{
if (!CanRotate(rotation)) return;
if (!CanRotate(rotationDirection)) return;
_running.Value = true;

switch (Mode)
{
case SelectionMode.Current:
{
var previewInfo = _detailMenu.Current.Value;
var newRotation = GetRotation(previewInfo.GeometryInfo.Value.Rotation, rotation);
var newRotation = GetRotation(previewInfo.GeometryInfo.Value.Rotation, rotationDirection);
await _library.RotateAsync(previewInfo, newRotation);

break;
Expand All @@ -49,7 +82,7 @@ private async void Rotate(Rotation rotation)
{
foreach (var previewInfo in _detailMenu.Selection.ToList())
{
var newRotation = GetRotation(previewInfo.GeometryInfo.Value.Rotation, rotation);
var newRotation = GetRotation(previewInfo.GeometryInfo.Value.Rotation, rotationDirection);
await _library.RotateAsync(previewInfo, newRotation);
}

Expand All @@ -63,21 +96,23 @@ private async void Rotate(Rotation rotation)
private static Vector3 GetRotation(Vector3 current, Rotation rotation)
{
var cur = Quaternion.Euler(current);
switch (rotation)
var amount = rotation.Kind == RotationKind.Big ? 90 : 10;

switch (rotation.Direction)
{
case Rotation.XClockwise: return (Quaternion.AngleAxis(+90, Vector3.right) * cur).eulerAngles;
case Rotation.XCounterClockwise: return (Quaternion.AngleAxis(-90, Vector3.right) * cur).eulerAngles;
case Rotation.YClockwise: return (Quaternion.AngleAxis(+90, Vector3.up) * cur).eulerAngles;
case Rotation.YCounterClockwise: return (Quaternion.AngleAxis(-90, Vector3.up) * cur).eulerAngles;
case Rotation.ZClockwise: return (Quaternion.AngleAxis(+90, Vector3.forward) * cur).eulerAngles;
case Rotation.ZCounterClockwise: return (Quaternion.AngleAxis(-90, Vector3.forward) * cur).eulerAngles;
case XClockwise: return (Quaternion.AngleAxis(+amount, Vector3.right) * cur).eulerAngles;
case XCounterClockwise: return (Quaternion.AngleAxis(-amount, Vector3.right) * cur).eulerAngles;
case YClockwise: return (Quaternion.AngleAxis(+amount, Vector3.up) * cur).eulerAngles;
case YCounterClockwise: return (Quaternion.AngleAxis(-amount, Vector3.up) * cur).eulerAngles;
case ZClockwise: return (Quaternion.AngleAxis(+amount, Vector3.forward) * cur).eulerAngles;
case ZCounterClockwise: return (Quaternion.AngleAxis(-amount, Vector3.forward) * cur).eulerAngles;
}

return current;
}
}

internal enum Rotation
internal enum RotationDirection
{
XClockwise,
XCounterClockwise,
Expand All @@ -86,4 +121,16 @@ internal enum Rotation
ZClockwise,
ZCounterClockwise
}

internal class Rotation
{
public RotationDirection Direction { get; set; }
public RotationKind Kind { get; set; }
}

internal enum RotationKind
{
Big,
Small
}
}
68 changes: 61 additions & 7 deletions Assets/Scripts/Views/RotatePanel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using StlVault.Util.Unity;
using StlVault.Util.Commands;
using StlVault.Util.Unity;
using StlVault.ViewModels;
using UnityEngine;
using static StlVault.ViewModels.RotationDirection;

#pragma warning disable 0649

Expand All @@ -17,12 +19,64 @@ internal class RotatePanel : ViewBase<RotateModel>

protected override void OnViewModelBound()
{
_xClockwise.BindTo(ViewModel.RotateCommand, Rotation.XClockwise);
_xCounterClockwise.BindTo(ViewModel.RotateCommand, Rotation.XCounterClockwise);
_zClockwise.BindTo(ViewModel.RotateCommand, Rotation.ZClockwise);
_zCounterClockwise.BindTo(ViewModel.RotateCommand, Rotation.ZCounterClockwise);
_yClockwise.BindTo(ViewModel.RotateCommand, Rotation.YClockwise);
_yCounterClockwise.BindTo(ViewModel.RotateCommand, Rotation.YCounterClockwise);
_xClockwise.BindTo(ViewModel.RotateCommand, () => new Rotation { Kind = GetKind(), Direction = XClockwise });
_xCounterClockwise.BindTo(ViewModel.RotateCommand, () => new Rotation { Kind = GetKind(), Direction = XCounterClockwise});
_zClockwise.BindTo(ViewModel.RotateCommand, () => new Rotation { Kind = GetKind(), Direction = ZClockwise});
_zCounterClockwise.BindTo(ViewModel.RotateCommand, () => new Rotation { Kind = GetKind(), Direction = ZCounterClockwise});
_yClockwise.BindTo(ViewModel.RotateCommand, () => new Rotation { Kind = GetKind(), Direction = YClockwise});
_yCounterClockwise.BindTo(ViewModel.RotateCommand, () => new Rotation { Kind = GetKind(), Direction = YCounterClockwise});
}

private static RotationKind GetKind()
{
return Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)
? RotationKind.Small
: RotationKind.Big;
}

private void Update()
{
if (!gameObject.activeInHierarchy) return;

var ctrl = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
if (!ctrl) return;

if (HandleResetRotation()) return;
if (HandleRotation()) return;
}

private bool HandleRotation()
{
RotationDirection? rot = null;
if (Input.GetKeyDown(KeyCode.LeftArrow)) rot = YClockwise;
if (Input.GetKeyDown(KeyCode.RightArrow)) rot = YCounterClockwise;
if (Input.GetKeyDown(KeyCode.UpArrow)) rot = XClockwise;
if (Input.GetKeyDown(KeyCode.DownArrow)) rot = XCounterClockwise;

if (!rot.HasValue) return true;

var rotation = new Rotation {Kind = GetKind(), Direction = rot.Value};
if (ViewModel.RotateCommand.CanExecute(rotation))
{
ViewModel.RotateCommand.Execute(rotation);
return true;
}

return false;
}

private bool HandleResetRotation()
{
if (Input.GetKeyDown(KeyCode.Alpha0) || Input.GetKeyDown(KeyCode.Keypad0))
{
if (ViewModel.ResetRotationCommand.CanExecute())
{
ViewModel.ResetRotationCommand.Execute();
return true;
}
}

return false;
}
}
}
11 changes: 11 additions & 0 deletions Assets/Scripts/Views/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ public static void BindTo<T>(this SimpleButton button, ICommand command, T param
button.Clicked += OnButtonOnClicked;
}

public static void BindTo<T>(this SimpleButton button, ICommand command, Func<T> param)
{
void OnChange(object sender, EventArgs args) => button.Enabled.Value = command.CanExecute(param());
void OnButtonOnClicked() => command.Execute(param());

command.CanExecuteChanged += OnChange;
button.Enabled.Value = command.CanExecute(param());

button.Clicked += OnButtonOnClicked;
}

public static void BindTo(this SimpleButton button, ICommand command)
{
void OnChange(object sender, EventArgs args) => button.Enabled.Value = command.CanExecute();
Expand Down

0 comments on commit eba299a

Please sign in to comment.