Example #1
0
        public void FromString_ShouldReturnObject(
            string theString,
            double expectedL,
            double expectedA,
            double expectedB,
            bool expectedNull = false
            )
        {
            var lab = Lab.FromString(theString);

            if (expectedNull)
            {
                lab.ShouldBeNull();
            }
            else
            {
                lab.ShouldBeOfType <Lab>();
                lab?.L.ShouldBe(expectedL);
                lab?.A.ShouldBe(expectedA);
                lab?.B.ShouldBe(expectedB);
            }
        }
Example #2
0
        public MainWindow()
        {
            InitializeComponent();
            Style = (Style)FindResource(typeof(System.Windows.Window));
            _vm   = new MainWindowViewModel();
            _vm.LoadFromPersistedData(PersistedState.Data, ColorModelTabs);
            DataContext = _vm;

            if (_vm.Rgb == Rgb.Empty)
            {
                // Material Blue – #2196F3
                _vm.RefreshFromRgb(Rgb.FromScaledValues(33, 150, 243));
            }

            // Eyedropper
            EyedropperButton.Click += (o, ev) => { StartEyedropper(); };

            // Lighter / Darker Buttons
            LighterButton.Click += (o, ev) => { _vm.RefreshFromHsl(_vm.Hsl.Lighter(5)); };
            DarkerButton.Click  += (o, ev) => { _vm.RefreshFromHsl(_vm.Hsl.Darker(5)); };

            // Color model tabs
            _vm.PropertyChanged += (o, ev) =>
            {
                if (ev.PropertyName == "ActiveColorModelTabItem")
                {
                    _vm.SaveToPersistedData(PersistedState.Data);
                }
            };

            // Helper function to reduce clipboard button boilerplate:
            void SetupClipboardButton(Button button, Func <string> valueGetter)
            {
                EventUtil.HandleClick(button, (sender, e) =>
                {
                    // SetDataObject seems to work better than SetText or other methods, when programs like RealVNC are locking up the clipboard.
                    Clipboard.SetDataObject(valueGetter());
                    DisplayClipboardCheckmark((Button)sender);
                });
            }

            //
            // RGB fields
            //

            EventUtil.HandleKey(Key.Up, RgbRed, () => { _vm.RefreshFromRgb(_vm.Rgb.WithRed(_vm.Rgb.Red + 0.01)); });
            EventUtil.HandleKey(Key.Up, RgbGreen, () => { _vm.RefreshFromRgb(_vm.Rgb.WithGreen(_vm.Rgb.Green + 0.01)); });
            EventUtil.HandleKey(Key.Up, RgbBlue, () => { _vm.RefreshFromRgb(_vm.Rgb.WithBlue(_vm.Rgb.Blue + 0.01)); });
            EventUtil.HandleKey(Key.Up, RgbScaledRed, () => { _vm.RefreshFromRgb(_vm.Rgb.WithRed(_vm.Rgb.ScaledRed + 1)); });
            EventUtil.HandleKey(Key.Up, RgbScaledGreen, () => { _vm.RefreshFromRgb(_vm.Rgb.WithGreen(_vm.Rgb.ScaledGreen + 1)); });
            EventUtil.HandleKey(Key.Up, RgbScaledBlue, () => { _vm.RefreshFromRgb(_vm.Rgb.WithBlue(_vm.Rgb.ScaledBlue + 1)); });

            EventUtil.HandleKey(Key.Down, RgbRed, () => { _vm.RefreshFromRgb(_vm.Rgb.WithRed(_vm.Rgb.Red - 0.01)); });
            EventUtil.HandleKey(Key.Down, RgbGreen, () => { _vm.RefreshFromRgb(_vm.Rgb.WithGreen(_vm.Rgb.Green - 0.01)); });
            EventUtil.HandleKey(Key.Down, RgbBlue, () => { _vm.RefreshFromRgb(_vm.Rgb.WithBlue(_vm.Rgb.Blue - 0.01)); });
            EventUtil.HandleKey(Key.Down, RgbScaledRed, () => { _vm.RefreshFromRgb(_vm.Rgb.WithRed(_vm.Rgb.ScaledRed - 1)); });
            EventUtil.HandleKey(Key.Down, RgbScaledGreen, () => { _vm.RefreshFromRgb(_vm.Rgb.WithGreen(_vm.Rgb.ScaledGreen - 1)); });
            EventUtil.HandleKey(Key.Down, RgbScaledBlue, () => { _vm.RefreshFromRgb(_vm.Rgb.WithBlue(_vm.Rgb.ScaledBlue - 1)); });

            EventUtil.HandleMouseWheel(
                RgbRed,
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithRed(_vm.Rgb.Red + 0.01)); },
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithRed(_vm.Rgb.Red - 0.01)); }
                );
            EventUtil.HandleMouseWheel(
                RgbGreen,
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithGreen(_vm.Rgb.Green + 0.01)); },
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithGreen(_vm.Rgb.Green - 0.01)); }
                );
            EventUtil.HandleMouseWheel(
                RgbBlue,
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithBlue(_vm.Rgb.Blue + 0.01)); },
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithBlue(_vm.Rgb.Blue - 0.01)); }
                );

            EventUtil.HandleMouseWheel(
                RgbScaledRed,
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithRed(_vm.Rgb.ScaledRed + 1)); },
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithRed(_vm.Rgb.ScaledRed - 1)); }
                );
            EventUtil.HandleMouseWheel(
                RgbScaledGreen,
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithGreen(_vm.Rgb.ScaledGreen + 1)); },
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithGreen(_vm.Rgb.ScaledGreen - 1)); }
                );
            EventUtil.HandleMouseWheel(
                RgbScaledBlue,
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithBlue(_vm.Rgb.ScaledBlue + 1)); },
                () => { _vm.RefreshFromRgb(_vm.Rgb.WithBlue(_vm.Rgb.ScaledBlue - 1)); }
                );

            EventUtil.HandleSliderChange(RgbRedSlider, value => { _vm.RefreshFromRgb(_vm.Rgb.WithRed(value)); });
            EventUtil.HandleSliderChange(RgbGreenSlider, value => { _vm.RefreshFromRgb(_vm.Rgb.WithGreen(value)); });
            EventUtil.HandleSliderChange(RgbBlueSlider, value => { _vm.RefreshFromRgb(_vm.Rgb.WithBlue(value)); });

            _vm.PropertyChangedByUser += (o, ev) =>
            {
                int    intVal;
                bool   isInt;
                bool   isDouble;
                double doubleVal;
                Rgb?   nullableRgb;

                switch (ev.PropertyName)
                {
                case nameof(MainWindowViewModel.RgbText):
                    nullableRgb = Rgb.FromString(_vm.RgbText);
                    if (nullableRgb.HasValue)
                    {
                        _vm.RefreshFromRgb(nullableRgb.Value);
                    }
                    break;

                case nameof(MainWindowViewModel.RgbScaledText):
                    nullableRgb = Rgb.FromScaledString(_vm.RgbScaledText);
                    if (nullableRgb.HasValue)
                    {
                        _vm.RefreshFromRgb(nullableRgb.Value);
                    }
                    break;

                case nameof(MainWindowViewModel.RgbScaledRed):
                    isInt = int.TryParse(_vm.RgbScaledRed, out intVal);
                    if (!isInt || !Rgb.IsValidScaledComponent(intVal))
                    {
                        return;
                    }
                    _vm.RefreshFromRgb(_vm.Rgb.WithRed(intVal));
                    break;

                case nameof(MainWindowViewModel.RgbScaledGreen):
                    isInt = int.TryParse(_vm.RgbScaledGreen, out intVal);
                    if (!isInt || !Rgb.IsValidScaledComponent(intVal))
                    {
                        return;
                    }
                    _vm.RefreshFromRgb(_vm.Rgb.WithGreen(intVal));
                    break;

                case nameof(MainWindowViewModel.RgbScaledBlue):
                    isInt = int.TryParse(_vm.RgbScaledBlue, out intVal);
                    if (!isInt || !Rgb.IsValidScaledComponent(intVal))
                    {
                        return;
                    }
                    _vm.RefreshFromRgb(_vm.Rgb.WithBlue(intVal));
                    break;

                case nameof(MainWindowViewModel.RgbRed):
                    isDouble = double.TryParse(_vm.RgbRed, out doubleVal);
                    if (!isDouble || !Rgb.IsValidComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromRgb(_vm.Rgb.WithRed(doubleVal));
                    break;

                case nameof(MainWindowViewModel.RgbGreen):
                    isDouble = double.TryParse(_vm.RgbGreen, out doubleVal);
                    if (!isDouble || !Rgb.IsValidComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromRgb(_vm.Rgb.WithGreen(doubleVal));
                    break;

                case nameof(MainWindowViewModel.RgbBlue):
                    isDouble = double.TryParse(_vm.RgbBlue, out doubleVal);
                    if (!isDouble || !Rgb.IsValidComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromRgb(_vm.Rgb.WithBlue(doubleVal));
                    break;
                }
            };

            SetupClipboardButton(RgbScaledTextClipboardButton, () => _vm.RgbScaledText);
            SetupClipboardButton(RgbTextClipboardButton, () => _vm.RgbText);

            //
            // HEX fields
            //

            EventUtil.HandleKey(Key.Up, HexRed, () => { _vm.RefreshFromHex(_vm.Hex.WithRed(_vm.Hex.Red + 1)); });
            EventUtil.HandleKey(Key.Up, HexGreen, () => { _vm.RefreshFromHex(_vm.Hex.WithGreen(_vm.Hex.Green + 1)); });
            EventUtil.HandleKey(Key.Up, HexBlue, () => { _vm.RefreshFromHex(_vm.Hex.WithBlue(_vm.Hex.Blue + 1)); });

            EventUtil.HandleKey(Key.Down, HexRed, () => { _vm.RefreshFromHex(_vm.Hex.WithRed(_vm.Hex.Red - 1)); });
            EventUtil.HandleKey(Key.Down, HexGreen, () => { _vm.RefreshFromHex(_vm.Hex.WithGreen(_vm.Hex.Green - 1)); });
            EventUtil.HandleKey(Key.Down, HexBlue, () => { _vm.RefreshFromHex(_vm.Hex.WithBlue(_vm.Hex.Blue - 1)); });

            EventUtil.HandleMouseWheel(
                HexRed,
                () => { _vm.RefreshFromHex(_vm.Hex.WithRed(_vm.Hex.Red + 1)); },
                () => { _vm.RefreshFromHex(_vm.Hex.WithRed(_vm.Hex.Red - 1)); }
                );
            EventUtil.HandleMouseWheel(
                HexGreen,
                () => { _vm.RefreshFromHex(_vm.Hex.WithGreen(_vm.Hex.Green + 1)); },
                () => { _vm.RefreshFromHex(_vm.Hex.WithGreen(_vm.Hex.Green - 1)); }
                );
            EventUtil.HandleMouseWheel(
                HexBlue,
                () => { _vm.RefreshFromHex(_vm.Hex.WithBlue(_vm.Hex.Blue + 1)); },
                () => { _vm.RefreshFromHex(_vm.Hex.WithBlue(_vm.Hex.Blue - 1)); }
                );

            EventUtil.HandleSliderChange(HexRedSlider, value => { _vm.RefreshFromRgb(_vm.Rgb.WithRed(value)); });
            EventUtil.HandleSliderChange(HexGreenSlider, value => { _vm.RefreshFromRgb(_vm.Rgb.WithGreen(value)); });
            EventUtil.HandleSliderChange(HexBlueSlider, value => { _vm.RefreshFromRgb(_vm.Rgb.WithBlue(value)); });

            // The box actively ignores 3-char hex strings while typing so that 6-chars may be entered without interruption.
            // However, 3-char hex strings can still be entered by pressing Enter or dropping focus:
            EventUtil.HandleInputEnterOrFocusLost(HexText, value =>
            {
                var nullableHex = Hex.FromString(_vm.HexText);
                if (nullableHex.HasValue)
                {
                    _vm.RefreshFromHex(nullableHex.Value);
                }
            });

            _vm.PropertyChangedByUser += (o, ev) =>
            {
                switch (ev.PropertyName)
                {
                case nameof(MainWindowViewModel.HexText):
                    var nullableHex = Hex.From6CharString(_vm.HexText);
                    if (nullableHex.HasValue)
                    {
                        _vm.RefreshFromHex(nullableHex.Value);
                    }
                    break;

                case nameof(MainWindowViewModel.HexRed):
                    // Length comparison for better UX. Stops converting "0" to "00" and moving user cursor.
                    if (!Hex.IsValidHexPart(_vm.HexRed) || _vm.HexRed.Length != 2)
                    {
                        return;
                    }
                    _vm.RefreshFromHex(_vm.Hex.WithRed(_vm.HexRed));
                    break;

                case nameof(MainWindowViewModel.HexGreen):
                    // Length comparison for better UX. Stops converting "0" to "00" and moving user cursor.
                    if (!Hex.IsValidHexPart(_vm.HexGreen) || _vm.HexGreen.Length != 2)
                    {
                        return;
                    }
                    _vm.RefreshFromHex(_vm.Hex.WithGreen(_vm.HexGreen));
                    break;

                case nameof(MainWindowViewModel.HexBlue):
                    // Length comparison for better UX. Stops converting "0" to "00" and moving user cursor.
                    if (!Hex.IsValidHexPart(_vm.HexBlue) || _vm.HexBlue.Length != 2)
                    {
                        return;
                    }
                    _vm.RefreshFromHex(_vm.Hex.WithBlue(_vm.HexBlue));
                    break;
                }
            };

            SetupClipboardButton(HexTextClipboardButton, () => _vm.HexText);

            //
            // HSL fields
            //

            EventUtil.HandleKey(Key.Up, HslHue, () => { _vm.RefreshFromHsl(_vm.Hsl.WithHue(_vm.Hsl.Hue + 0.01)); });
            EventUtil.HandleKey(Key.Up, HslSaturation, () => { _vm.RefreshFromHsl(_vm.Hsl.WithSaturation(_vm.Hsl.Saturation + 0.01)); });
            EventUtil.HandleKey(Key.Up, HslLuminance, () => { _vm.RefreshFromHsl(_vm.Hsl.WithLuminance(_vm.Hsl.Luminance + 0.01)); });
            EventUtil.HandleKey(Key.Up, HslScaledHue, () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledHue(_vm.Hsl.ScaledHue + 1)); });
            EventUtil.HandleKey(Key.Up, HslScaledSaturation, () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledSaturation(_vm.Hsl.ScaledSaturation + 1)); });
            EventUtil.HandleKey(Key.Up, HslScaledLuminance, () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledLuminance(_vm.Hsl.ScaledLuminance + 1)); });

            EventUtil.HandleKey(Key.Down, HslHue, () => { _vm.RefreshFromHsl(_vm.Hsl.WithHue(_vm.Hsl.Hue - 0.01)); });
            EventUtil.HandleKey(Key.Down, HslSaturation, () => { _vm.RefreshFromHsl(_vm.Hsl.WithSaturation(_vm.Hsl.Saturation - 0.01)); });
            EventUtil.HandleKey(Key.Down, HslLuminance, () => { _vm.RefreshFromHsl(_vm.Hsl.WithLuminance(_vm.Hsl.Luminance - 0.01)); });
            EventUtil.HandleKey(Key.Down, HslScaledHue, () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledHue(_vm.Hsl.ScaledHue - 1)); });
            EventUtil.HandleKey(Key.Down, HslScaledSaturation, () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledSaturation(_vm.Hsl.ScaledSaturation - 1)); });
            EventUtil.HandleKey(Key.Down, HslScaledLuminance, () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledLuminance(_vm.Hsl.ScaledLuminance - 1)); });

            EventUtil.HandleMouseWheel(
                HslHue,
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithHue(_vm.Hsl.Hue + 0.01)); },
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithHue(_vm.Hsl.Hue - 0.01)); }
                );
            EventUtil.HandleMouseWheel(
                HslSaturation,
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithSaturation(_vm.Hsl.Saturation + 0.01)); },
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithSaturation(_vm.Hsl.Saturation - 0.01)); }
                );
            EventUtil.HandleMouseWheel(
                HslLuminance,
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithLuminance(_vm.Hsl.Luminance + 0.01)); },
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithLuminance(_vm.Hsl.Luminance - 0.01)); }
                );
            EventUtil.HandleMouseWheel(
                HslScaledHue,
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledHue(_vm.Hsl.ScaledHue + 1)); },
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledHue(_vm.Hsl.ScaledHue - 1)); }
                );
            EventUtil.HandleMouseWheel(
                HslScaledSaturation,
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledSaturation(_vm.Hsl.ScaledSaturation + 1)); },
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledSaturation(_vm.Hsl.ScaledSaturation - 1)); }
                );
            EventUtil.HandleMouseWheel(
                HslScaledLuminance,
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledLuminance(_vm.Hsl.ScaledLuminance + 1)); },
                () => { _vm.RefreshFromHsl(_vm.Hsl.WithScaledLuminance(_vm.Hsl.ScaledLuminance - 1)); }
                );

            EventUtil.HandleSliderChange(HslHueSlider, value => { _vm.RefreshFromHsl(_vm.Hsl.WithHue(value)); });
            EventUtil.HandleSliderChange(HslSaturationSlider, value => { _vm.RefreshFromHsl(_vm.Hsl.WithSaturation(value)); });
            EventUtil.HandleSliderChange(HslLuminanceSlider, value => { _vm.RefreshFromHsl(_vm.Hsl.WithLuminance(value)); });

            _vm.PropertyChangedByUser += (o, ev) =>
            {
                bool   isDouble;
                double doubleVal;
                Hsl?   nullableHsl;

                switch (ev.PropertyName)
                {
                case nameof(MainWindowViewModel.HslText):
                    nullableHsl = Hsl.FromString(_vm.HslText);
                    if (nullableHsl.HasValue)
                    {
                        _vm.RefreshFromHsl(nullableHsl.Value);
                    }
                    break;

                case nameof(MainWindowViewModel.HslScaledText):
                    nullableHsl = Hsl.FromScaledString(_vm.HslScaledText);
                    if (nullableHsl.HasValue)
                    {
                        _vm.RefreshFromHsl(nullableHsl.Value);
                    }
                    break;

                case nameof(MainWindowViewModel.HslHue):
                    isDouble = double.TryParse(_vm.HslHue, out doubleVal);
                    if (!isDouble || !Hsl.IsValidHue(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsl(_vm.Hsl.WithHue(doubleVal));
                    break;

                case nameof(MainWindowViewModel.HslSaturation):
                    isDouble = double.TryParse(_vm.HslSaturation, out doubleVal);
                    if (!isDouble || !Hsl.IsValidSaturation(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsl(_vm.Hsl.WithSaturation(doubleVal));
                    break;

                case nameof(MainWindowViewModel.HslLuminance):
                    isDouble = double.TryParse(_vm.HslLuminance, out doubleVal);
                    if (!isDouble || !Hsl.IsValidLuminance(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsl(_vm.Hsl.WithLuminance(doubleVal));
                    break;

                case nameof(MainWindowViewModel.HslScaledHue):
                    isDouble = double.TryParse(_vm.HslScaledHue, out doubleVal);
                    if (!isDouble || !Hsl.IsValidScaledHue(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsl(_vm.Hsl.WithScaledHue(doubleVal));
                    break;

                case nameof(MainWindowViewModel.HslScaledSaturation):
                    isDouble = double.TryParse(_vm.HslScaledHue, out doubleVal);
                    if (!isDouble || !Hsl.IsValidScaledSaturation(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsl(_vm.Hsl.WithScaledSaturation(doubleVal));
                    break;

                case nameof(MainWindowViewModel.HslScaledLuminance):
                    isDouble = double.TryParse(_vm.HslScaledHue, out doubleVal);
                    if (!isDouble || !Hsl.IsValidScaledLuminance(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsl(_vm.Hsl.WithScaledLuminance(doubleVal));
                    break;
                }
            };

            SetupClipboardButton(HslScaledTextClipboardButton, () => _vm.HslScaledText);
            SetupClipboardButton(HslTextClipboardButton, () => _vm.HslText);

            //
            // HSV fields
            //

            EventUtil.HandleKey(Key.Up, HsvHue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithHue(_vm.Hsv.Hue + 0.01)); });
            EventUtil.HandleKey(Key.Up, HsvSaturation, () => { _vm.RefreshFromHsv(_vm.Hsv.WithSaturation(_vm.Hsv.Saturation + 0.01)); });
            EventUtil.HandleKey(Key.Up, HsvValue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithValue(_vm.Hsv.Value + 0.01)); });
            EventUtil.HandleKey(Key.Up, HsvScaledHue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledHue(_vm.Hsv.ScaledHue + 1)); });
            EventUtil.HandleKey(Key.Up, HsvScaledSaturation, () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledSaturation(_vm.Hsv.ScaledSaturation + 1)); });
            EventUtil.HandleKey(Key.Up, HsvScaledValue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledValue(_vm.Hsv.ScaledValue + 1)); });

            EventUtil.HandleKey(Key.Down, HsvHue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithHue(_vm.Hsv.Hue - 0.01)); });
            EventUtil.HandleKey(Key.Down, HsvSaturation, () => { _vm.RefreshFromHsv(_vm.Hsv.WithSaturation(_vm.Hsv.Saturation - 0.01)); });
            EventUtil.HandleKey(Key.Down, HsvValue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithValue(_vm.Hsv.Value - 0.01)); });
            EventUtil.HandleKey(Key.Down, HsvScaledHue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledHue(_vm.Hsv.ScaledHue - 1)); });
            EventUtil.HandleKey(Key.Down, HsvScaledSaturation, () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledSaturation(_vm.Hsv.ScaledSaturation - 1)); });
            EventUtil.HandleKey(Key.Down, HsvScaledValue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledValue(_vm.Hsv.ScaledValue - 1)); });

            EventUtil.HandleMouseWheel(HsvHue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithHue(_vm.Hsv.Hue + 0.01)); },
                                       () => { _vm.RefreshFromHsv(_vm.Hsv.WithHue(_vm.Hsv.Hue - 0.01)); });
            EventUtil.HandleMouseWheel(HsvSaturation, () => { _vm.RefreshFromHsv(_vm.Hsv.WithSaturation(_vm.Hsv.Saturation + 0.01)); },
                                       () => { _vm.RefreshFromHsv(_vm.Hsv.WithSaturation(_vm.Hsv.Saturation - 0.01)); });
            EventUtil.HandleMouseWheel(HsvValue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithValue(_vm.Hsv.Value + 0.01)); },
                                       () => { _vm.RefreshFromHsv(_vm.Hsv.WithValue(_vm.Hsv.Value - 0.01)); });
            EventUtil.HandleMouseWheel(HsvScaledHue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledHue(_vm.Hsv.ScaledHue + 1)); },
                                       () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledHue(_vm.Hsv.ScaledHue - 1)); });
            EventUtil.HandleMouseWheel(HsvScaledSaturation, () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledSaturation(_vm.Hsv.ScaledSaturation + 1)); },
                                       () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledSaturation(_vm.Hsv.ScaledSaturation - 1)); });
            EventUtil.HandleMouseWheel(HsvScaledValue, () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledValue(_vm.Hsv.ScaledValue + 1)); },
                                       () => { _vm.RefreshFromHsv(_vm.Hsv.WithScaledValue(_vm.Hsv.ScaledValue - 1)); });

            EventUtil.HandleSliderChange(HsvHueSlider, value => { _vm.RefreshFromHsv(_vm.Hsv.WithHue(value)); });
            EventUtil.HandleSliderChange(HsvSaturationSlider, value => { _vm.RefreshFromHsv(_vm.Hsv.WithSaturation(value)); });
            EventUtil.HandleSliderChange(HsvValueSlider, value => { _vm.RefreshFromHsv(_vm.Hsv.WithValue(value)); });

            _vm.PropertyChangedByUser += (o, ev) =>
            {
                bool   isDouble;
                double doubleVal;
                Hsv?   nullableHsv;

                switch (ev.PropertyName)
                {
                case nameof(MainWindowViewModel.HsvText):
                    nullableHsv = Hsv.FromString(_vm.HsvText);
                    if (nullableHsv.HasValue)
                    {
                        _vm.RefreshFromHsv(nullableHsv.Value);
                    }
                    break;

                case nameof(MainWindowViewModel.HsvScaledText):
                    nullableHsv = Hsv.FromScaledString(_vm.HsvScaledText);
                    if (nullableHsv.HasValue)
                    {
                        _vm.RefreshFromHsv(nullableHsv.Value);
                    }
                    break;

                case nameof(MainWindowViewModel.HsvHue):
                    isDouble = double.TryParse(_vm.HsvHue, out doubleVal);
                    if (!isDouble || !Hsv.IsValidHue(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsv(_vm.Hsv.WithHue(doubleVal));
                    break;

                case nameof(MainWindowViewModel.HsvSaturation):
                    isDouble = double.TryParse(_vm.HsvSaturation, out doubleVal);
                    if (!isDouble || !Hsv.IsValidSaturation(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsv(_vm.Hsv.WithSaturation(doubleVal));
                    break;

                case nameof(MainWindowViewModel.HsvValue):
                    isDouble = double.TryParse(_vm.HsvValue, out doubleVal);
                    if (!isDouble || !Hsv.IsValidValue(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsv(_vm.Hsv.WithValue(doubleVal));
                    break;

                case nameof(MainWindowViewModel.HsvScaledHue):
                    isDouble = double.TryParse(_vm.HsvScaledHue, out doubleVal);
                    if (!isDouble || !Hsv.IsValidScaledHue(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsv(_vm.Hsv.WithScaledHue(doubleVal));
                    break;

                case nameof(MainWindowViewModel.HsvScaledSaturation):
                    isDouble = double.TryParse(_vm.HsvScaledHue, out doubleVal);
                    if (!isDouble || !Hsv.IsValidScaledSaturation(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsv(_vm.Hsv.WithScaledSaturation(doubleVal));
                    break;

                case nameof(MainWindowViewModel.HsvScaledValue):
                    isDouble = double.TryParse(_vm.HsvScaledHue, out doubleVal);
                    if (!isDouble || !Hsv.IsValidScaledValue(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromHsv(_vm.Hsv.WithScaledValue(doubleVal));
                    break;
                }
            };

            SetupClipboardButton(HsvScaledTextClipboardButton, () => _vm.HsvScaledText);
            SetupClipboardButton(HsvTextClipboardButton, () => _vm.HsvText);

            //
            // CMYK fields
            //

            EventUtil.HandleKey(Key.Up, CmykCyan, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithCyan(_vm.Cmyk.Cyan + 0.01)); });
            EventUtil.HandleKey(Key.Up, CmykMagenta, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithMagenta(_vm.Cmyk.Magenta + 0.01)); });
            EventUtil.HandleKey(Key.Up, CmykYellow, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithYellow(_vm.Cmyk.Yellow + 0.01)); });
            EventUtil.HandleKey(Key.Up, CmykKey, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithKey(_vm.Cmyk.Key + 0.01)); });
            EventUtil.HandleKey(Key.Up, CmykScaledCyan, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledCyan(_vm.Cmyk.ScaledCyan + 1)); });
            EventUtil.HandleKey(Key.Up, CmykScaledMagenta, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledMagenta(_vm.Cmyk.ScaledMagenta + 1)); });
            EventUtil.HandleKey(Key.Up, CmykScaledYellow, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledYellow(_vm.Cmyk.ScaledYellow + 1)); });
            EventUtil.HandleKey(Key.Up, CmykScaledKey, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledKey(_vm.Cmyk.ScaledKey + 1)); });

            EventUtil.HandleKey(Key.Down, CmykCyan, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithCyan(_vm.Cmyk.Cyan - 0.01)); });
            EventUtil.HandleKey(Key.Down, CmykMagenta, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithMagenta(_vm.Cmyk.Magenta - 0.01)); });
            EventUtil.HandleKey(Key.Down, CmykYellow, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithYellow(_vm.Cmyk.Yellow - 0.01)); });
            EventUtil.HandleKey(Key.Down, CmykKey, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithKey(_vm.Cmyk.Key - 0.01)); });
            EventUtil.HandleKey(Key.Down, CmykScaledCyan, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledCyan(_vm.Cmyk.ScaledCyan - 1)); });
            EventUtil.HandleKey(Key.Down, CmykScaledMagenta, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledMagenta(_vm.Cmyk.ScaledMagenta - 1)); });
            EventUtil.HandleKey(Key.Down, CmykScaledYellow, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledYellow(_vm.Cmyk.ScaledYellow - 1)); });
            EventUtil.HandleKey(Key.Down, CmykScaledKey, () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledKey(_vm.Cmyk.ScaledKey - 1)); });

            EventUtil.HandleMouseWheel(CmykCyan,
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithCyan(_vm.Cmyk.Cyan + 0.01)); },
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithCyan(_vm.Cmyk.Cyan - 0.01)); });
            EventUtil.HandleMouseWheel(CmykMagenta,
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithMagenta(_vm.Cmyk.Magenta + 0.01)); },
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithMagenta(_vm.Cmyk.Magenta - 0.01)); });
            EventUtil.HandleMouseWheel(CmykYellow,
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithYellow(_vm.Cmyk.Yellow + 0.01)); },
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithYellow(_vm.Cmyk.Yellow - 0.01)); });
            EventUtil.HandleMouseWheel(CmykKey,
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithKey(_vm.Cmyk.Key + 0.01)); },
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithKey(_vm.Cmyk.Key - 0.01)); });
            EventUtil.HandleMouseWheel(CmykScaledCyan,
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledCyan(_vm.Cmyk.ScaledCyan + 1)); },
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledCyan(_vm.Cmyk.ScaledCyan - 1)); });
            EventUtil.HandleMouseWheel(CmykScaledMagenta,
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledMagenta(_vm.Cmyk.ScaledMagenta + 1)); },
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledMagenta(_vm.Cmyk.ScaledMagenta - 1)); });
            EventUtil.HandleMouseWheel(CmykScaledYellow,
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledYellow(_vm.Cmyk.ScaledYellow + 1)); },
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledYellow(_vm.Cmyk.ScaledYellow - 1)); });
            EventUtil.HandleMouseWheel(CmykScaledKey,
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledKey(_vm.Cmyk.ScaledKey + 1)); },
                                       () => { _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledKey(_vm.Cmyk.ScaledKey - 1)); });

            EventUtil.HandleSliderChange(CmykCyanSlider, value => { _vm.RefreshFromCmyk(_vm.Cmyk.WithCyan(value)); });
            EventUtil.HandleSliderChange(CmykMagentaSlider, value => { _vm.RefreshFromCmyk(_vm.Cmyk.WithMagenta(value)); });
            EventUtil.HandleSliderChange(CmykYellowSlider, value => { _vm.RefreshFromCmyk(_vm.Cmyk.WithYellow(value)); });
            EventUtil.HandleSliderChange(CmykKeySlider, value => { _vm.RefreshFromCmyk(_vm.Cmyk.WithKey(value)); });

            _vm.PropertyChangedByUser += (o, ev) =>
            {
                bool   isDouble;
                double doubleVal;
                Cmyk?  nullableCmyk;

                switch (ev.PropertyName)
                {
                case nameof(MainWindowViewModel.CmykText):
                    nullableCmyk = Cmyk.FromString(_vm.CmykText);
                    if (nullableCmyk.HasValue)
                    {
                        _vm.RefreshFromCmyk(nullableCmyk.Value);
                    }
                    break;

                case nameof(MainWindowViewModel.CmykScaledText):
                    nullableCmyk = Cmyk.FromScaledString(_vm.CmykScaledText);
                    if (nullableCmyk.HasValue)
                    {
                        _vm.RefreshFromCmyk(nullableCmyk.Value);
                    }
                    break;

                case nameof(MainWindowViewModel.CmykCyan):
                    isDouble = double.TryParse(_vm.CmykCyan, out doubleVal);
                    if (!isDouble || !Cmyk.IsValidComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromCmyk(_vm.Cmyk.WithCyan(doubleVal));
                    break;

                case nameof(MainWindowViewModel.CmykMagenta):
                    isDouble = double.TryParse(_vm.CmykMagenta, out doubleVal);
                    if (!isDouble || !Cmyk.IsValidComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromCmyk(_vm.Cmyk.WithMagenta(doubleVal));
                    break;

                case nameof(MainWindowViewModel.CmykYellow):
                    isDouble = double.TryParse(_vm.CmykYellow, out doubleVal);
                    if (!isDouble || !Cmyk.IsValidComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromCmyk(_vm.Cmyk.WithYellow(doubleVal));
                    break;

                case nameof(MainWindowViewModel.CmykKey):
                    isDouble = double.TryParse(_vm.CmykKey, out doubleVal);
                    if (!isDouble || !Cmyk.IsValidComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromCmyk(_vm.Cmyk.WithKey(doubleVal));
                    break;

                case nameof(MainWindowViewModel.CmykScaledCyan):
                    isDouble = double.TryParse(_vm.CmykScaledCyan, out doubleVal);
                    if (!isDouble || !Cmyk.IsValidScaledComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledCyan(doubleVal));
                    break;

                case nameof(MainWindowViewModel.CmykScaledMagenta):
                    isDouble = double.TryParse(_vm.CmykScaledMagenta, out doubleVal);
                    if (!isDouble || !Cmyk.IsValidScaledComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledMagenta(doubleVal));
                    break;

                case nameof(MainWindowViewModel.CmykScaledYellow):
                    isDouble = double.TryParse(_vm.CmykScaledYellow, out doubleVal);
                    if (!isDouble || !Cmyk.IsValidScaledComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledYellow(doubleVal));
                    break;

                case nameof(MainWindowViewModel.CmykScaledKey):
                    isDouble = double.TryParse(_vm.CmykScaledKey, out doubleVal);
                    if (!isDouble || !Cmyk.IsValidScaledComponent(doubleVal))
                    {
                        return;
                    }
                    _vm.RefreshFromCmyk(_vm.Cmyk.WithScaledKey(doubleVal));
                    break;
                }
            };

            SetupClipboardButton(CmykScaledTextClipboardButton, () => _vm.CmykScaledText);
            SetupClipboardButton(CmykTextClipboardButton, () => _vm.CmykText);

            //
            // LAB fields
            //

            EventUtil.HandleKey(Key.Up, LabL, () => { _vm.RefreshFromLab(_vm.Lab.WithL(_vm.Lab.L + 1)); });
            EventUtil.HandleKey(Key.Up, LabA, () => { _vm.RefreshFromLab(_vm.Lab.WithA(_vm.Lab.A + 1)); });
            EventUtil.HandleKey(Key.Up, LabB, () => { _vm.RefreshFromLab(_vm.Lab.WithB(_vm.Lab.B + 1)); });

            EventUtil.HandleKey(Key.Down, LabL, () => { _vm.RefreshFromLab(_vm.Lab.WithL(_vm.Lab.L - 1)); });
            EventUtil.HandleKey(Key.Down, LabA, () => { _vm.RefreshFromLab(_vm.Lab.WithA(_vm.Lab.A - 1)); });
            EventUtil.HandleKey(Key.Down, LabB, () => { _vm.RefreshFromLab(_vm.Lab.WithB(_vm.Lab.B - 1)); });

            EventUtil.HandleMouseWheel(LabL,
                                       () => { _vm.RefreshFromLab(_vm.Lab.WithL(_vm.Lab.L + 1)); },
                                       () => { _vm.RefreshFromLab(_vm.Lab.WithL(_vm.Lab.L - 1)); });
            EventUtil.HandleMouseWheel(LabA,
                                       () => { _vm.RefreshFromLab(_vm.Lab.WithA(_vm.Lab.A + 1)); },
                                       () => { _vm.RefreshFromLab(_vm.Lab.WithA(_vm.Lab.A - 1)); });
            EventUtil.HandleMouseWheel(LabB,
                                       () => { _vm.RefreshFromLab(_vm.Lab.WithB(_vm.Lab.B + 1)); },
                                       () => { _vm.RefreshFromLab(_vm.Lab.WithB(_vm.Lab.B - 1)); });

            EventUtil.HandleSliderChange(LabLSlider, value => { _vm.RefreshFromLab(_vm.Lab.WithL(value)); });
            EventUtil.HandleSliderChange(LabASlider, value => { _vm.RefreshFromLab(_vm.Lab.WithA(value)); });
            EventUtil.HandleSliderChange(LabBSlider, value => { _vm.RefreshFromLab(_vm.Lab.WithB(value)); });

            _vm.PropertyChangedByUser += (o, ev) =>
            {
                switch (ev.PropertyName)
                {
                case nameof(MainWindowViewModel.LabText):
                    var nullableLab = Lab.FromString(_vm.LabText);
                    if (nullableLab.HasValue)
                    {
                        _vm.RefreshFromLab(nullableLab.Value);
                    }
                    break;

                case nameof(MainWindowViewModel.LabL):
                    if (!Lab.IsValidL(_vm.LabL))
                    {
                        return;
                    }
                    _vm.RefreshFromLab(_vm.Lab.WithL(_vm.LabL));
                    break;

                case nameof(MainWindowViewModel.LabA):
                    if (!Lab.IsValidA(_vm.LabA))
                    {
                        return;
                    }
                    _vm.RefreshFromLab(_vm.Lab.WithA(_vm.LabA));
                    break;

                case nameof(MainWindowViewModel.LabB):
                    if (!Lab.IsValidB(_vm.LabB))
                    {
                        return;
                    }
                    _vm.RefreshFromLab(_vm.Lab.WithB(_vm.LabB));
                    break;
                }
            };

            SetupClipboardButton(LabTextClipboardButton, () => _vm.LabText);

            //
            // ----
            //

            // Hack to bring window to front on start. (I had instances where I would open the tool
            // only for it to come up behind my current window.)
            if (!IsVisible)
            {
                Show();
            }
            WindowState = WindowState.Normal;
            Activate();
            Topmost = true;
            Topmost = false;
            Focus();
        }