public override XmlElement VToXML()
        {
            var doc = new XmlDocument();
            var rigidBodyElement = doc.CreateElement(GetComponentName(this));

            var materialElement = doc.CreateElement("Material");

            materialElement.SetAttribute("material", m_sMaterial);
            rigidBodyElement.AppendChild(materialElement);

            var physicsElement = doc.CreateElement("Physics");

            physicsElement.SetAttribute("fixedRotation", m_bFixedRotation.ToString(CultureInfo.InvariantCulture));
            physicsElement.SetAttribute("gravityScale", m_fGravityScale.ToString(CultureInfo.InvariantCulture));
            physicsElement.SetAttribute("maxVelocity", MaxVelocity.ToString(CultureInfo.InvariantCulture));
            physicsElement.SetAttribute("maxAngVelocity", MaxAngularVelocity.ToString(CultureInfo.InvariantCulture));
            rigidBodyElement.AppendChild(physicsElement);

            var bodyElement = doc.CreateElement("Body");

            bodyElement.SetAttribute("linearDamping", m_fLinearDamping.ToString(CultureInfo.InvariantCulture));
            bodyElement.SetAttribute("angularDamping", m_fAngularDamping.ToString(CultureInfo.InvariantCulture));
            bodyElement.SetAttribute("followEntityRotation", UseEntityRotation.ToString(CultureInfo.InvariantCulture));

            var bodyTypeStr = "";

            switch (m_RigidBodyType)
            {
            case Cv_BodyType.Static:
                bodyTypeStr = "static";
                break;

            case Cv_BodyType.Dynamic:
                bodyTypeStr = "dynamic";
                break;

            default:
                bodyTypeStr = "kinematic";
                break;
            }

            bodyElement.SetAttribute("type", bodyTypeStr);
            rigidBodyElement.AppendChild(bodyElement);

            var collisionShapesElement = doc.CreateElement("CollisionShapes");

            foreach (var shape in m_Shapes)
            {
                var shapeTypeStr = "";

                switch (shape.Type)
                {
                case ShapeType.Box:
                    shapeTypeStr = "Box";
                    break;

                case ShapeType.Circle:
                    shapeTypeStr = "Circle";
                    break;

                case ShapeType.Polygon:
                    shapeTypeStr = "Polygon";
                    break;

                default:
                    shapeTypeStr = "Trigger";
                    break;
                }

                var shapeElement = doc.CreateElement(shapeTypeStr);
                shapeElement.SetAttribute("id", shape.ShapeID);
                shapeElement.SetAttribute("material", shape.Material);
                shapeElement.SetAttribute("anchorX", ((int)shape.Anchor.X).ToString(CultureInfo.InvariantCulture));
                shapeElement.SetAttribute("anchorY", ((int)shape.Anchor.Y).ToString(CultureInfo.InvariantCulture));
                shapeElement.SetAttribute("isBullet", shape.IsBullet.ToString(CultureInfo.InvariantCulture));

                if (shape.Type == ShapeType.Circle)
                {
                    shapeElement.SetAttribute("radius", shape.Radius.ToString(CultureInfo.InvariantCulture));
                }
                else if (shape.Type == ShapeType.Box)
                {
                    shapeElement.SetAttribute("dimensionsX", ((int)shape.Dimensions.X).ToString(CultureInfo.InvariantCulture));
                    shapeElement.SetAttribute("dimensionsY", ((int)shape.Dimensions.Y).ToString(CultureInfo.InvariantCulture));
                }
                else if (shape.Type == ShapeType.Trigger)
                {
                    shapeElement.SetAttribute("dimensions", ((int)shape.Dimensions.X).ToString(CultureInfo.InvariantCulture));
                }
                else
                {
                    foreach (var point in shape.Points)
                    {
                        var pointElement = doc.CreateElement("Point");
                        pointElement.SetAttribute("x", ((int)point.X).ToString(CultureInfo.InvariantCulture));
                        pointElement.SetAttribute("y", ((int)point.Y).ToString(CultureInfo.InvariantCulture));
                        shapeElement.AppendChild(pointElement);
                    }
                }

                var collisionCategories = shape.Categories.GetCategoriesArray();
                foreach (var category in collisionCategories)
                {
                    var collisionCategoriesElement = doc.CreateElement("CollisionCategory");
                    collisionCategoriesElement.SetAttribute("id", category.ToString(CultureInfo.InvariantCulture));
                    shapeElement.AppendChild(collisionCategoriesElement);
                }

                var collidesWith = shape.CollidesWith.GetCategoriesArray();
                foreach (var category in collidesWith)
                {
                    var collidesWithElement = doc.CreateElement("CollidesWith");
                    collidesWithElement.SetAttribute("id", category.ToString(CultureInfo.InvariantCulture));
                    collidesWithElement.SetAttribute("directions", shape.CollisionDirections[category]);
                    shapeElement.AppendChild(collidesWithElement);
                }

                collisionShapesElement.AppendChild(shapeElement);
            }

            rigidBodyElement.AppendChild(collisionShapesElement);

            return(rigidBodyElement);
        }
        internal AccelerationViewModel(Acceleration acceleration, Train train, MotorCar car)
        {
            CultureInfo culture = CultureInfo.InvariantCulture;

            Entries = acceleration.Entries
                      .ToReadOnlyReactiveCollection(x => new EntryViewModel(x))
                      .AddTo(disposable);

            SelectedEntryIndex = acceleration
                                 .ToReactivePropertyAsSynchronized(
                x => x.SelectedEntryIndex,
                ignoreValidationErrorValue: true
                )
                                 .SetValidateNotifyError(x => x < 0 ? string.Empty : null)
                                 .AddTo(disposable);

            SelectedEntry = SelectedEntryIndex
                            .Select(x => x < 0 ? null : Entries[x])
                            .ToReadOnlyReactivePropertySlim()
                            .AddTo(disposable);

            MinVelocity = acceleration
                          .ToReactivePropertyAsSynchronized(
                x => x.MinVelocity,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                          .AddTo(disposable);

            MaxVelocity = acceleration
                          .ToReactivePropertyAsSynchronized(
                x => x.MaxVelocity,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                          .AddTo(disposable);

            MinAcceleration = acceleration
                              .ToReactivePropertyAsSynchronized(
                x => x.MinAcceleration,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                              .AddTo(disposable);

            MaxAcceleration = acceleration
                              .ToReactivePropertyAsSynchronized(
                x => x.MaxAcceleration,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                              .AddTo(disposable);

            NowVelocity = acceleration
                          .ObserveProperty(x => x.NowVelocity)
                          .Select(x => $"{x} km/h")
                          .ToReadOnlyReactivePropertySlim()
                          .AddTo(disposable);

            NowAcceleration = acceleration
                              .ObserveProperty(x => x.NowAcceleration)
                              .Select(x => $"{x} km/h/s")
                              .ToReadOnlyReactivePropertySlim()
                              .AddTo(disposable);

            Resistance = acceleration
                         .ToReactivePropertyAsSynchronized(x => x.Resistance)
                         .AddTo(disposable);

            ImageWidth = acceleration
                         .ToReactivePropertyAsSynchronized(
                x => x.ImageWidth,
                ignoreValidationErrorValue: true
                )
                         .SetValidateNotifyError(x => x <= 0 ? string.Empty : null)
                         .AddTo(disposable);

            ImageHeight = acceleration
                          .ToReactivePropertyAsSynchronized(
                x => x.ImageHeight,
                ignoreValidationErrorValue: true
                )
                          .SetValidateNotifyError(x => x <= 0 ? string.Empty : null)
                          .AddTo(disposable);

            Image = acceleration
                    .ObserveProperty(x => x.Image)
                    .ToReadOnlyReactivePropertySlim()
                    .AddTo(disposable);

            new[]
            {
                acceleration
                .PropertyChangedAsObservable()
                .Where(x => x.PropertyName != nameof(acceleration.NowVelocity) &&
                       x.PropertyName != nameof(acceleration.NowAcceleration) &&
                       x.PropertyName != nameof(acceleration.Image)
                       )
                .OfType <object>(),
                acceleration.Entries.ObserveElementPropertyChanged().OfType <object>()
            }
            .Merge()
            .ToReadOnlyReactivePropertySlim()
            .Subscribe(_ => train.DrawAccelerationImage(car))
            .AddTo(disposable);

            ZoomIn = new ReactiveCommand();
            ZoomIn.Subscribe(acceleration.ZoomIn).AddTo(disposable);

            ZoomOut = new ReactiveCommand();
            ZoomOut.Subscribe(acceleration.ZoomOut).AddTo(disposable);

            Reset = new ReactiveCommand();
            Reset.Subscribe(acceleration.Reset).AddTo(disposable);

            MoveLeft = new ReactiveCommand();
            MoveLeft.Subscribe(acceleration.MoveLeft).AddTo(disposable);

            MoveRight = new ReactiveCommand();
            MoveRight.Subscribe(acceleration.MoveRight).AddTo(disposable);

            MoveBottom = new ReactiveCommand();
            MoveBottom.Subscribe(acceleration.MoveBottom).AddTo(disposable);

            MoveTop = new ReactiveCommand();
            MoveTop.Subscribe(acceleration.MoveTop).AddTo(disposable);

            MouseMove = new ReactiveCommand <InputEventModel.EventArgs>();
            MouseMove.Subscribe(acceleration.MouseMove).AddTo(disposable);

            MinVelocity
            .SetValidateNotifyError(x =>
            {
                double min;
                string message;

                if (Utilities.TryParse(x, NumberRange.NonNegative, out min, out message))
                {
                    double max;

                    if (Utilities.TryParse(MaxVelocity.Value, NumberRange.NonNegative, out max) && min >= max)
                    {
                        message = "MinはMax未満でなければなりません。";
                    }
                }

                return(message);
            })
            .Subscribe(_ => MaxVelocity.ForceValidate())
            .AddTo(disposable);

            MinVelocity.ObserveHasErrors
            .ToReadOnlyReactivePropertySlim(mode: ReactivePropertyMode.DistinctUntilChanged)
            .Where(x => !x)
            .Subscribe(_ => MinVelocity.ForceNotify())
            .AddTo(disposable);

            MaxVelocity
            .SetValidateNotifyError(x =>
            {
                double max;
                string message;

                if (Utilities.TryParse(x, NumberRange.NonNegative, out max, out message))
                {
                    double min;

                    if (Utilities.TryParse(MinVelocity.Value, NumberRange.NonNegative, out min) && max <= min)
                    {
                        message = "MinはMax未満でなければなりません。";
                    }
                }

                return(message);
            })
            .Subscribe(_ => MinVelocity.ForceValidate())
            .AddTo(disposable);

            MaxVelocity.ObserveHasErrors
            .ToReadOnlyReactivePropertySlim(mode: ReactivePropertyMode.DistinctUntilChanged)
            .Where(x => !x)
            .Subscribe(_ => MaxVelocity.ForceNotify())
            .AddTo(disposable);

            MinAcceleration
            .SetValidateNotifyError(x =>
            {
                double min;
                string message;

                if (Utilities.TryParse(x, NumberRange.NonNegative, out min, out message))
                {
                    double max;

                    if (Utilities.TryParse(MaxAcceleration.Value, NumberRange.NonNegative, out max) && min >= max)
                    {
                        message = "MinはMax未満でなければなりません。";
                    }
                }

                return(message);
            })
            .Subscribe(_ => MaxAcceleration.ForceValidate())
            .AddTo(disposable);

            MinAcceleration.ObserveHasErrors
            .ToReadOnlyReactivePropertySlim(mode: ReactivePropertyMode.DistinctUntilChanged)
            .Where(x => !x)
            .Subscribe(_ => MinAcceleration.ForceNotify())
            .AddTo(disposable);

            MaxAcceleration
            .SetValidateNotifyError(x =>
            {
                double max;
                string message;

                if (Utilities.TryParse(x, NumberRange.NonNegative, out max, out message))
                {
                    double min;

                    if (Utilities.TryParse(MinAcceleration.Value, NumberRange.NonNegative, out min) && max <= min)
                    {
                        message = "MinはMax未満でなければなりません。";
                    }
                }

                return(message);
            })
            .Subscribe(_ => MinAcceleration.ForceValidate())
            .AddTo(disposable);

            MaxAcceleration.ObserveHasErrors
            .ToReadOnlyReactivePropertySlim(mode: ReactivePropertyMode.DistinctUntilChanged)
            .Where(x => !x)
            .Subscribe(_ => MaxAcceleration.ForceNotify())
            .AddTo(disposable);
        }
Beispiel #3
0
        internal MotorViewModel(Motor motor)
        {
            CultureInfo culture = CultureInfo.InvariantCulture;

            MessageBox = motor
                         .ObserveProperty(x => x.MessageBox)
                         .Do(_ => MessageBox?.Value.Dispose())
                         .Select(x => new MessageBoxViewModel(x))
                         .ToReadOnlyReactivePropertySlim()
                         .AddTo(disposable);

            ToolTipVertexPitch = motor
                                 .ObserveProperty(x => x.ToolTipVertexPitch)
                                 .Do(_ => ToolTipVertexPitch?.Value.Dispose())
                                 .Select(x => new ToolTipViewModel(x))
                                 .ToReadOnlyReactivePropertySlim()
                                 .AddTo(disposable);

            ToolTipVertexVolume = motor
                                  .ObserveProperty(x => x.ToolTipVertexVolume)
                                  .Do(_ => ToolTipVertexVolume?.Value.Dispose())
                                  .Select(x => new ToolTipViewModel(x))
                                  .ToReadOnlyReactivePropertySlim()
                                  .AddTo(disposable);

            CurrentModifierKeys = motor
                                  .ToReactivePropertyAsSynchronized(x => x.CurrentModifierKeys)
                                  .AddTo(disposable);

            CurrentCursorType = motor
                                .ObserveProperty(x => x.CurrentCursorType)
                                .ToReadOnlyReactivePropertySlim()
                                .AddTo(disposable);

            MinVelocity = motor
                          .ToReactivePropertyAsSynchronized(
                x => x.MinVelocity,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                          .AddTo(disposable);

            MinVelocity.Subscribe(_ => motor.IsRefreshGlControl = true).AddTo(disposable);

            MaxVelocity = motor
                          .ToReactivePropertyAsSynchronized(
                x => x.MaxVelocity,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                          .AddTo(disposable);

            MaxVelocity.Subscribe(_ => motor.IsRefreshGlControl = true).AddTo(disposable);

            MinPitch = motor
                       .ToReactivePropertyAsSynchronized(
                x => x.MinPitch,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                       .AddTo(disposable);

            MinPitch.Subscribe(_ => motor.IsRefreshGlControl = true).AddTo(disposable);

            MaxPitch = motor
                       .ToReactivePropertyAsSynchronized(
                x => x.MaxPitch,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                       .AddTo(disposable);

            MaxPitch.Subscribe(_ => motor.IsRefreshGlControl = true).AddTo(disposable);

            MinVolume = motor
                        .ToReactivePropertyAsSynchronized(
                x => x.MinVolume,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                        .AddTo(disposable);

            MinVolume.Subscribe(_ => motor.IsRefreshGlControl = true).AddTo(disposable);

            MaxVolume = motor
                        .ToReactivePropertyAsSynchronized(
                x => x.MaxVolume,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                        .AddTo(disposable);

            MaxVolume.Subscribe(_ => motor.IsRefreshGlControl = true).AddTo(disposable);

            NowVelocity = motor
                          .ObserveProperty(x => x.NowVelocity)
                          .ToReadOnlyReactivePropertySlim()
                          .AddTo(disposable);

            NowPitch = motor
                       .ObserveProperty(x => x.NowPitch)
                       .ToReadOnlyReactivePropertySlim()
                       .AddTo(disposable);

            NowVolume = motor
                        .ObserveProperty(x => x.NowVolume)
                        .ToReadOnlyReactivePropertySlim()
                        .AddTo(disposable);

            CurrentSelectedTrack = motor
                                   .ObserveProperty(x => x.SelectedTrackInfo)
                                   .ToReadOnlyReactivePropertySlim()
                                   .AddTo(disposable);

            CurrentSelectedTrack
            .Subscribe(_ =>
            {
                motor.ResetSelect();
                motor.IsRefreshGlControl = true;
            })
            .AddTo(disposable);

            CurrentInputMode = motor
                               .ObserveProperty(x => x.CurrentInputMode)
                               .ToReadOnlyReactivePropertySlim()
                               .AddTo(disposable);

            CurrentInputMode
            .Subscribe(_ =>
            {
                motor.ResetSelect();
                motor.IsRefreshGlControl = true;
            })
            .AddTo(disposable);

            SelectedSoundIndex = motor
                                 .ToReactivePropertyAsSynchronized(x => x.SelectedSoundIndex)
                                 .AddTo(disposable);

            CurrentToolMode = motor
                              .ObserveProperty(x => x.CurrentToolMode)
                              .ToReadOnlyReactivePropertySlim()
                              .AddTo(disposable);

            CurrentToolMode
            .Subscribe(_ =>
            {
                motor.ResetSelect();
                motor.IsRefreshGlControl = true;
            })
            .AddTo(disposable);

            StoppedSim = motor
                         .ObserveProperty(x => x.CurrentSimState)
                         .Select(x => x == Motor.SimulationState.Disable || x == Motor.SimulationState.Stopped)
                         .ToReadOnlyReactivePropertySlim()
                         .AddTo(disposable);

            RunIndex = motor
                       .ToReactivePropertyAsSynchronized(x => x.RunIndex)
                       .AddTo(disposable);

            IsPlayTrack1 = motor
                           .ToReactivePropertyAsSynchronized(x => x.IsPlayTrack1)
                           .AddTo(disposable);

            IsPlayTrack2 = motor
                           .ToReactivePropertyAsSynchronized(x => x.IsPlayTrack2)
                           .AddTo(disposable);

            IsLoop = motor
                     .ToReactivePropertyAsSynchronized(x => x.IsLoop)
                     .AddTo(disposable);

            IsConstant = motor
                         .ToReactivePropertyAsSynchronized(x => x.IsConstant)
                         .AddTo(disposable);

            Acceleration = motor
                           .ToReactivePropertyAsSynchronized(
                x => x.Acceleration,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                           .SetValidateNotifyError(x =>
            {
                double result;
                string message;

                Utilities.TryParse(x, NumberRange.NonNegative, out result, out message);

                return(message);
            })
                           .AddTo(disposable);

            StartSpeed = motor
                         .ToReactivePropertyAsSynchronized(
                x => x.StartSpeed,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                         .SetValidateNotifyError(x =>
            {
                double result;
                string message;

                Utilities.TryParse(x, NumberRange.NonNegative, out result, out message);

                return(message);
            })
                         .AddTo(disposable);

            EndSpeed = motor
                       .ToReactivePropertyAsSynchronized(
                x => x.EndSpeed,
                x => x.ToString(culture),
                x => double.Parse(x, NumberStyles.Float, culture),
                ignoreValidationErrorValue: true
                )
                       .SetValidateNotifyError(x =>
            {
                double result;
                string message;

                Utilities.TryParse(x, NumberRange.NonNegative, out result, out message);

                return(message);
            })
                       .AddTo(disposable);

            EnabledDirect = new[]
            {
                CurrentInputMode.Select(x => x != Motor.InputMode.SoundIndex),
                StoppedSim
            }
            .CombineLatestValuesAreAllTrue()
            .ToReadOnlyReactivePropertySlim()
            .AddTo(disposable);

            DirectX = new ReactiveProperty <string>(0.0.ToString(culture))
                      .SetValidateNotifyError(x =>
            {
                double result;
                string message;

                switch (CurrentToolMode.Value)
                {
                case Motor.ToolMode.Move:
                    Utilities.TryParse(x, NumberRange.Any, out result, out message);
                    break;

                case Motor.ToolMode.Dot:
                    Utilities.TryParse(x, NumberRange.NonNegative, out result, out message);
                    break;

                default:
                    message = null;
                    break;
                }

                return(message);
            })
                      .AddTo(disposable);

            DirectY = new ReactiveProperty <string>(0.0.ToString(culture))
                      .SetValidateNotifyError(x =>
            {
                double result;
                string message;

                switch (CurrentToolMode.Value)
                {
                case Motor.ToolMode.Move:
                    Utilities.TryParse(x, NumberRange.Any, out result, out message);
                    break;

                case Motor.ToolMode.Dot:
                    Utilities.TryParse(x, NumberRange.NonNegative, out result, out message);
                    break;

                default:
                    message = null;
                    break;
                }

                return(message);
            })
                      .AddTo(disposable);

            CurrentToolMode
            .Subscribe(_ =>
            {
                DirectX.ForceValidate();
                DirectY.ForceValidate();
            })
            .AddTo(disposable);

            GlControlWidth = motor
                             .ToReactivePropertyAsSynchronized(
                x => x.GlControlWidth,
                ignoreValidationErrorValue: true
                )
                             .SetValidateNotifyError(x => x <= 0 ? string.Empty : null)
                             .AddTo(disposable);

            GlControlWidth.Subscribe(_ => motor.IsRefreshGlControl = true).AddTo(disposable);

            GlControlHeight = motor
                              .ToReactivePropertyAsSynchronized(
                x => x.GlControlHeight,
                ignoreValidationErrorValue: true
                )
                              .SetValidateNotifyError(x => x <= 0 ? string.Empty : null)
                              .AddTo(disposable);

            GlControlHeight.Subscribe(_ => motor.IsRefreshGlControl = true).AddTo(disposable);

            IsRefreshGlControl = motor
                                 .ToReactivePropertyAsSynchronized(x => x.IsRefreshGlControl)
                                 .AddTo(disposable);

            ChangeSelectedTrack = new ReactiveCommand <Motor.TrackInfo>().WithSubscribe(x => motor.SelectedTrackInfo = x).AddTo(disposable);

            ChangeInputMode = new ReactiveCommand <Motor.InputMode>().WithSubscribe(x => motor.CurrentInputMode = x).AddTo(disposable);

            ChangeToolMode = new[]
            {
                CurrentInputMode.Select(x => x != Motor.InputMode.SoundIndex),
                StoppedSim
            }
            .CombineLatestValuesAreAllTrue()
            .ToReactiveCommand <Motor.ToolMode>()
            .WithSubscribe(x => motor.CurrentToolMode = x)
            .AddTo(disposable);

            ZoomIn = new ReactiveCommand().WithSubscribe(motor.ZoomIn).AddTo(disposable);

            ZoomOut = new ReactiveCommand().WithSubscribe(motor.ZoomOut).AddTo(disposable);

            Reset = new ReactiveCommand().WithSubscribe(motor.Reset).AddTo(disposable);

            MoveLeft = new ReactiveCommand().WithSubscribe(motor.MoveLeft).AddTo(disposable);

            MoveRight = new ReactiveCommand().WithSubscribe(motor.MoveRight).AddTo(disposable);

            MoveBottom = new ReactiveCommand().WithSubscribe(motor.MoveBottom).AddTo(disposable);

            MoveTop = new ReactiveCommand().WithSubscribe(motor.MoveTop).AddTo(disposable);

            Undo = new[]
            {
                new[]
                {
                    motor.PropertyChangedAsObservable().Where(x => x.PropertyName == nameof(motor.SelectedTrackInfo)).OfType <object>(),
                    motor.PrevTrackStates.CollectionChangedAsObservable().OfType <object>()
                }
                .Merge()
                .Select(_ => motor.PrevTrackStates.Any(x => x.Info == motor.SelectedTrackInfo)),
                StoppedSim
            }
            .CombineLatestValuesAreAllTrue()
            .ToReactiveCommand(false)
            .WithSubscribe(motor.Undo)
            .AddTo(disposable);

            Redo = new[]
            {
                new[]
                {
                    motor.PropertyChangedAsObservable().Where(x => x.PropertyName == nameof(motor.SelectedTrackInfo)).OfType <object>(),
                    motor.NextTrackStates.CollectionChangedAsObservable().OfType <object>()
                }
                .Merge()
                .Select(_ => motor.NextTrackStates.Any(x => x.Info == motor.SelectedTrackInfo)),
                StoppedSim
            }
            .CombineLatestValuesAreAllTrue()
            .ToReactiveCommand(false)
            .WithSubscribe(motor.Redo)
            .AddTo(disposable);

            TearingOff = StoppedSim
                         .ToReactiveCommand()
                         .WithSubscribe(motor.TearingOff)
                         .AddTo(disposable);

            Copy = StoppedSim
                   .ToReactiveCommand()
                   .WithSubscribe(motor.Copy)
                   .AddTo(disposable);

            Paste = new[]
            {
                motor.ObserveProperty(x => x.CopyTrack).Select(x => x != null),
                StoppedSim
            }
            .CombineLatestValuesAreAllTrue()
            .ToReactiveCommand()
            .WithSubscribe(motor.Paste)
            .AddTo(disposable);

            Cleanup = StoppedSim
                      .ToReactiveCommand()
                      .WithSubscribe(motor.Cleanup)
                      .AddTo(disposable);

            Delete = StoppedSim
                     .ToReactiveCommand()
                     .WithSubscribe(motor.Delete)
                     .AddTo(disposable);

            MouseDown = new ReactiveCommand <InputEventModel.EventArgs>().WithSubscribe(motor.MouseDown).AddTo(disposable);

            MouseMove = new ReactiveCommand <InputEventModel.EventArgs>().WithSubscribe(motor.MouseMove).AddTo(disposable);

            MouseUp = new ReactiveCommand().WithSubscribe(motor.MouseUp).AddTo(disposable);

            DirectDot = new[]
            {
                StoppedSim,
                CurrentToolMode.Select(x => x == Motor.ToolMode.Dot),
                DirectX.ObserveHasErrors.Select(x => !x),
                DirectY.ObserveHasErrors.Select(x => !x)
            }
            .CombineLatestValuesAreAllTrue()
            .ToReactiveCommand()
            .WithSubscribe(() => motor.DirectDot(double.Parse(DirectX.Value), double.Parse(DirectY.Value)))
            .AddTo(disposable);

            DirectMove = new[]
            {
                StoppedSim,
                CurrentToolMode.Select(x => x == Motor.ToolMode.Move),
                DirectX.ObserveHasErrors.Select(x => !x),
                DirectY.ObserveHasErrors.Select(x => !x)
            }
            .CombineLatestValuesAreAllTrue()
            .ToReactiveCommand()
            .WithSubscribe(() => motor.DirectMove(double.Parse(DirectX.Value), double.Parse(DirectY.Value)))
            .AddTo(disposable);

            SwapSpeed = StoppedSim
                        .ToReactiveCommand()
                        .WithSubscribe(() =>
            {
                string tmp       = StartSpeed.Value;
                StartSpeed.Value = EndSpeed.Value;
                EndSpeed.Value   = tmp;
            })
                        .AddTo(disposable);

            SimulationTimer = new ReactiveTimer(TimeSpan.FromMilliseconds(1000.0 / 30.0));

            SimulationTimer
            .Subscribe(_ =>
            {
                SimulationTimer.Stop();
                Observable.Start(motor.RunSimulation, UIDispatcherScheduler.Default).Wait();
                SimulationTimer.Start(TimeSpan.FromMilliseconds(1000.0 / 30.0));
            })
            .AddTo(disposable);

            motor.ObserveProperty(x => x.CurrentSimState)
            .ToReadOnlyReactivePropertySlim()
            .Subscribe(x =>
            {
                switch (x)
                {
                case Motor.SimulationState.Started:
                    SimulationTimer.Start();
                    break;

                default:
                    SimulationTimer.Stop();
                    break;
                }
            })
            .AddTo(disposable);

            StartSimulation = motor
                              .ObserveProperty(x => x.CurrentSimState)
                              .Select(x => x == Motor.SimulationState.Paused || x == Motor.SimulationState.Stopped)
                              .ToReactiveCommand()
                              .WithSubscribe(motor.StartSimulation)
                              .AddTo(disposable);

            PauseSimulation = motor
                              .ObserveProperty(x => x.CurrentSimState)
                              .Select(x => x == Motor.SimulationState.Started)
                              .ToReactiveCommand()
                              .WithSubscribe(motor.PauseSimulation)
                              .AddTo(disposable);

            StopSimulation = motor
                             .ObserveProperty(x => x.CurrentSimState)
                             .Select(x => x == Motor.SimulationState.Paused || x == Motor.SimulationState.Started)
                             .ToReactiveCommand()
                             .WithSubscribe(motor.StopSimulation)
                             .AddTo(disposable);

            DrawGlControl = new ReactiveCommand()
                            .WithSubscribe(motor.DrawGlControl)
                            .AddTo(disposable);

            MinVelocity
            .SetValidateNotifyError(x =>
            {
                double min;
                string message;

                if (Utilities.TryParse(x, NumberRange.NonNegative, out min, out message))
                {
                    double max;

                    if (Utilities.TryParse(MaxVelocity.Value, NumberRange.NonNegative, out max) && min >= max)
                    {
                        message = "MinはMax未満でなければなりません。";
                    }
                }

                return(message);
            })
            .Subscribe(_ => MaxVelocity.ForceValidate())
            .AddTo(disposable);

            MinVelocity.ObserveHasErrors
            .ToReadOnlyReactivePropertySlim(mode: ReactivePropertyMode.DistinctUntilChanged)
            .Where(x => !x)
            .Subscribe(_ => MinVelocity.ForceNotify())
            .AddTo(disposable);

            MaxVelocity
            .SetValidateNotifyError(x =>
            {
                double max;
                string message;

                if (Utilities.TryParse(x, NumberRange.NonNegative, out max, out message))
                {
                    double min;

                    if (Utilities.TryParse(MinVelocity.Value, NumberRange.NonNegative, out min) && max <= min)
                    {
                        message = "MinはMax未満でなければなりません。";
                    }
                }

                return(message);
            })
            .Subscribe(_ => MinVelocity.ForceValidate())
            .AddTo(disposable);

            MaxVelocity.ObserveHasErrors
            .ToReadOnlyReactivePropertySlim(mode: ReactivePropertyMode.DistinctUntilChanged)
            .Where(x => !x)
            .Subscribe(_ => MaxVelocity.ForceNotify())
            .AddTo(disposable);

            MinPitch
            .SetValidateNotifyError(x =>
            {
                double min;
                string message;

                if (Utilities.TryParse(x, NumberRange.NonNegative, out min, out message))
                {
                    double max;

                    if (Utilities.TryParse(MaxPitch.Value, NumberRange.NonNegative, out max) && min >= max)
                    {
                        message = "MinはMax未満でなければなりません。";
                    }
                }

                return(message);
            })
            .Subscribe(_ => MaxPitch.ForceValidate())
            .AddTo(disposable);

            MinPitch.ObserveHasErrors
            .ToReadOnlyReactivePropertySlim(mode: ReactivePropertyMode.DistinctUntilChanged)
            .Where(x => !x)
            .Subscribe(_ => MinPitch.ForceNotify())
            .AddTo(disposable);

            MaxPitch
            .SetValidateNotifyError(x =>
            {
                double max;
                string message;

                if (Utilities.TryParse(x, NumberRange.NonNegative, out max, out message))
                {
                    double min;

                    if (Utilities.TryParse(MinPitch.Value, NumberRange.NonNegative, out min) && max <= min)
                    {
                        message = "MinはMax未満でなければなりません。";
                    }
                }

                return(message);
            })
            .Subscribe(_ => MinPitch.ForceValidate())
            .AddTo(disposable);

            MaxPitch.ObserveHasErrors
            .ToReadOnlyReactivePropertySlim(mode: ReactivePropertyMode.DistinctUntilChanged)
            .Where(x => !x)
            .Subscribe(_ => MaxPitch.ForceNotify())
            .AddTo(disposable);

            MinVolume
            .SetValidateNotifyError(x =>
            {
                double min;
                string message;

                if (Utilities.TryParse(x, NumberRange.NonNegative, out min, out message))
                {
                    double max;

                    if (Utilities.TryParse(MaxVolume.Value, NumberRange.NonNegative, out max) && min >= max)
                    {
                        message = "MinはMax未満でなければなりません。";
                    }
                }

                return(message);
            })
            .Subscribe(_ => MaxVolume.ForceValidate())
            .AddTo(disposable);

            MinVolume.ObserveHasErrors
            .ToReadOnlyReactivePropertySlim(mode: ReactivePropertyMode.DistinctUntilChanged)
            .Where(x => !x)
            .Subscribe(_ => MinVolume.ForceNotify())
            .AddTo(disposable);

            MaxVolume
            .SetValidateNotifyError(x =>
            {
                double max;
                string message;

                if (Utilities.TryParse(x, NumberRange.NonNegative, out max, out message))
                {
                    double min;

                    if (Utilities.TryParse(MinVolume.Value, NumberRange.NonNegative, out min) && max <= min)
                    {
                        message = "MinはMax未満でなければなりません。";
                    }
                }

                return(message);
            })
            .Subscribe(_ => MinVolume.ForceValidate())
            .AddTo(disposable);

            MaxVolume.ObserveHasErrors
            .ToReadOnlyReactivePropertySlim(mode: ReactivePropertyMode.DistinctUntilChanged)
            .Where(x => !x)
            .Subscribe(_ => MaxVolume.ForceNotify())
            .AddTo(disposable);
        }