public override void Update(UpdateState state)
        {
            var _3d = FSOEnvironment.Enable3D;

            base.Update(state);
            bool rotated = false;

            if (!Master.TVisible || !UIScreen.Current.Visible || !state.ProcessMouseEvents)
            {
                ScrollWheelInvalid = true;
            }

            if (!FSOEnvironment.SoftwareKeyboard)
            {
                if (!state.WindowFocused)
                {
                    ScrollWheelInvalid = true;
                }
                else if (ScrollWheelInvalid)
                {
                    LastMouseWheel     = state.MouseState.ScrollWheelValue;
                    ScrollWheelInvalid = false;
                }
                if (state.WindowFocused && state.MouseState.ScrollWheelValue != LastMouseWheel)
                {
                    var diff = state.MouseState.ScrollWheelValue - LastMouseWheel;
                    Master.TargetZoom  = Master.TargetZoom + diff / 1600f;
                    LastMouseWheel     = state.MouseState.ScrollWheelValue;
                    Master.TargetZoom  = Math.Max(MinZoom, Math.Min(Master.TargetZoom, MaxZoom));
                    Master.UserModZoom = true;
                    ZoomFreezeTime     = 10 * FSOEnvironment.RefreshRate / 60;
                }
            }

            MiceDown = new HashSet <int>(MiceDown.Intersect(state.MouseStates.Select(x => x.ID)));

            int transitionTo = -2;

            if (MiceDown.Count == 0)
            {
                if (Mode != -1)
                {
                    transitionTo = -1;
                }
            }
            else if (MiceDown.Count == 1)
            {
                if (Mode == -1)
                {
                    transitionTo = 0;
                }
                if (Mode == 2)
                {
                    transitionTo = 1;
                }
            }
            else if (MiceDown.Count >= 2)
            {
                //cannot possibly be a touch
                if (Mode < 2)
                {
                    transitionTo = 2;
                }
                if (Mode == -1)
                {
                    Mode = 0;
                }
            }

            switch (Mode)
            {
            case -1:
                if (transitionTo == 0)
                {
                    var mouse = state.MouseStates.FirstOrDefault(x => x.ID == MiceDown.First());
                    if (mouse != null)
                    {
                        TapPoint      = new Point(mouse.MouseState.X, mouse.MouseState.Y);
                        MiceDownTimer = 0;
                        Mode          = 0;
                    }
                }
                break;

            case 0:
            case 1:
                if (transitionTo == 2)
                {
                    //register the first distance between the two taps
                    var m1 = state.MouseStates.FirstOrDefault(x => x.ID == MiceDown.ElementAt(0));
                    var m2 = state.MouseStates.FirstOrDefault(x => x.ID == MiceDown.ElementAt(1));
                    BaseVector = (new Point(m2.MouseState.X, m2.MouseState.Y) - new Point(m1.MouseState.X, m1.MouseState.Y)).ToVector2();
                    StartScale = Master.TargetZoom;
                    if (_3d)
                    {
                        LastAngleX = null;
                    }

                    //scroll anchor should change to center of two touches without drastically changing scroll
                    TapPoint = new Point(m2.MouseState.X / 2, m2.MouseState.Y / 2) + new Point(m1.MouseState.X / 2, m1.MouseState.Y / 2);

                    Mode = 2;
                }
                else
                {
                    if (Mode == 0)
                    {
                        ScrollVelocity = new Vector2();
                        if (transitionTo == -1)
                        {
                            Mode = -1;
                            Master.Click(GetScaledPoint(TapPoint), state);
                        }
                        else
                        {
                            var mouse = state.MouseStates.FirstOrDefault(x => x.ID == MiceDown.First());
                            var time  = FSOEnvironment.SoftwareKeyboard ? (TAP_TIMER * FSOEnvironment.RefreshRate / 60) : 0;
                            if ((TapPoint - new Point(mouse.MouseState.X, mouse.MouseState.Y)).ToVector2().Length() > TAP_POINT_DIST)
                            {
                                Mode = 1;     //become a scroll
                            }
                            else if (++MiceDownTimer > time)
                            {
                                Mode = 3;
                                Master.Click(GetScaledPoint(TapPoint), state);
                            }
                        }
                    }
                    if (Mode == 1)
                    {
                        if (transitionTo == -1)
                        {
                            //release our scroll velocity
                            Mode = -1;
                        }
                        else
                        {
                            var mouse  = state.MouseStates.FirstOrDefault(x => x.ID == MiceDown.First());
                            var newTap = new Point(mouse.MouseState.X, mouse.MouseState.Y);
                            ScrollVelocity = (newTap - TapPoint).ToVector2();
                            TapPoint       = newTap;
                        }
                    }
                }
                break;

            case 2:
                if (transitionTo != -2)
                {
                    //release rotation gesture. todo.
                }
                if (transitionTo == 1)
                {
                    //go back to being a normal scroll
                    //again, anchor should change to single point without drastic scroll change
                    var mouse = state.MouseStates.FirstOrDefault(x => x.ID == MiceDown.First());
                    TapPoint = new Point(mouse.MouseState.X, mouse.MouseState.Y);
                    Mode     = 1;
                }
                else if (transitionTo == -1)
                {
                    Mode = -1;
                }
                else if (transitionTo == -2)
                {
                    var m1     = state.MouseStates.FirstOrDefault(x => x.ID == MiceDown.ElementAt(0));
                    var m2     = state.MouseStates.FirstOrDefault(x => x.ID == MiceDown.ElementAt(1));
                    var vector = (new Point(m2.MouseState.X, m2.MouseState.Y) - new Point(m1.MouseState.X, m1.MouseState.Y)).ToVector2();
                    var newTap = new Point(m2.MouseState.X / 2, m2.MouseState.Y / 2) + new Point(m1.MouseState.X / 2, m1.MouseState.Y / 2);
                    ScrollVelocity = (newTap - TapPoint).ToVector2();
                    TapPoint       = newTap;

                    Master.TargetZoom  = vector.Length() / BaseVector.Length() * StartScale;
                    Master.UserModZoom = true;

                    //clockwise if dot product b against a rotated 90 degrees clockwise is positive
                    var a = BaseVector;
                    var b = vector;
                    a.Normalize(); b.Normalize();
                    var clockwise = ((-a.Y) * b.X + a.X * b.Y) > 0;
                    var angle     = (float)Math.Acos(Vector2.Dot(a, b));
                    RotateAngle = clockwise ? angle : -angle;

                    if (_3d)
                    {
                        var rcState = Master.Rotate;
                        if (LastAngleX != null)
                        {
                            float rot = rcState.RotationX - (float)DirectionUtils.Difference(RotateAngle, LastAngleX.Value);
                            if (!float.IsNaN(rot))
                            {
                                rcState.RotationX = rot;
                            }
                        }
                        LastAngleX         = RotateAngle;
                        rcState.RotationY += ScrollVelocity.Y / 200;
                        ScrollVelocity     = Vector2.Zero;
                    }
                    else
                    {
                        if (Math.Abs(RotateAngle) > Math.PI / 8)
                        {
                            Master.TargetZoom = StartScale;
                        }
                    }
                }
                break;

            case 3:
                if (transitionTo == -1)
                {
                    Mode = -1;
                }
                break;
            }
            if (Mode != 2 && RotateAngle != 0 && !_3d)
            {
                if (Math.Abs(RotateAngle) > Math.PI / 4)
                {
                    //confirmed
                    var screen = (IGameScreen)GameFacade.Screens.CurrentUIScreen;
                    if (RotateAngle > 0)
                    {
                        screen.Rotation = (screen.Rotation + 1) % 4;
                    }
                    else
                    {
                        screen.Rotation = (screen.Rotation + 3) % 4;
                    }

                    HITVM.Get.PlaySoundEvent(UISounds.ObjectRotate);
                    rotated = true;
                }
                RotateAngle = 0;
            }
            ScrollVelocityHistory.Insert(0, ScrollVelocity);
            if (ScrollVelocityHistory.Count > 5)
            {
                ScrollVelocityHistory.RemoveAt(ScrollVelocityHistory.Count - 1);
            }
            if (transitionTo == -1)
            {
                //ScrollVelocity = LastValidScrollVelocity / UpdatesSinceDraw;
                if (ScrollVelocityHistory.Count > 1)
                {
                    int total = 0;
                    ScrollVelocity = new Vector2();
                    for (int i = 1; i < ScrollVelocityHistory.Count; i++)
                    {
                        total++;
                        ScrollVelocity += ScrollVelocityHistory[i];
                    }
                    ScrollVelocity /= total;
                }
                ScrollVelocityHistory.Clear();
            }

            if (Mode == -1)
            {
                ScrollVelocity *= 0.95f * Math.Min(ScrollVelocity.Length(), 1);
                if (Master.TargetZoom < 1.25f && ZoomFreezeTime == 0 && !_3d)
                {
                    Master.UserModZoom = true;
                    float snapZoom = 1f;
                    float dist     = 200f;
                    foreach (var snappable in SnapZooms)
                    {
                        var newDist = Math.Abs(Master.TargetZoom - snappable);
                        if (newDist < dist)
                        {
                            dist     = newDist;
                            snapZoom = snappable;
                        }
                    }
                    if (dist > 0)
                    {
                        var move = snapZoom - Master.TargetZoom;
                        if (move != 0)
                        {
                            if (move > 0)
                            {
                                Master.TargetZoom += 0.01f;
                            }
                            else
                            {
                                Master.TargetZoom -= 0.01f;
                            }
                        }
                        if (move * (snapZoom - Master.TargetZoom) < 0)
                        {
                            Master.TargetZoom = snapZoom;
                        }
                    }
                }
                if (ZoomFreezeTime > 0)
                {
                    ZoomFreezeTime--;
                }
            }
            if (ScrollVelocity.Length() > 0.001f)
            {
                Master.Scroll(-ScrollVelocity / (Master.TargetZoom * 128));
            }

            UpdatesSinceDraw++;
        }